fix(plugin): adapt promise host wire values

This commit is contained in:
Dax Raad
2026-07-10 17:18:03 -04:00
parent b87bb4486f
commit 20e37e7122
3 changed files with 137 additions and 19 deletions
@@ -193,12 +193,12 @@ import { ClientError } from "./client-error"
export interface ClientOptions { export interface ClientOptions {
readonly baseUrl: string readonly baseUrl: string
readonly fetch?: typeof globalThis.fetch readonly fetch?: typeof globalThis.fetch
readonly headers?: HeadersInit readonly headers?: RequestInit["headers"]
} }
export interface RequestOptions { export interface RequestOptions {
readonly signal?: AbortSignal readonly signal?: AbortSignal
readonly headers?: HeadersInit readonly headers?: RequestInit["headers"]
} }
interface RequestDescriptor { interface RequestDescriptor {
+134 -16
View File
@@ -2,13 +2,26 @@ export * as PluginPromise from "./promise"
import { Plugin } from "@opencode-ai/plugin/v2/effect" import { Plugin } from "@opencode-ai/plugin/v2/effect"
import type { AnyTool } from "@opencode-ai/plugin/v2/tool" import type { AnyTool } from "@opencode-ai/plugin/v2/tool"
import { Effect, Scope, Stream } from "effect" import { Agent } from "@opencode-ai/schema/agent"
import { Integration } from "@opencode-ai/schema/integration"
import { Location } from "@opencode-ai/schema/location"
import { Model } from "@opencode-ai/schema/model"
import { Provider } from "@opencode-ai/schema/provider"
import { AbsolutePath } from "@opencode-ai/schema/schema"
import { Session } from "@opencode-ai/schema/session"
import { SessionMessage } from "@opencode-ai/schema/session-message"
import { Workspace } from "@opencode-ai/schema/workspace"
import { DateTime, Effect, Scope, Stream } from "effect"
import { Tool } from "../tool/tool" import { Tool } from "../tool/tool"
type HostRegistration = { readonly dispose: Effect.Effect<void> } type HostRegistration = { readonly dispose: Effect.Effect<void> }
type Registration = { readonly dispose: () => Promise<void> } type Registration = { readonly dispose: () => Promise<void> }
type PromisePlugin = import("@opencode-ai/plugin/v2/plugin").Plugin type PromisePlugin = import("@opencode-ai/plugin/v2/plugin").Plugin
type PromisePluginContext = import("@opencode-ai/plugin/v2/plugin").Context type PromisePluginContext = import("@opencode-ai/plugin/v2/plugin").Context
type PromiseEvent = ReturnType<PromisePluginContext["event"]["subscribe"]> extends AsyncIterable<infer Event>
? Event
: never
type JsonValue = null | boolean | number | string | Array<JsonValue> | { [key: string]: JsonValue }
/** /**
* Adapts a Promise plugin into an Effect plugin so the existing Effect-only * Adapts a Promise plugin into an Effect plugin so the existing Effect-only
@@ -33,7 +46,7 @@ export function fromPromise(plugin: PromisePlugin) {
dispose: () => Effect.runPromiseWith(context)(registration.dispose), dispose: () => Effect.runPromiseWith(context)(registration.dispose),
})) }))
const run = <A, E>(effect: Effect.Effect<A, E>) => Effect.runPromiseWith(context)(effect) const run = <A, E>(effect: Effect.Effect<A, E>) => Effect.runPromiseWith(context)(effect).then(wire)
const transform = const transform =
<Draft>(domain: { <Draft>(domain: {
@@ -60,11 +73,12 @@ export function fromPromise(plugin: PromisePlugin) {
catalog: { catalog: {
provider: { provider: {
list: (input) => run(host.catalog.provider.list(input)), list: (input) => run(host.catalog.provider.list(input)),
get: (input) => run(host.catalog.provider.get(input)), get: (input) => run(host.catalog.provider.get({ ...input, providerID: Provider.ID.make(input.providerID) })),
}, },
model: { model: {
list: (input) => run(host.catalog.model.list(input)), list: (input) => run(host.catalog.model.list(input)),
default: (input) => run(host.catalog.model.default(input)), default: (input) =>
run(host.catalog.model.default(input)).then((result) => ({ ...result, data: result.data ?? null })),
}, },
transform: transform(host.catalog), transform: transform(host.catalog),
reload: () => run(host.catalog.reload()), reload: () => run(host.catalog.reload()),
@@ -75,19 +89,48 @@ export function fromPromise(plugin: PromisePlugin) {
reload: () => run(host.command.reload()), reload: () => run(host.command.reload()),
}, },
event: { event: {
subscribe: () => Stream.toAsyncIterable(host.event.subscribe()), subscribe: () => Stream.toAsyncIterable(host.event.subscribe().pipe(Stream.map(wireEvent))),
}, },
integration: { integration: {
list: (input) => run(host.integration.list(input)), list: (input) => run(host.integration.list(input)),
get: (input) => run(host.integration.get(input)), get: (input) =>
run(host.integration.get({ ...input, integrationID: Integration.ID.make(input.integrationID) })).then(
(result) => ({ ...result, data: result.data ?? null }),
),
connect: { connect: {
key: (input) => run(host.integration.connect.key(input)), key: (input) =>
oauth: (input) => run(host.integration.connect.oauth(input)), run(host.integration.connect.key({ ...input, integrationID: Integration.ID.make(input.integrationID) })),
oauth: (input) =>
run(
host.integration.connect.oauth({
...input,
integrationID: Integration.ID.make(input.integrationID),
methodID: Integration.MethodID.make(input.methodID),
}),
),
}, },
attempt: { attempt: {
status: (input) => run(host.integration.attempt.status(input)), status: (input) =>
complete: (input) => run(host.integration.attempt.complete(input)), run(
cancel: (input) => run(host.integration.attempt.cancel(input)), host.integration.attempt.status({
...input,
attemptID: Integration.AttemptID.make(input.attemptID),
}),
),
complete: (input) =>
run(
host.integration.attempt.complete({
...input,
attemptID: Integration.AttemptID.make(input.attemptID),
}),
),
cancel: (input) =>
run(
host.integration.attempt.cancel({
...input,
attemptID: Integration.AttemptID.make(input.attemptID),
}),
),
}, },
transform: transform(host.integration), transform: transform(host.integration),
reload: () => run(host.integration.reload()), reload: () => run(host.integration.reload()),
@@ -122,11 +165,53 @@ export function fromPromise(plugin: PromisePlugin) {
register(host.tool.hook(name, (event) => Effect.promise(() => Promise.resolve(callback(event))))), register(host.tool.hook(name, (event) => Effect.promise(() => Promise.resolve(callback(event))))),
}, },
session: { session: {
create: (input) => run(host.session.create(input)), create: (input) =>
get: (input) => run(host.session.get(input)), run(
prompt: (input) => run(host.session.prompt(input)), host.session.create(
command: (input) => run(host.session.command(input)), input === undefined
interrupt: (input) => run(host.session.interrupt(input)), ? undefined
: {
id: input.id == null ? undefined : Session.ID.make(input.id),
agent: input.agent == null ? undefined : Agent.ID.make(input.agent),
model: input.model == null ? undefined : model(input.model),
location:
input.location == null
? undefined
: Location.Ref.make({
directory: AbsolutePath.make(input.location.directory),
workspaceID:
input.location.workspaceID === undefined
? undefined
: Workspace.ID.make(input.location.workspaceID),
}),
},
),
),
get: (input) => run(host.session.get({ sessionID: Session.ID.make(input.sessionID) })),
prompt: (input) =>
run(
host.session.prompt({
...input,
sessionID: Session.ID.make(input.sessionID),
id: input.id == null ? undefined : SessionMessage.ID.make(input.id),
delivery: input.delivery ?? undefined,
resume: input.resume ?? undefined,
}),
),
command: (input) =>
run(
host.session.command({
...input,
sessionID: Session.ID.make(input.sessionID),
id: input.id == null ? undefined : SessionMessage.ID.make(input.id),
agent: input.agent == null ? undefined : Agent.ID.make(input.agent),
model: input.model == null ? undefined : model(input.model),
arguments: input.arguments ?? undefined,
delivery: input.delivery ?? undefined,
resume: input.resume ?? undefined,
}),
),
interrupt: (input) => run(host.session.interrupt({ sessionID: Session.ID.make(input.sessionID) })),
}, },
} }
@@ -137,6 +222,39 @@ export function fromPromise(plugin: PromisePlugin) {
}) })
} }
function model(input: { readonly id: string; readonly providerID: string; readonly variant?: string }) {
return Model.Ref.make({
id: Model.ID.make(input.id),
providerID: Provider.ID.make(input.providerID),
variant: input.variant === undefined ? undefined : Model.VariantID.make(input.variant),
})
}
type Wire<Value> = unknown extends Value
? JsonValue
: Value extends string | number | boolean | bigint | symbol | null | undefined
? Value
: Value extends DateTime.DateTime
? number
: Value extends ReadonlyArray<infer Item>
? Array<Wire<Item>>
: Value extends object
? { -readonly [Key in keyof Value]: Wire<Value[Key]> }
: Value
function wire<Value>(value: Value): Wire<Value>
function wire(value: unknown): unknown {
if (DateTime.isDateTime(value)) return DateTime.toEpochMillis(value)
if (Array.isArray(value)) return value.map(wire)
if (typeof value !== "object" || value === null) return value
return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, wire(item)]))
}
function wireEvent(value: unknown): PromiseEvent
function wireEvent(value: unknown): unknown {
return wire(value)
}
function fromPromiseTool(tool: AnyTool) { function fromPromiseTool(tool: AnyTool) {
if ("jsonSchema" in tool) if ("jsonSchema" in tool)
return Tool.make({ return Tool.make({
File diff suppressed because one or more lines are too long