Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/opencode/script/seed-e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const seed = async () => {
const { Instance } = await import("../src/project/instance")
const { InstanceBootstrap } = await import("../src/project/bootstrap")
const { Config } = await import("../src/config/config")
const { disposeRuntime } = await import("../src/effect/runtime")
const { disposeAllRuntimes } = await import("../src/effect/runtime")
const { Session } = await import("../src/session")
const { MessageID, PartID } = await import("../src/session/schema")
const { Project } = await import("../src/project/project")
Expand Down Expand Up @@ -55,7 +55,7 @@ const seed = async () => {
})
} finally {
await Instance.disposeAll().catch(() => {})
await disposeRuntime().catch(() => {})
await disposeAllRuntimes().catch(() => {})
}
}

Expand Down
3 changes: 3 additions & 0 deletions packages/opencode/src/account/effect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
PollSuccess,
UserCode,
} from "./schema"
import { makeRuntimeGlobal } from "@/effect/runtime"

export * from "./schema"

Expand Down Expand Up @@ -357,4 +358,6 @@ export namespace AccountEffect {
)

export const defaultLayer = layer.pipe(Layer.provide(AccountRepo.layer), Layer.provide(FetchHttpClient.layer))

export const { runtime, runSync, runPromise } = makeRuntimeGlobal(Service, defaultLayer)
}
27 changes: 5 additions & 22 deletions packages/opencode/src/account/index.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,24 @@
import { Effect, Option } from "effect"
import { Option } from "effect"

import {
Account as AccountSchema,
type AccountError,
type AccessToken,
AccountID,
AccountEffect,
OrgID,
} from "./effect"
import { Account as AccountSchema, type AccessToken, AccountID, OrgID, AccountEffect } from "./effect"

export { AccessToken, AccountID, OrgID } from "./effect"

import { runtime } from "@/effect/runtime"

function runSync<A>(f: (service: AccountEffect.Interface) => Effect.Effect<A, AccountError>) {
return runtime.runSync(AccountEffect.Service.use(f))
}

function runPromise<A>(f: (service: AccountEffect.Interface) => Effect.Effect<A, AccountError>) {
return runtime.runPromise(AccountEffect.Service.use(f))
}

export namespace Account {
export const Account = AccountSchema
export type Account = AccountSchema

export function active(): Account | undefined {
return Option.getOrUndefined(runSync((service) => service.active()))
return Option.getOrUndefined(AccountEffect.runSync((service) => service.active()))
}

export async function config(accountID: AccountID, orgID: OrgID): Promise<Record<string, unknown> | undefined> {
const config = await runPromise((service) => service.config(accountID, orgID))
const config = await AccountEffect.runPromise((service) => service.config(accountID, orgID))
return Option.getOrUndefined(config)
}

export async function token(accountID: AccountID): Promise<AccessToken | undefined> {
const token = await runPromise((service) => service.token(accountID))
const token = await AccountEffect.runPromise((service) => service.token(accountID))
return Option.getOrUndefined(token)
}
}
3 changes: 3 additions & 0 deletions packages/opencode/src/auth/effect.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import path from "path"
import { Effect, Layer, Record, Result, Schema, ServiceMap } from "effect"
import { makeRuntimeGlobal } from "@/effect/runtime"
import { Global } from "../global"
import { Filesystem } from "../util/filesystem"

Expand Down Expand Up @@ -91,4 +92,6 @@ export namespace AuthEffect {
return Service.of({ get, all, set, remove })
}),
)

export const { runtime, runSync, runPromise } = makeRuntimeGlobal(Service, layer)
}
14 changes: 4 additions & 10 deletions packages/opencode/src/auth/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
import { Effect } from "effect"
import z from "zod"
import { runtime } from "@/effect/runtime"
import * as S from "./effect"

export { OAUTH_DUMMY_KEY } from "./effect"

function runPromise<A>(f: (service: S.AuthEffect.Interface) => Effect.Effect<A, S.AuthError>) {
return runtime.runPromise(S.AuthEffect.Service.use(f))
}

export namespace Auth {
export const Oauth = z
.object({
Expand Down Expand Up @@ -40,18 +34,18 @@ export namespace Auth {
export type Info = z.infer<typeof Info>

export async function get(providerID: string) {
return runPromise((service) => service.get(providerID))
return S.AuthEffect.runPromise((service) => service.get(providerID))
}

export async function all(): Promise<Record<string, Info>> {
return runPromise((service) => service.all())
return S.AuthEffect.runPromise((service) => service.all())
}

export async function set(key: string, info: Info) {
return runPromise((service) => service.set(key, info))
return S.AuthEffect.runPromise((service) => service.set(key, info))
}

export async function remove(key: string) {
return runPromise((service) => service.remove(key))
return S.AuthEffect.runPromise((service) => service.remove(key))
}
}
9 changes: 4 additions & 5 deletions packages/opencode/src/cli/cmd/account.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { cmd } from "./cmd"
import { Duration, Effect, Match, Option } from "effect"
import { UI } from "../ui"
import { runtime } from "@/effect/runtime"
import { AccountID, AccountEffect, OrgID, PollExpired, type PollResult } from "@/account/effect"
import { type AccountError } from "@/account/schema"
import * as Prompt from "../effect/prompt"
Expand Down Expand Up @@ -160,7 +159,7 @@ export const LoginCommand = cmd({
}),
async handler(args) {
UI.empty()
await runtime.runPromise(loginEffect(args.url))
await AccountEffect.runtime().runPromise(loginEffect(args.url))
},
})

Expand All @@ -174,7 +173,7 @@ export const LogoutCommand = cmd({
}),
async handler(args) {
UI.empty()
await runtime.runPromise(logoutEffect(args.email))
await AccountEffect.runtime().runPromise(logoutEffect(args.email))
},
})

Expand All @@ -183,7 +182,7 @@ export const SwitchCommand = cmd({
describe: false,
async handler() {
UI.empty()
await runtime.runPromise(switchEffect())
await AccountEffect.runtime().runPromise(switchEffect())
},
})

Expand All @@ -192,7 +191,7 @@ export const OrgsCommand = cmd({
describe: false,
async handler() {
UI.empty()
await runtime.runPromise(orgsEffect())
await AccountEffect.runtime().runPromise(orgsEffect())
},
})

Expand Down
12 changes: 0 additions & 12 deletions packages/opencode/src/effect/instance-registry.ts

This file was deleted.

68 changes: 0 additions & 68 deletions packages/opencode/src/effect/instances.ts

This file was deleted.

112 changes: 96 additions & 16 deletions packages/opencode/src/effect/runtime.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,103 @@
import { Effect, Layer, ManagedRuntime } from "effect"
import { AccountEffect } from "@/account/effect"
import { AuthEffect } from "@/auth/effect"
import { Instances } from "@/effect/instances"
import type { InstanceServices } from "@/effect/instances"
import { TruncateEffect } from "@/tool/truncate-effect"
import * as ServiceMap from "effect/ServiceMap"
import { InstanceContext } from "./instance-context"
import { Instance } from "@/project/instance"
import * as Exit from "effect/Exit"
import * as Cause from "effect/Cause"

export const runtime = ManagedRuntime.make(
Layer.mergeAll(
AccountEffect.defaultLayer, //
TruncateEffect.defaultLayer,
Instances.layer,
).pipe(Layer.provideMerge(AuthEffect.layer)),
)
export const opencodeMemoMap = Layer.makeMemoMapUnsafe()

export function runPromiseInstance<A, E>(effect: Effect.Effect<A, E, InstanceServices>) {
return runtime.runPromise(effect.pipe(Effect.provide(Instances.get(Instance.directory))))
export interface ServiceRuntime<I, S, E> {
readonly runtime: () => ManagedRuntime.ManagedRuntime<I, E>
readonly runSync: <A, E>(f: (service: S) => Effect.Effect<A, E>) => A
readonly runPromise: <A, E>(f: (service: S) => Effect.Effect<A, E>, options?: Effect.RunOptions) => Promise<A>
}

export function disposeRuntime() {
return runtime.dispose()
export const makeRuntimeGlobal = <I, S, E>(
service: ServiceMap.Service<I, S>,
layer: Layer.Layer<I, E>,
): ServiceRuntime<I, S, E> => {
let runtime = globalRuntimes.get(layer) as ManagedRuntime.ManagedRuntime<I, E> | undefined
if (!runtime) {
runtime = ManagedRuntime.make(layer, { memoMap: opencodeMemoMap })
globalRuntimes.set(layer, runtime)
}
const runSync = <A, E>(f: (service: S) => Effect.Effect<A, E>) => runtime.runSync(service.use(f))
const runPromise = <A, E>(f: (service: S) => Effect.Effect<A, E>) => runtime.runPromise(service.use(f))
return { runtime: () => runtime, runSync, runPromise }
}
const globalRuntimes = new Map<Layer.Layer<any, any, any>, ManagedRuntime.ManagedRuntime<any, any>>()

export const makeRuntimeInstance = <I, S, E>(
service: ServiceMap.Service<I, S>,
layer: Layer.Layer<I, E, InstanceContext>,
): ServiceRuntime<I, S, E> => {
const runSync = <A, E>(f: (service: S) => Effect.Effect<A, E>) => getInstanceRuntime(layer).runSync(service.use(f))
const runPromise = <A, E>(f: (service: S) => Effect.Effect<A, E>, options?: Effect.RunOptions) =>
new Promise<A>((resolve, reject) => {
const fiber = getInstanceRuntime(layer).runFork(service.use(f), options)
fiber.addObserver((exit) => {
if (Exit.isSuccess(exit)) {
return resolve(exit.value)
} else if (Cause.hasInterruptsOnly(exit.cause)) {
return
}
reject(Cause.squash(exit.cause))
})
})
return { runtime: () => getInstanceRuntime(layer), runSync, runPromise }
}

const allRuntimes = new Map<string, Map<Layer.Layer<any, any, any>, ManagedRuntime.ManagedRuntime<any, any>>>()

const getInstanceRuntime = <A, E>(
layer: Layer.Layer<A, E, InstanceContext>,
): ManagedRuntime.ManagedRuntime<A | InstanceContext, E> => {
const directory = Instance.directory

let map = allRuntimes.get(directory)
if (!map) {
map = new Map()
allRuntimes.set(directory, map)
}

let runtime = map.get(layer) as ManagedRuntime.ManagedRuntime<A | InstanceContext, E> | undefined
if (!runtime) {
runtime = ManagedRuntime.make(
Layer.provideMerge(
layer,
Layer.sync(InstanceContext, () => InstanceContext.of(Instance.current)),
),
{ memoMap: opencodeMemoMap },
)
map.set(layer, runtime)
}

return runtime
}

export async function disposeAllRuntimes() {
let promises: Promise<void>[] = []
for (const runtime of globalRuntimes.values()) {
promises.push(runtime.dispose())
}
globalRuntimes.clear()
for (const map of allRuntimes.values()) {
for (const runtime of map.values()) {
promises.push(runtime.dispose())
}
}
allRuntimes.clear()
await Promise.all(promises)
}

export const disposeInstanceRuntimes = async (directory: string) => {
const map = allRuntimes.get(directory)
if (!map) return
let promises: Promise<void>[] = []
for (const runtime of map.values()) {
promises.push(runtime.dispose())
}
allRuntimes.delete(directory)
await Promise.all(promises)
}
Loading
Loading