@opencode/plugin 0.0.0-reserved → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/dist/app.d.ts +5 -0
  2. package/dist/app.js +0 -0
  3. package/dist/effect/agent.d.ts +15 -0
  4. package/dist/effect/agent.js +0 -0
  5. package/dist/effect/aisdk.d.ts +20 -0
  6. package/dist/effect/aisdk.js +0 -0
  7. package/dist/effect/catalog.d.ts +33 -0
  8. package/dist/effect/catalog.js +2 -0
  9. package/dist/effect/command.d.ts +23 -0
  10. package/dist/effect/command.js +0 -0
  11. package/dist/effect/event.d.ts +3 -0
  12. package/dist/effect/event.js +0 -0
  13. package/dist/effect/index.d.ts +18 -0
  14. package/dist/effect/index.js +17 -0
  15. package/dist/effect/integration.d.ts +80 -0
  16. package/dist/effect/integration.js +2 -0
  17. package/dist/effect/mcp.d.ts +15 -0
  18. package/dist/effect/mcp.js +0 -0
  19. package/dist/effect/permission.d.ts +21 -0
  20. package/dist/effect/permission.js +0 -0
  21. package/dist/effect/plugin.d.ts +56 -0
  22. package/dist/effect/plugin.js +3 -0
  23. package/dist/effect/reference.d.ts +14 -0
  24. package/dist/effect/reference.js +0 -0
  25. package/dist/effect/registration.d.ts +13 -0
  26. package/dist/effect/registration.js +0 -0
  27. package/dist/effect/rpc.d.ts +19 -0
  28. package/dist/effect/rpc.js +0 -0
  29. package/dist/effect/session.d.ts +107 -0
  30. package/dist/effect/session.js +0 -0
  31. package/dist/effect/shell.d.ts +14 -0
  32. package/dist/effect/shell.js +0 -0
  33. package/dist/effect/skill.d.ts +15 -0
  34. package/dist/effect/skill.js +1 -0
  35. package/dist/effect/storage.d.ts +8 -0
  36. package/dist/effect/storage.js +0 -0
  37. package/dist/effect/tool.d.ts +52 -0
  38. package/dist/effect/tool.js +1 -0
  39. package/dist/effect/vcs.d.ts +41 -0
  40. package/dist/effect/vcs.js +0 -0
  41. package/dist/effect/websearch.d.ts +20 -0
  42. package/dist/effect/websearch.js +0 -0
  43. package/dist/effect/worktree.d.ts +18 -0
  44. package/dist/effect/worktree.js +0 -0
  45. package/dist/host.d.ts +12 -0
  46. package/dist/host.js +33 -0
  47. package/dist/options.d.ts +1 -0
  48. package/dist/options.js +0 -0
  49. package/dist/promise/adapter.d.ts +12 -0
  50. package/dist/promise/adapter.js +444 -0
  51. package/dist/promise/agent.d.ts +15 -0
  52. package/dist/promise/agent.js +0 -0
  53. package/dist/promise/aisdk.d.ts +20 -0
  54. package/dist/promise/aisdk.js +0 -0
  55. package/dist/promise/catalog.d.ts +33 -0
  56. package/dist/promise/catalog.js +0 -0
  57. package/dist/promise/command.d.ts +22 -0
  58. package/dist/promise/command.js +0 -0
  59. package/dist/promise/event.d.ts +3 -0
  60. package/dist/promise/event.js +0 -0
  61. package/dist/promise/index.d.ts +19 -0
  62. package/dist/promise/index.js +17 -0
  63. package/dist/promise/integration.d.ts +79 -0
  64. package/dist/promise/integration.js +2 -0
  65. package/dist/promise/mcp.d.ts +15 -0
  66. package/dist/promise/mcp.js +0 -0
  67. package/dist/promise/permission.d.ts +21 -0
  68. package/dist/promise/permission.js +0 -0
  69. package/dist/promise/plugin.d.ts +57 -0
  70. package/dist/promise/plugin.js +3 -0
  71. package/dist/promise/reference.d.ts +13 -0
  72. package/dist/promise/reference.js +0 -0
  73. package/dist/promise/registration.d.ts +12 -0
  74. package/dist/promise/registration.js +0 -0
  75. package/dist/promise/rpc.d.ts +22 -0
  76. package/dist/promise/rpc.js +0 -0
  77. package/dist/promise/session.d.ts +107 -0
  78. package/dist/promise/session.js +0 -0
  79. package/dist/promise/shell.d.ts +14 -0
  80. package/dist/promise/shell.js +0 -0
  81. package/dist/promise/skill.d.ts +15 -0
  82. package/dist/promise/skill.js +0 -0
  83. package/dist/promise/storage.d.ts +8 -0
  84. package/dist/promise/storage.js +0 -0
  85. package/dist/promise/tool.d.ts +56 -0
  86. package/dist/promise/tool.js +2 -0
  87. package/dist/promise/types.d.ts +3 -0
  88. package/dist/promise/types.js +0 -0
  89. package/dist/promise/vcs.d.ts +50 -0
  90. package/dist/promise/vcs.js +0 -0
  91. package/dist/promise/websearch.d.ts +21 -0
  92. package/dist/promise/websearch.js +0 -0
  93. package/dist/promise/worktree.d.ts +23 -0
  94. package/dist/promise/worktree.js +0 -0
  95. package/dist/rpc.d.ts +1 -0
  96. package/dist/rpc.js +1 -0
  97. package/dist/source.bun.d.ts +4 -0
  98. package/dist/source.bun.js +62 -0
  99. package/dist/source.d.ts +8 -0
  100. package/dist/source.js +59 -0
  101. package/dist/source.node.d.ts +5 -0
  102. package/dist/source.node.js +45 -0
  103. package/dist/storage.d.ts +14 -0
  104. package/dist/storage.js +0 -0
  105. package/dist/tui/context.d.ts +458 -0
  106. package/dist/tui/context.js +0 -0
  107. package/dist/tui/index.d.ts +2 -0
  108. package/dist/tui/index.js +2 -0
  109. package/dist/tui/plugin.d.ts +8 -0
  110. package/dist/tui/plugin.js +3 -0
  111. package/dist/tui/solid.d.ts +7 -0
  112. package/dist/tui/solid.js +16 -0
  113. package/dist/worktree.d.ts +18 -0
  114. package/dist/worktree.js +0 -0
  115. package/package.json +83 -11
  116. package/README.md +0 -5
@@ -0,0 +1,41 @@
1
+ import type { VcsApi } from "@opencode/client/effect/api";
2
+ import type { FileDiff } from "@opencode/schema/file-diff";
3
+ import type { Vcs } from "@opencode/schema/vcs";
4
+ import type { Effect } from "effect";
5
+ import type { Transform } from "./registration.js";
6
+ export interface VcsScope {
7
+ readonly directory: string;
8
+ readonly worktree: string;
9
+ readonly canonical: string;
10
+ readonly store?: string;
11
+ }
12
+ export interface VcsBranchesInput extends VcsScope {
13
+ readonly search?: string;
14
+ readonly limit?: number;
15
+ }
16
+ export interface VcsDiffInput extends VcsScope {
17
+ readonly mode: Vcs.Mode;
18
+ readonly base?: string;
19
+ readonly context: number;
20
+ readonly maxOutputBytes: number;
21
+ }
22
+ export interface VcsDefinition {
23
+ readonly id: string;
24
+ readonly name: string;
25
+ readonly info: (input: VcsScope) => Effect.Effect<Vcs.Info, unknown>;
26
+ readonly base?: (input: VcsScope) => Effect.Effect<Vcs.Base | null, unknown>;
27
+ readonly branches: (input: VcsBranchesInput) => Effect.Effect<Vcs.BranchList, unknown>;
28
+ readonly status: (input: VcsScope) => Effect.Effect<readonly Vcs.FileStatus[], unknown>;
29
+ readonly diff: (input: VcsDiffInput) => Effect.Effect<readonly FileDiff.Info[], unknown>;
30
+ }
31
+ export interface VcsDomain extends VcsApi<unknown> {
32
+ readonly transform: Transform<VcsEditor>;
33
+ readonly reload: () => Effect.Effect<void>;
34
+ }
35
+ export interface VcsEditor {
36
+ add(definition: VcsDefinition): void;
37
+ readonly default: {
38
+ get(): string | undefined;
39
+ set(selection: string): void;
40
+ };
41
+ }
File without changes
@@ -0,0 +1,20 @@
1
+ import type { WebSearch } from "@opencode/schema/websearch";
2
+ import type { WebSearchApi } from "@opencode/client/effect/api";
3
+ import type { Effect } from "effect";
4
+ import type { Transform } from "./registration.js";
5
+ export interface WebSearchDefinition {
6
+ readonly id: string;
7
+ readonly name: string;
8
+ readonly execute: (input: WebSearch.ProviderInput) => Effect.Effect<readonly WebSearch.Result[], unknown>;
9
+ }
10
+ export interface WebSearchDomain extends WebSearchApi<unknown> {
11
+ readonly transform: Transform<WebSearchEditor>;
12
+ readonly reload: () => Effect.Effect<void>;
13
+ }
14
+ export interface WebSearchEditor {
15
+ add(definition: WebSearchDefinition): void;
16
+ readonly default: {
17
+ get(): string | false | undefined;
18
+ set(selection: string | false): void;
19
+ };
20
+ }
File without changes
@@ -0,0 +1,18 @@
1
+ import type { WorktreeApi } from "@opencode/client/effect/api";
2
+ import type { Effect } from "effect";
3
+ import type { WorktreeCreateInput, WorktreeEntry, WorktreeRemoveInput, WorktreeResult } from "../worktree.js";
4
+ import type { Transform } from "./registration.js";
5
+ export interface WorktreeDefinition {
6
+ readonly id: string;
7
+ readonly create: (input: WorktreeCreateInput) => Effect.Effect<WorktreeResult, unknown>;
8
+ readonly remove: (input: WorktreeRemoveInput) => Effect.Effect<void, unknown>;
9
+ readonly list: (sourceDirectory: string) => Effect.Effect<readonly WorktreeEntry[], unknown>;
10
+ }
11
+ export interface WorktreeEditor {
12
+ /** Registers an implementation and selects it as the default. Later active registrations win. */
13
+ add(definition: WorktreeDefinition): void;
14
+ }
15
+ export interface WorktreeDomain extends WorktreeApi<unknown> {
16
+ readonly transform: Transform<WorktreeEditor>;
17
+ readonly reload: () => Effect.Effect<void>;
18
+ }
File without changes
package/dist/host.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ export * as Host from "./host.js";
2
+ export interface Target {
3
+ readonly directory: string;
4
+ readonly name?: string;
5
+ }
6
+ export interface Entrypoints {
7
+ readonly server?: string;
8
+ readonly tui?: string;
9
+ readonly rpc?: string;
10
+ }
11
+ export declare function resolve(target: Target): Entrypoints;
12
+ export declare function load(entrypoint: string): Promise<unknown>;
package/dist/host.js ADDED
@@ -0,0 +1,33 @@
1
+ export * as Host from "./host.js";
2
+ import path from "node:path";
3
+ import { importModule, resolveModule } from "@opencode/util/runtime-import";
4
+ export function resolve(target) {
5
+ const entry = (subpaths) => {
6
+ for (const subpath of subpaths) {
7
+ const specifier = target.name
8
+ ? [target.name, subpath].filter(Boolean).join("/")
9
+ : path.resolve(target.directory, subpath || "index");
10
+ try {
11
+ return resolveModule(specifier, target.directory);
12
+ }
13
+ catch (error) {
14
+ if (!(error instanceof Error) ||
15
+ !("code" in error) ||
16
+ ![
17
+ "ENOENT",
18
+ "ENOTDIR",
19
+ "MODULE_NOT_FOUND",
20
+ "ERR_MODULE_NOT_FOUND",
21
+ "ERR_PACKAGE_PATH_NOT_EXPORTED",
22
+ "ERR_UNSUPPORTED_DIR_IMPORT",
23
+ ].includes(String(error.code)))
24
+ throw error;
25
+ }
26
+ }
27
+ return undefined;
28
+ };
29
+ return { server: entry(["server", ""]), tui: entry(["tui"]), rpc: entry(["rpc"]) };
30
+ }
31
+ export function load(entrypoint) {
32
+ return importModule(entrypoint);
33
+ }
@@ -0,0 +1 @@
1
+ export type PluginOptions = Readonly<Record<string, any>>;
File without changes
@@ -0,0 +1,12 @@
1
+ import type { Scope } from "effect";
2
+ import type { Plugin } from "./plugin.js";
3
+ /**
4
+ * Adapts a Promise plugin into an Effect plugin so the existing Effect-only
5
+ * Core plugin runtime can run it unchanged.
6
+ *
7
+ * Hook registrations created during the async `setup` attach to the plugin's
8
+ * scope, so unloading the plugin disposes them. The captured fiber context
9
+ * preserves boot-time batching, so Promise-plugin transforms still coalesce
10
+ * into one reload per domain.
11
+ */
12
+ export declare function fromPromise(plugin: Plugin): import("../effect/plugin.js").Plugin<Scope.Scope>;
@@ -0,0 +1,444 @@
1
+ import { Tool } from "@opencode/schema/tool";
2
+ import { Effect, Schema, SchemaAST, Stream } from "effect";
3
+ import { HttpApiEndpoint, HttpApiSchema } from "effect/unstable/httpapi";
4
+ import { define } from "../effect/plugin.js";
5
+ const compiledEndpoints = new WeakMap();
6
+ const JsonInput = Schema.fromJsonString(Schema.Unknown);
7
+ class ReturnedRpcError extends Error {
8
+ type;
9
+ data;
10
+ constructor(type, message, data) {
11
+ super(message);
12
+ this.type = type;
13
+ this.data = data;
14
+ }
15
+ }
16
+ const makeStreams = Effect.fn("Plugin.Event.makeStreams")(function* () {
17
+ const context = yield* Effect.context();
18
+ const subscriptions = new Set();
19
+ // Async iterators own separate scopes, so close them when the plugin unloads.
20
+ yield* Effect.addFinalizer(() => Effect.promise(() => Promise.all(Array.from(subscriptions, (close) => close()))));
21
+ return ((stream, options) => ({
22
+ [Symbol.asyncIterator]() {
23
+ const iterator = Stream.toAsyncIterableWith(stream, context)[Symbol.asyncIterator]();
24
+ const close = () => {
25
+ subscriptions.delete(close);
26
+ options?.signal?.removeEventListener("abort", abort);
27
+ return iterator.return?.() ?? Promise.resolve({ done: true, value: undefined });
28
+ };
29
+ const abort = () => {
30
+ void close();
31
+ };
32
+ subscriptions.add(close);
33
+ options?.signal?.addEventListener("abort", abort, { once: true });
34
+ if (options?.signal?.aborted)
35
+ abort();
36
+ return {
37
+ next: () => iterator.next().then((result) => (result.done ? close().then(() => result) : result), (error) => close().then(() => Promise.reject(error))),
38
+ return: close,
39
+ };
40
+ },
41
+ }));
42
+ });
43
+ const rpcFromEffect = Effect.fn("Plugin.Rpc.fromEffect")(function* (host, streams) {
44
+ const context = yield* Effect.context();
45
+ const run = Effect.runPromiseWith(context);
46
+ const client = (definition) => {
47
+ const local = host(definition);
48
+ const subscribe = (name, options) => streams(local.events.subscribe(name), options);
49
+ return Object.assign(Object.fromEntries(Object.keys(definition.methods).map((name) => [
50
+ name,
51
+ (input, options) => {
52
+ // SAFETY: The local client was built from this definition, so every declared key is an Effect method.
53
+ // oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion
54
+ const method = local[name];
55
+ return run(method(input), { signal: options?.signal });
56
+ },
57
+ ])), {
58
+ events: {
59
+ subscribe,
60
+ on: (name, handler, options) => {
61
+ const controller = new AbortController();
62
+ const signal = options?.signal ? AbortSignal.any([controller.signal, options.signal]) : controller.signal;
63
+ void (async () => {
64
+ for await (const event of subscribe(name, { signal }))
65
+ await handler(event);
66
+ })().catch((error) => run(Effect.logError(error)));
67
+ return () => controller.abort();
68
+ },
69
+ },
70
+ });
71
+ };
72
+ const register = (definition, handlers) => run(host.register(definition,
73
+ // SAFETY: Each entry preserves its definition key; Core restores that method's erased schema and error types.
74
+ // oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion
75
+ Object.fromEntries(Object.entries(handlers).map(([name, handler]) => [
76
+ name,
77
+ (input, context) => Effect.tryPromise({
78
+ try: (signal) => {
79
+ // SAFETY: Promise RPC handlers return Promise values before this adapter erases their concrete types.
80
+ // oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion
81
+ return Reflect.apply(handler, undefined, [
82
+ input,
83
+ {
84
+ signal,
85
+ error: (type, message, data) => new ReturnedRpcError(type, message, data),
86
+ },
87
+ ]);
88
+ },
89
+ catch: (error) => hostRpcError(context, error),
90
+ }).pipe(Effect.flatMap((result) => result instanceof ReturnedRpcError
91
+ ? Effect.fail(hostRpcError(context, result))
92
+ : Effect.succeed(result))),
93
+ ])))).then((registration) => ({
94
+ dispose: () => run(registration.dispose),
95
+ events: { emit: (...args) => run(registration.events.emit(...args)) },
96
+ }));
97
+ // SAFETY: Client and register implement RpcDomain from the same portable definitions and schema adapters.
98
+ // oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion
99
+ return Object.assign(client, { register });
100
+ });
101
+ function hostRpcError(context, error) {
102
+ if (!(error instanceof ReturnedRpcError))
103
+ return error;
104
+ return context.error(error.type, error.message, error.data);
105
+ }
106
+ function compileEndpoint(endpoint) {
107
+ const cached = compiledEndpoints.get(endpoint);
108
+ if (cached)
109
+ return cached;
110
+ const payloadSchemas = Array.from(endpoint.payload.values()).flatMap(({ schemas }) => schemas);
111
+ const successSchemas = Array.from(endpoint.success);
112
+ if (payloadSchemas.length > 1 || successSchemas.length > 1) {
113
+ throw new Error(`Unsupported API schema cardinality: ${endpoint.identifier}`);
114
+ }
115
+ const inputs = [
116
+ endpoint.params,
117
+ endpoint.query === undefined ? undefined : Schema.toType(endpoint.query),
118
+ endpoint.headers,
119
+ ...payloadSchemas,
120
+ ].filter((schema) => schema !== undefined);
121
+ const success = (successSchemas[0] ?? HttpApiSchema.NoContent);
122
+ const noContent = HttpApiSchema.isNoContent(success.ast);
123
+ const type = Schema.toType(success).ast;
124
+ const data = SchemaAST.isObjects(success.ast)
125
+ ? success.ast.propertySignatures.find((property) => property.name === "data")
126
+ : undefined;
127
+ const output = !noContent &&
128
+ SchemaAST.isObjects(type) &&
129
+ type.indexSignatures.length === 0 &&
130
+ type.propertySignatures.length === 1 &&
131
+ type.propertySignatures[0]?.name === "data" &&
132
+ data !== undefined
133
+ ? Schema.make(data.type)
134
+ : success;
135
+ const compiled = {
136
+ decode: inputs.map((schema) => Schema.decodeUnknownEffect(schema)),
137
+ encode: Schema.encodeUnknownEffect(output),
138
+ noContent,
139
+ };
140
+ compiledEndpoints.set(endpoint, compiled);
141
+ return compiled;
142
+ }
143
+ /**
144
+ * Adapts a Promise plugin into an Effect plugin so the existing Effect-only
145
+ * Core plugin runtime can run it unchanged.
146
+ *
147
+ * Hook registrations created during the async `setup` attach to the plugin's
148
+ * scope, so unloading the plugin disposes them. The captured fiber context
149
+ * preserves boot-time batching, so Promise-plugin transforms still coalesce
150
+ * into one reload per domain.
151
+ */
152
+ export function fromPromise(plugin) {
153
+ return define({
154
+ id: plugin.id,
155
+ effect: (host) => Effect.gen(function* () {
156
+ const [{ ClientApi }, { OpenCodeEvent }] = yield* Effect.promise(() => Promise.all([import("@opencode/protocol/client"), import("@opencode/protocol/groups/event")]));
157
+ const AgentEndpoints = ClientApi.groups["server.agent"].endpoints;
158
+ const CommandEndpoints = ClientApi.groups["server.command"].endpoints;
159
+ const ExperimentalEndpoints = ClientApi.groups["server.experimental"].endpoints;
160
+ const GenerateEndpoints = ClientApi.groups["server.generate"].endpoints;
161
+ const IntegrationEndpoints = ClientApi.groups["server.integration"].endpoints;
162
+ const McpEndpoints = ClientApi.groups["server.mcp"].endpoints;
163
+ const ModelEndpoints = ClientApi.groups["server.model"].endpoints;
164
+ const PluginEndpoints = ClientApi.groups["server.plugin"].endpoints;
165
+ const PermissionEndpoints = ClientApi.groups["server.permission"].endpoints;
166
+ const ProviderEndpoints = ClientApi.groups["server.provider"].endpoints;
167
+ const ReferenceEndpoints = ClientApi.groups["server.reference"].endpoints;
168
+ const SessionEndpoints = ClientApi.groups["server.session"].endpoints;
169
+ const SkillEndpoints = ClientApi.groups["server.skill"].endpoints;
170
+ const VcsEndpoints = ClientApi.groups["server.vcs"].endpoints;
171
+ const WebSearchEndpoints = ClientApi.groups["server.websearch"].endpoints;
172
+ const WorktreeEndpoints = ClientApi.groups["server.worktree"].endpoints;
173
+ const context = yield* Effect.context();
174
+ const streams = yield* makeStreams();
175
+ // Run a hook registration on the plugin scope and resolve once it is registered.
176
+ const register = (effect) => Effect.runPromiseWith(context)(effect).then((registration) => ({
177
+ dispose: () => Effect.runPromiseWith(context)(registration.dispose),
178
+ }));
179
+ const run = (effect) => Effect.runPromiseWith(context)(effect);
180
+ const promiseExecutor = (execute) => (input, context) => run(execute(input, {
181
+ ...context,
182
+ progress: (update) => Effect.promise(() => context.progress(update)),
183
+ }));
184
+ const adaptApiMethod = (endpoint, method) => {
185
+ const compiled = compileEndpoint(endpoint);
186
+ return ((input) => Effect.gen(function* () {
187
+ // Match the generated Promise client, whose request body crosses JSON before endpoint decoding.
188
+ const normalized = yield* Schema.encodeUnknownEffect(JsonInput)(input ?? {}).pipe(Effect.flatMap(Schema.decodeUnknownEffect(JsonInput)));
189
+ const decoded = yield* Effect.forEach(compiled.decode, (decode) => decode(normalized));
190
+ const result = yield* method(Object.assign({}, ...decoded));
191
+ if (compiled.noContent)
192
+ return undefined;
193
+ return yield* compiled.encode(result);
194
+ }).pipe(Effect.runPromiseWith(context)));
195
+ };
196
+ const transform = (domain) => (callback) => register(domain.transform((editor) => {
197
+ callback(editor);
198
+ }));
199
+ const context2 = {
200
+ app: host.app,
201
+ location: host.location,
202
+ options: host.options,
203
+ agent: {
204
+ get: adaptApiMethod(AgentEndpoints["agent.get"], host.agent.get),
205
+ list: adaptApiMethod(AgentEndpoints["agent.list"], host.agent.list),
206
+ transform: transform(host.agent),
207
+ reload: () => run(host.agent.reload()),
208
+ },
209
+ aisdk: {
210
+ hook: (name, callback, options) => register(host.aisdk.hook(name, (event) => Effect.promise(() => Promise.resolve(callback(event))), options)),
211
+ },
212
+ catalog: {
213
+ provider: {
214
+ list: adaptApiMethod(ProviderEndpoints["provider.list"], host.catalog.provider.list),
215
+ get: adaptApiMethod(ProviderEndpoints["provider.get"], host.catalog.provider.get),
216
+ },
217
+ model: {
218
+ list: adaptApiMethod(ModelEndpoints["model.list"], host.catalog.model.list),
219
+ default: adaptApiMethod(ModelEndpoints["model.default"], host.catalog.model.default),
220
+ },
221
+ transform: transform(host.catalog),
222
+ reload: () => run(host.catalog.reload()),
223
+ },
224
+ command: {
225
+ list: adaptApiMethod(CommandEndpoints["command.list"], host.command.list),
226
+ transform: (callback) => register(host.command.transform((editor) => callback({
227
+ add: (definition) => editor.add({
228
+ ...definition,
229
+ execute: (input) => Effect.tryPromise({ try: () => definition.execute(input), catch: (cause) => cause }),
230
+ }),
231
+ }))),
232
+ reload: () => run(host.command.reload()),
233
+ },
234
+ event: {
235
+ subscribe: (options) => streams(host.event.subscribe().pipe(Stream.mapEffect((event) => Schema.encodeUnknownEffect(OpenCodeEvent)(event)), Stream.map((event) => event)), options),
236
+ },
237
+ experimental: {
238
+ terminal: {
239
+ read: adaptApiMethod(ExperimentalEndpoints["persistentPty.read"], host.experimental.terminal.read),
240
+ },
241
+ },
242
+ generate: {
243
+ text: adaptApiMethod(GenerateEndpoints["generate.text"], host.generate.text),
244
+ },
245
+ integration: {
246
+ list: adaptApiMethod(IntegrationEndpoints["integration.list"], host.integration.list),
247
+ get: adaptApiMethod(IntegrationEndpoints["integration.get"], host.integration.get),
248
+ connect: {
249
+ key: adaptApiMethod(IntegrationEndpoints["integration.connect.key"], host.integration.connect.key),
250
+ },
251
+ oauth: {
252
+ connect: adaptApiMethod(IntegrationEndpoints["integration.oauth.connect"], host.integration.oauth.connect),
253
+ status: adaptApiMethod(IntegrationEndpoints["integration.oauth.status"], host.integration.oauth.status),
254
+ complete: adaptApiMethod(IntegrationEndpoints["integration.oauth.complete"], host.integration.oauth.complete),
255
+ cancel: adaptApiMethod(IntegrationEndpoints["integration.oauth.cancel"], host.integration.oauth.cancel),
256
+ },
257
+ command: {
258
+ connect: adaptApiMethod(IntegrationEndpoints["integration.command.connect"], host.integration.command.connect),
259
+ status: adaptApiMethod(IntegrationEndpoints["integration.command.status"], host.integration.command.status),
260
+ cancel: adaptApiMethod(IntegrationEndpoints["integration.command.cancel"], host.integration.command.cancel),
261
+ },
262
+ transform: (callback) => register(host.integration.transform((editor) => callback({
263
+ list: editor.list,
264
+ get: editor.get,
265
+ update: editor.update,
266
+ remove: editor.remove,
267
+ method: {
268
+ list: editor.method.list,
269
+ update: (input) => {
270
+ if (!("authorize" in input))
271
+ return editor.method.update(input);
272
+ const refresh = input.refresh;
273
+ editor.method.update({
274
+ ...input,
275
+ authorize: (answer) => Effect.promise(() => input.authorize(answer)).pipe(Effect.map((authorization) => authorization.mode === "auto"
276
+ ? {
277
+ ...authorization,
278
+ callback: Effect.promise(() => authorization.callback),
279
+ }
280
+ : {
281
+ ...authorization,
282
+ callback: (code) => Effect.promise(() => authorization.callback(code)),
283
+ })),
284
+ refresh: refresh === undefined
285
+ ? undefined
286
+ : (credential) => Effect.promise(() => refresh(credential)),
287
+ });
288
+ },
289
+ remove: editor.method.remove,
290
+ },
291
+ }))),
292
+ reload: () => run(host.integration.reload()),
293
+ connection: {
294
+ active: (id) => Effect.runPromiseWith(context)(host.integration.connection.active(id)),
295
+ resolve: (connection) => Effect.runPromiseWith(context)(host.integration.connection.resolve(connection)),
296
+ },
297
+ },
298
+ mcp: {
299
+ list: adaptApiMethod(McpEndpoints["mcp.list"], host.mcp.list),
300
+ transform: transform(host.mcp),
301
+ reload: () => run(host.mcp.reload()),
302
+ },
303
+ permission: {
304
+ hook: (name, callback) => register(host.permission.hook(name, (event) => Effect.promise(() => Promise.resolve(callback(event))))),
305
+ list: adaptApiMethod(PermissionEndpoints["session.permission.list"], host.permission.list),
306
+ get: adaptApiMethod(PermissionEndpoints["session.permission.get"], host.permission.get),
307
+ reply: adaptApiMethod(PermissionEndpoints["session.permission.reply"], host.permission.reply),
308
+ rules: adaptApiMethod(PermissionEndpoints["session.permission.rules"], host.permission.rules),
309
+ },
310
+ plugin: {
311
+ list: adaptApiMethod(PluginEndpoints["plugin.list"], host.plugin.list),
312
+ },
313
+ reference: {
314
+ list: adaptApiMethod(ReferenceEndpoints["reference.list"], host.reference.list),
315
+ transform: transform(host.reference),
316
+ reload: () => run(host.reference.reload()),
317
+ },
318
+ rpc: yield* rpcFromEffect(host.rpc, streams),
319
+ skill: {
320
+ list: adaptApiMethod(SkillEndpoints["skill.list"], host.skill.list),
321
+ transform: transform(host.skill),
322
+ reload: () => run(host.skill.reload()),
323
+ },
324
+ storage: {
325
+ get: (key) => run(host.storage.get(key)),
326
+ set: (key, value) => run(host.storage.set(key, value)),
327
+ remove: (key) => run(host.storage.remove(key)),
328
+ scan: (options) => run(host.storage.scan(options)),
329
+ },
330
+ tool: {
331
+ reload: () => run(host.tool.reload()),
332
+ transform: (callback) => register(host.tool.transform((editor) => callback({
333
+ list: () => editor.list().map((tool) => ({ ...tool, execute: promiseExecutor(tool.execute) })),
334
+ get: (id) => {
335
+ const tool = editor.get(id);
336
+ return tool ? { ...tool, execute: promiseExecutor(tool.execute) } : undefined;
337
+ },
338
+ namespace: editor.namespace,
339
+ add: (tool) => editor.add({
340
+ ...tool,
341
+ execute: (input, context) => executePromiseTool(tool, input, context),
342
+ }),
343
+ update: (id, update) => editor.update(id, (tool) => {
344
+ const value = {
345
+ ...tool,
346
+ execute: promiseExecutor(tool.execute),
347
+ };
348
+ update(value);
349
+ Object.assign(tool, value, {
350
+ output: value.output,
351
+ options: value.options,
352
+ execute: (input, context) => executePromiseTool(value, input, context),
353
+ });
354
+ }),
355
+ remove: editor.remove,
356
+ }))),
357
+ hook: (name, callback) => register(host.tool.hook(name, (event) => Effect.promise(() => Promise.resolve(callback(event))))),
358
+ },
359
+ vcs: {
360
+ get: adaptApiMethod(VcsEndpoints["vcs.get"], host.vcs.get),
361
+ base: adaptApiMethod(VcsEndpoints["vcs.base"], host.vcs.base),
362
+ branches: adaptApiMethod(VcsEndpoints["vcs.branches"], host.vcs.branches),
363
+ status: adaptApiMethod(VcsEndpoints["vcs.status"], host.vcs.status),
364
+ diff: adaptApiMethod(VcsEndpoints["vcs.diff"], host.vcs.diff),
365
+ reload: () => run(host.vcs.reload()),
366
+ transform: (callback) => register(host.vcs.transform((editor) => {
367
+ callback({
368
+ add: (definition) => {
369
+ const base = definition.base?.bind(definition);
370
+ editor.add({
371
+ id: definition.id,
372
+ name: definition.name,
373
+ info: (input) => attempt((signal) => definition.info(input, { signal })),
374
+ base: base ? (input) => attempt((signal) => base(input, { signal })) : undefined,
375
+ branches: (input) => attempt((signal) => definition.branches(input, { signal })),
376
+ status: (input) => attempt((signal) => definition.status(input, { signal })),
377
+ diff: (input) => attempt((signal) => definition.diff(input, { signal })),
378
+ });
379
+ },
380
+ default: editor.default,
381
+ });
382
+ })),
383
+ },
384
+ websearch: {
385
+ providers: adaptApiMethod(WebSearchEndpoints["websearch.providers"], host.websearch.providers),
386
+ query: adaptApiMethod(WebSearchEndpoints["websearch.query"], host.websearch.query),
387
+ reload: () => run(host.websearch.reload()),
388
+ transform: (callback) => register(host.websearch.transform((editor) => {
389
+ callback({
390
+ add: (definition) => editor.add({
391
+ id: definition.id,
392
+ name: definition.name,
393
+ execute: (input) => attempt((signal) => definition.execute(input, { signal })),
394
+ }),
395
+ default: editor.default,
396
+ });
397
+ })),
398
+ },
399
+ worktree: {
400
+ list: adaptApiMethod(WorktreeEndpoints["worktree.list"], host.worktree.list),
401
+ create: adaptApiMethod(WorktreeEndpoints["worktree.create"], host.worktree.create),
402
+ remove: adaptApiMethod(WorktreeEndpoints["worktree.remove"], host.worktree.remove),
403
+ refresh: adaptApiMethod(WorktreeEndpoints["worktree.refresh"], host.worktree.refresh),
404
+ reload: () => run(host.worktree.reload()),
405
+ transform: (callback) => register(host.worktree.transform((editor) => callback({
406
+ add: (definition) => editor.add({
407
+ id: definition.id,
408
+ create: (input) => attempt((signal) => definition.create(input, { signal })),
409
+ remove: (input) => attempt((signal) => definition.remove(input, { signal })),
410
+ list: (directory) => attempt((signal) => definition.list(directory, { signal })),
411
+ }),
412
+ }))),
413
+ },
414
+ session: {
415
+ hook: (name, callback, options) => register(host.session.hook(name, (event) => Effect.promise(() => Promise.resolve(callback(event))), options)),
416
+ create: adaptApiMethod(SessionEndpoints["session.create"], host.session.create),
417
+ get: adaptApiMethod(SessionEndpoints["session.get"], host.session.get),
418
+ switchAgent: adaptApiMethod(SessionEndpoints["session.switchAgent"], host.session.switchAgent),
419
+ switchModel: adaptApiMethod(SessionEndpoints["session.switchModel"], host.session.switchModel),
420
+ prompt: adaptApiMethod(SessionEndpoints["session.prompt"], host.session.prompt),
421
+ generate: adaptApiMethod(SessionEndpoints["session.generate"], host.session.generate),
422
+ command: adaptApiMethod(SessionEndpoints["session.command"], host.session.command),
423
+ synthetic: adaptApiMethod(SessionEndpoints["session.synthetic"], host.session.synthetic),
424
+ interrupt: adaptApiMethod(SessionEndpoints["session.interrupt"], host.session.interrupt),
425
+ rename: adaptApiMethod(SessionEndpoints["session.rename"], host.session.rename),
426
+ move: adaptApiMethod(SessionEndpoints["session.move"], host.session.move),
427
+ wait: adaptApiMethod(SessionEndpoints["session.wait"], host.session.wait),
428
+ context: adaptApiMethod(SessionEndpoints["session.context"], host.session.context),
429
+ },
430
+ shell: {
431
+ hook: (name, callback) => register(host.shell.hook(name, (event) => Effect.promise(() => Promise.resolve(callback(event))))),
432
+ },
433
+ };
434
+ yield* Effect.acquireRelease(Effect.promise(() => Promise.resolve(plugin.setup(context2))), (cleanup) => (cleanup ? Effect.promise(() => Promise.resolve(cleanup())) : Effect.void));
435
+ }),
436
+ });
437
+ }
438
+ function attempt(evaluate) {
439
+ return Effect.tryPromise({ try: evaluate, catch: (cause) => cause });
440
+ }
441
+ const executePromiseTool = (tool, input, context) => Effect.promise(() => tool.execute(input, {
442
+ ...context,
443
+ progress: (update) => Effect.runPromise(context.progress(update)),
444
+ }));
@@ -0,0 +1,15 @@
1
+ import type { AgentApi } from "@opencode/client/promise/api";
2
+ import type { Agent } from "@opencode/schema/agent";
3
+ import type { Transform } from "./registration.js";
4
+ import type { DeepMutable } from "./types.js";
5
+ export interface AgentEditor {
6
+ list(): readonly DeepMutable<Agent.Info>[];
7
+ get(id: string): DeepMutable<Agent.Info> | undefined;
8
+ default(id: string | undefined): void;
9
+ update(id: string, update: (agent: DeepMutable<Agent.Info>) => void): void;
10
+ remove(id: string): void;
11
+ }
12
+ export interface AgentDomain extends AgentApi {
13
+ readonly transform: Transform<AgentEditor>;
14
+ readonly reload: () => Promise<void>;
15
+ }
File without changes
@@ -0,0 +1,20 @@
1
+ import type { LanguageModelV3 } from "@ai-sdk/provider";
2
+ import type { Model } from "@opencode/schema/model";
3
+ import type { ModelHooks } from "./registration.js";
4
+ export interface AISDKHooks {
5
+ sdk: {
6
+ readonly model: Model.Info;
7
+ readonly package: string;
8
+ readonly options: Record<string, any>;
9
+ sdk?: any;
10
+ };
11
+ language: {
12
+ readonly model: Model.Info;
13
+ readonly sdk: any;
14
+ readonly options: Record<string, any>;
15
+ language?: LanguageModelV3;
16
+ };
17
+ }
18
+ export interface AISDKDomain {
19
+ readonly hook: ModelHooks<AISDKHooks>;
20
+ }
File without changes