@opencode/plugin 2.0.27 → 2.0.29

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.
@@ -2,6 +2,9 @@ import type { PermissionApi } from "@opencode/client/effect/api";
2
2
  import type { Agent } from "@opencode/schema/agent";
3
3
  import type { Permission } from "@opencode/schema/permission";
4
4
  import type { Session } from "@opencode/schema/session";
5
+ import type { Tool } from "@opencode/schema/tool";
6
+ import type { Effect } from "effect";
7
+ import type { AssertInput } from "../permission.js";
5
8
  import type { Hooks } from "./registration.js";
6
9
  export interface PermissionEvaluation {
7
10
  readonly sessionID: Session.ID;
@@ -18,4 +21,10 @@ export interface PermissionHooks {
18
21
  }
19
22
  export type PermissionDomain = Pick<PermissionApi<unknown>, "list" | "get" | "reply"> & {
20
23
  readonly hook: Hooks<PermissionHooks>;
24
+ /**
25
+ * Evaluates the session's permission rules and, when approval is required, prompts the user and
26
+ * waits for the reply. Succeeds when allowed. A deny rule or a rejection with feedback fails with a
27
+ * `Tool.Error` carrying the reason; a plain rejection ends the current step.
28
+ */
29
+ readonly assert: (input: AssertInput) => Effect.Effect<void, Tool.Error>;
21
30
  };
@@ -1,4 +1,4 @@
1
- import type { ExperimentalApi, GenerateApi, PluginApi } from "@opencode/client/effect/api";
1
+ import type { CredentialApi, ExperimentalApi, GenerateApi, PluginApi } from "@opencode/client/effect/api";
2
2
  import type { Location } from "@opencode/schema/location";
3
3
  import type { Effect, Scope } from "effect";
4
4
  import type { PluginOptions } from "../options.js";
@@ -29,6 +29,7 @@ export interface Context {
29
29
  readonly agent: AgentDomain;
30
30
  readonly aisdk: AISDKDomain;
31
31
  readonly command: CommandDomain;
32
+ readonly credential: Pick<CredentialApi<unknown>, "activate">;
32
33
  readonly event: EventDomain;
33
34
  readonly experimental: {
34
35
  readonly terminal: Pick<ExperimentalApi<unknown>["persistentPty"], "read">;
@@ -0,0 +1,23 @@
1
+ import { Schema } from "effect";
2
+ export declare const AssertInput: Schema.Struct<{
3
+ readonly sessionID: Schema.brand<Schema.String, "SessionID"> & {
4
+ create: () => string & import("effect/Brand").Brand<"SessionID">;
5
+ descending: (id?: string) => string & import("effect/Brand").Brand<"SessionID">;
6
+ };
7
+ readonly agent: Schema.optional<Schema.brand<Schema.String, "Agent.ID">>;
8
+ readonly action: Schema.String;
9
+ readonly resources: Schema.$Array<Schema.String>;
10
+ readonly save: Schema.decodeTo<Schema.optional<Schema.toType<Schema.$Array<Schema.String>>>, Schema.optionalKey<Schema.$Array<Schema.String>>, never, never>;
11
+ readonly metadata: Schema.decodeTo<Schema.optional<Schema.toType<Schema.$Record<Schema.String, Schema.Unknown>>>, Schema.optionalKey<Schema.$Record<Schema.String, Schema.Unknown>>, never, never>;
12
+ readonly source: Schema.decodeTo<Schema.optional<Schema.toType<Schema.Union<readonly [Schema.Struct<{
13
+ readonly type: Schema.Literal<"tool">;
14
+ readonly messageID: Schema.String;
15
+ readonly id: Schema.String;
16
+ }>]>>>, Schema.optionalKey<Schema.Union<readonly [Schema.Struct<{
17
+ readonly type: Schema.Literal<"tool">;
18
+ readonly messageID: Schema.String;
19
+ readonly id: Schema.String;
20
+ }>]>>, never, never>;
21
+ }>;
22
+ export interface AssertInput extends Schema.Schema.Type<typeof AssertInput> {
23
+ }
@@ -0,0 +1,12 @@
1
+ import { Agent } from "@opencode/schema/agent";
2
+ import { Permission } from "@opencode/schema/permission";
3
+ import { Schema } from "effect";
4
+ export const AssertInput = Schema.Struct({
5
+ sessionID: Permission.Request.fields.sessionID,
6
+ agent: Agent.ID.pipe(Schema.optional),
7
+ action: Permission.Request.fields.action,
8
+ resources: Permission.Request.fields.resources,
9
+ save: Permission.Request.fields.save,
10
+ metadata: Permission.Request.fields.metadata,
11
+ source: Permission.Request.fields.source,
12
+ });
@@ -2,6 +2,7 @@ import { Tool } from "@opencode/schema/tool";
2
2
  import { Effect, Schema, SchemaAST, Stream } from "effect";
3
3
  import { HttpApiEndpoint, HttpApiSchema } from "effect/http-api";
4
4
  import { define } from "../effect/plugin.js";
5
+ import { AssertInput } from "../permission.js";
5
6
  const compiledEndpoints = new WeakMap();
6
7
  const JsonInput = Schema.fromJsonString(Schema.Unknown);
7
8
  class ReturnedRpcError extends Error {
@@ -157,6 +158,7 @@ export function fromPromise(plugin) {
157
158
  const [{ ClientApi }, { OpenCodeEvent }] = yield* Effect.promise(() => Promise.all([import("@opencode/protocol/client"), import("@opencode/protocol/groups/event")]));
158
159
  const AgentEndpoints = ClientApi.groups["server.agent"].endpoints;
159
160
  const CommandEndpoints = ClientApi.groups["server.command"].endpoints;
161
+ const CredentialEndpoints = ClientApi.groups["server.credential"].endpoints;
160
162
  const ExperimentalEndpoints = ClientApi.groups["server.experimental"].endpoints;
161
163
  const GenerateEndpoints = ClientApi.groups["server.generate"].endpoints;
162
164
  const IntegrationEndpoints = ClientApi.groups["server.integration"].endpoints;
@@ -220,6 +222,9 @@ export function fromPromise(plugin) {
220
222
  }))),
221
223
  reload: () => run(host.command.reload()),
222
224
  },
225
+ credential: {
226
+ activate: adaptApiMethod(CredentialEndpoints["credential.activate"], host.credential.activate),
227
+ },
223
228
  event: {
224
229
  subscribe: (options) => streams(host.event.subscribe().pipe(Stream.mapEffect((event) => Schema.encodeUnknownEffect(OpenCodeEvent)(event)), Stream.map((event) => event)), options),
225
230
  },
@@ -308,6 +313,7 @@ export function fromPromise(plugin) {
308
313
  list: adaptApiMethod(PermissionEndpoints["session.permission.list"], host.permission.list),
309
314
  get: adaptApiMethod(PermissionEndpoints["session.permission.get"], host.permission.get),
310
315
  reply: adaptApiMethod(PermissionEndpoints["session.permission.reply"], host.permission.reply),
316
+ assert: (input) => run(Schema.decodeUnknownEffect(AssertInput)(input).pipe(Effect.flatMap(host.permission.assert))),
311
317
  },
312
318
  plugin: {
313
319
  list: adaptApiMethod(PluginEndpoints["plugin.list"], host.plugin.list),
@@ -357,7 +363,7 @@ export function fromPromise(plugin) {
357
363
  }),
358
364
  remove: editor.remove,
359
365
  }))),
360
- hook: (name, callback) => register(host.tool.hook(name, (event) => Effect.promise(() => Promise.resolve(callback(event))))),
366
+ hook: (name, callback) => register(host.tool.hook(name, (event) => toolHook[name](() => Promise.resolve(callback(event))))),
361
367
  },
362
368
  vcs: {
363
369
  get: adaptApiMethod(VcsEndpoints["vcs.get"], host.vcs.get),
@@ -447,8 +453,19 @@ export function fromPromise(plugin) {
447
453
  function attempt(evaluate) {
448
454
  return Effect.tryPromise({ try: evaluate, catch: (cause) => cause });
449
455
  }
450
- const executePromiseTool = (tool, input, context) => Effect.promise((signal) => tool.execute(input, {
451
- ...context,
452
- signal,
453
- progress: (update) => Effect.runPromise(context.progress(update), { signal }),
454
- }));
456
+ const toolError = (error) => error instanceof Tool.Error
457
+ ? error
458
+ : new Tool.Error({ message: error instanceof Error ? error.message : String(error), error });
459
+ // Only execute.before may reject a call; a rejection fails that call alone, like an Effect hook.
460
+ const toolHook = {
461
+ "execute.before": (run) => Effect.tryPromise({ try: run, catch: toolError }),
462
+ "execute.after": (run) => Effect.promise(run),
463
+ };
464
+ const executePromiseTool = (tool, input, context) => Effect.tryPromise({
465
+ try: (signal) => tool.execute(input, {
466
+ ...context,
467
+ signal,
468
+ progress: (update) => Effect.runPromise(context.progress(update), { signal }),
469
+ }),
470
+ catch: toolError,
471
+ });
@@ -2,6 +2,7 @@ import type { PermissionApi } from "@opencode/client/promise/api";
2
2
  import type { Agent } from "@opencode/schema/agent";
3
3
  import type { Permission } from "@opencode/schema/permission";
4
4
  import type { Session } from "@opencode/schema/session";
5
+ import type { AssertInput } from "../permission.js";
5
6
  import type { Hooks } from "./registration.js";
6
7
  export interface PermissionEvaluation {
7
8
  readonly sessionID: Session.ID;
@@ -18,4 +19,11 @@ export interface PermissionHooks {
18
19
  }
19
20
  export type PermissionDomain = Pick<PermissionApi, "list" | "get" | "reply"> & {
20
21
  readonly hook: Hooks<PermissionHooks>;
22
+ /**
23
+ * Evaluates the session's permission rules and, when approval is required, prompts the user and
24
+ * waits for the reply. Resolves when allowed. A deny rule or a rejection with feedback rejects with
25
+ * a `Tool.Error` carrying the reason. A plain rejection rejects with an error that ends the current
26
+ * step; rethrow it rather than returning it as tool output.
27
+ */
28
+ readonly assert: (input: typeof AssertInput.Encoded) => Promise<void>;
21
29
  };
@@ -1,5 +1,5 @@
1
1
  import type { OpenCodeClient } from "@opencode/client";
2
- import type { GenerateApi, PluginApi } from "@opencode/client/promise/api";
2
+ import type { CredentialApi, GenerateApi, PluginApi } from "@opencode/client/promise/api";
3
3
  import type { Location } from "@opencode/schema/location";
4
4
  import type { PluginOptions } from "../options.js";
5
5
  import type { App } from "../app.js";
@@ -29,6 +29,7 @@ export interface Context {
29
29
  readonly agent: AgentDomain;
30
30
  readonly aisdk: AISDKDomain;
31
31
  readonly command: CommandDomain;
32
+ readonly credential: Pick<CredentialApi, "activate">;
32
33
  readonly event: EventDomain;
33
34
  readonly experimental: {
34
35
  readonly terminal: Pick<OpenCodeClient["experimental"]["persistentPty"], "read">;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@opencode/plugin",
4
- "version": "2.0.27",
4
+ "version": "2.0.29",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "scripts": {
@@ -47,15 +47,15 @@
47
47
  ],
48
48
  "dependencies": {
49
49
  "@ai-sdk/provider": "3.0.8",
50
- "@opencode/ai": "2.0.27",
51
- "@opencode/client": "2.0.27",
52
- "@opencode/protocol": "2.0.27",
53
- "@opencode/schema": "2.0.27",
54
- "@opencode/util": "2.0.27",
50
+ "@opencode/ai": "2.0.29",
51
+ "@opencode/client": "2.0.29",
52
+ "@opencode/protocol": "2.0.29",
53
+ "@opencode/schema": "2.0.29",
54
+ "@opencode/util": "2.0.29",
55
55
  "effect": "4.0.1"
56
56
  },
57
57
  "peerDependencies": {
58
- "@opencode/theme": "2.0.27",
58
+ "@opencode/theme": "2.0.29",
59
59
  "@opentui/core": ">=0.5.17",
60
60
  "@opentui/solid": ">=0.5.17",
61
61
  "solid-js": ">=1.9.0"
@@ -75,7 +75,7 @@
75
75
  }
76
76
  },
77
77
  "devDependencies": {
78
- "@opencode/theme": "2.0.27",
78
+ "@opencode/theme": "2.0.29",
79
79
  "@opentui/core": "0.5.17",
80
80
  "@opentui/solid": "0.5.17",
81
81
  "@tsconfig/bun": "1.0.9",