@dudousxd/nestjs-agent-opencode 0.2.1 → 0.4.0

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.
@@ -1,6 +1,6 @@
1
1
  import { AgentDepsFactory, ChatQueueService, AgentEngine } from '@dudousxd/nestjs-agent';
2
2
  import { ConflictException, Logger, OnApplicationShutdown, Type, InjectionToken, Provider } from '@nestjs/common';
3
- import { ElicitationRequest, CostSource, AgentRunInput, SinkWriter, AgentStore, ApprovalRequirement, MessageUsage, AgentUiComponent, HumanReply, Actor, StoredMessage, ToolRegistry, TokenStreamSink, SkillsConfig, MemoryConfig, AgentPricingStore, PriceCatalogOptions, RolesPolicy, RememberToolInput, AiToolCtx } from '@dudousxd/nestjs-agent-core';
3
+ import { ElicitationRequest, CostSource, AgentRunInput, SinkWriter, AgentStore, ApprovalRequirement, ToolPresentation, MessageUsage, AgentUiComponent, HumanReply, Actor, StoredMessage, ToolRegistry, TokenStreamSink, SkillsConfig, MemoryConfig, AgentPricingStore, PriceCatalogOptions, RolesPolicy, RememberToolInput, AiToolCtx } from '@dudousxd/nestjs-agent-core';
4
4
  import { IncomingMessage, ServerResponse } from 'node:http';
5
5
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
6
6
 
@@ -297,6 +297,15 @@ interface OpenCodeTurnArgs {
297
297
  store: AgentStore;
298
298
  /** What an action needs before it runs (the module's `ApprovalPolicy`). */
299
299
  approvalFor: (action: string) => Promise<ApprovalRequirement>;
300
+ /**
301
+ * The app tool a permission `action` names, with its presentation: its `confirm` templates (filled
302
+ * from the call's arguments) word the approval card. `undefined` (or omitted) → an OpenCode
303
+ * built-in, worded from its name.
304
+ */
305
+ approvalPresentation?: (action: string) => {
306
+ name: string;
307
+ presentation?: ToolPresentation;
308
+ } | undefined;
300
309
  /** The model label usage is recorded under until OpenCode names the step's model. */
301
310
  modelLabel: string;
302
311
  /**
@@ -676,6 +685,8 @@ declare class OpenCodeToolRefusedError extends Error {
676
685
  declare class OpenCodeMcpEndpoint {
677
686
  private readonly turns;
678
687
  private readonly tokens;
688
+ /** Genui telemetry of the UI calls OpenCode makes (its code mode calls tools here, not in the loop). */
689
+ private readonly genui;
679
690
  constructor(turns: OpenCodeTurns, tokens: OpenCodeToolsTokens);
680
691
  /** The claims of a request's bearer token, or `null`. */
681
692
  authenticate(authorization: string | string[] | undefined): OpenCodeToolsClaims | null;
@@ -787,7 +798,8 @@ declare class OpenCodeTurns implements OnApplicationShutdown {
787
798
  private readonly host;
788
799
  private readonly sessions;
789
800
  private readonly settings;
790
- private readonly store;
801
+ /** Read by the MCP endpoint too (genui telemetry of the calls OpenCode makes). */
802
+ readonly store: AgentStore;
791
803
  private readonly sink;
792
804
  private readonly deps;
793
805
  readonly registry: ToolRegistry;
@@ -803,7 +815,9 @@ declare class OpenCodeTurns implements OnApplicationShutdown {
803
815
  private readonly spent;
804
816
  /** When the tools endpoint was last registered for a location (`server|boot|directory|actor`). */
805
817
  private readonly toolsIssued;
806
- constructor(host: OpenCodeHost, sessions: OpenCodeSessionStore, settings: OpenCodeEngineSettings, store: AgentStore, sink: TokenStreamSink, deps: AgentDepsFactory, registry: ToolRegistry, skills?: SkillsConfig | undefined, memory?: MemoryConfig | undefined, queue?: ChatQueueService | undefined, toolsTokens?: (OpenCodeToolsTokens | null) | undefined, pricingStore?: AgentPricingStore, agentOptions?: {
818
+ constructor(host: OpenCodeHost, sessions: OpenCodeSessionStore, settings: OpenCodeEngineSettings,
819
+ /** Read by the MCP endpoint too (genui telemetry of the calls OpenCode makes). */
820
+ store: AgentStore, sink: TokenStreamSink, deps: AgentDepsFactory, registry: ToolRegistry, skills?: SkillsConfig | undefined, memory?: MemoryConfig | undefined, queue?: ChatQueueService | undefined, toolsTokens?: (OpenCodeToolsTokens | null) | undefined, pricingStore?: AgentPricingStore, agentOptions?: {
807
821
  priceCatalog?: PriceCatalogOptions | false;
808
822
  });
809
823
  private readonly prices;
@@ -819,6 +833,11 @@ declare class OpenCodeTurns implements OnApplicationShutdown {
819
833
  }): Promise<StepCost>;
820
834
  /** The tools server's name in OpenCode. */
821
835
  get toolsServer(): string;
836
+ /** The app tool an OpenCode permission `action` names, and its presentation — `undefined` for a built-in. */
837
+ presentationOf(action: string): {
838
+ name: string;
839
+ presentation?: ToolPresentation;
840
+ } | undefined;
822
841
  /** The roles policy a turn's tools are gated by (the agent's own). */
823
842
  rolesPolicyFor(input: Pick<AgentRunInput, 'agentName'>): RolesPolicy;
824
843
  /** The tools a turn may reach: the agent's allow-list, narrowed by its persona's. */
@@ -1,6 +1,6 @@
1
1
  import { AgentDepsFactory, ChatQueueService, AgentEngine } from '@dudousxd/nestjs-agent';
2
2
  import { ConflictException, Logger, OnApplicationShutdown, Type, InjectionToken, Provider } from '@nestjs/common';
3
- import { ElicitationRequest, CostSource, AgentRunInput, SinkWriter, AgentStore, ApprovalRequirement, MessageUsage, AgentUiComponent, HumanReply, Actor, StoredMessage, ToolRegistry, TokenStreamSink, SkillsConfig, MemoryConfig, AgentPricingStore, PriceCatalogOptions, RolesPolicy, RememberToolInput, AiToolCtx } from '@dudousxd/nestjs-agent-core';
3
+ import { ElicitationRequest, CostSource, AgentRunInput, SinkWriter, AgentStore, ApprovalRequirement, ToolPresentation, MessageUsage, AgentUiComponent, HumanReply, Actor, StoredMessage, ToolRegistry, TokenStreamSink, SkillsConfig, MemoryConfig, AgentPricingStore, PriceCatalogOptions, RolesPolicy, RememberToolInput, AiToolCtx } from '@dudousxd/nestjs-agent-core';
4
4
  import { IncomingMessage, ServerResponse } from 'node:http';
5
5
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
6
6
 
@@ -297,6 +297,15 @@ interface OpenCodeTurnArgs {
297
297
  store: AgentStore;
298
298
  /** What an action needs before it runs (the module's `ApprovalPolicy`). */
299
299
  approvalFor: (action: string) => Promise<ApprovalRequirement>;
300
+ /**
301
+ * The app tool a permission `action` names, with its presentation: its `confirm` templates (filled
302
+ * from the call's arguments) word the approval card. `undefined` (or omitted) → an OpenCode
303
+ * built-in, worded from its name.
304
+ */
305
+ approvalPresentation?: (action: string) => {
306
+ name: string;
307
+ presentation?: ToolPresentation;
308
+ } | undefined;
300
309
  /** The model label usage is recorded under until OpenCode names the step's model. */
301
310
  modelLabel: string;
302
311
  /**
@@ -676,6 +685,8 @@ declare class OpenCodeToolRefusedError extends Error {
676
685
  declare class OpenCodeMcpEndpoint {
677
686
  private readonly turns;
678
687
  private readonly tokens;
688
+ /** Genui telemetry of the UI calls OpenCode makes (its code mode calls tools here, not in the loop). */
689
+ private readonly genui;
679
690
  constructor(turns: OpenCodeTurns, tokens: OpenCodeToolsTokens);
680
691
  /** The claims of a request's bearer token, or `null`. */
681
692
  authenticate(authorization: string | string[] | undefined): OpenCodeToolsClaims | null;
@@ -787,7 +798,8 @@ declare class OpenCodeTurns implements OnApplicationShutdown {
787
798
  private readonly host;
788
799
  private readonly sessions;
789
800
  private readonly settings;
790
- private readonly store;
801
+ /** Read by the MCP endpoint too (genui telemetry of the calls OpenCode makes). */
802
+ readonly store: AgentStore;
791
803
  private readonly sink;
792
804
  private readonly deps;
793
805
  readonly registry: ToolRegistry;
@@ -803,7 +815,9 @@ declare class OpenCodeTurns implements OnApplicationShutdown {
803
815
  private readonly spent;
804
816
  /** When the tools endpoint was last registered for a location (`server|boot|directory|actor`). */
805
817
  private readonly toolsIssued;
806
- constructor(host: OpenCodeHost, sessions: OpenCodeSessionStore, settings: OpenCodeEngineSettings, store: AgentStore, sink: TokenStreamSink, deps: AgentDepsFactory, registry: ToolRegistry, skills?: SkillsConfig | undefined, memory?: MemoryConfig | undefined, queue?: ChatQueueService | undefined, toolsTokens?: (OpenCodeToolsTokens | null) | undefined, pricingStore?: AgentPricingStore, agentOptions?: {
818
+ constructor(host: OpenCodeHost, sessions: OpenCodeSessionStore, settings: OpenCodeEngineSettings,
819
+ /** Read by the MCP endpoint too (genui telemetry of the calls OpenCode makes). */
820
+ store: AgentStore, sink: TokenStreamSink, deps: AgentDepsFactory, registry: ToolRegistry, skills?: SkillsConfig | undefined, memory?: MemoryConfig | undefined, queue?: ChatQueueService | undefined, toolsTokens?: (OpenCodeToolsTokens | null) | undefined, pricingStore?: AgentPricingStore, agentOptions?: {
807
821
  priceCatalog?: PriceCatalogOptions | false;
808
822
  });
809
823
  private readonly prices;
@@ -819,6 +833,11 @@ declare class OpenCodeTurns implements OnApplicationShutdown {
819
833
  }): Promise<StepCost>;
820
834
  /** The tools server's name in OpenCode. */
821
835
  get toolsServer(): string;
836
+ /** The app tool an OpenCode permission `action` names, and its presentation — `undefined` for a built-in. */
837
+ presentationOf(action: string): {
838
+ name: string;
839
+ presentation?: ToolPresentation;
840
+ } | undefined;
822
841
  /** The roles policy a turn's tools are gated by (the agent's own). */
823
842
  rolesPolicyFor(input: Pick<AgentRunInput, 'agentName'>): RolesPolicy;
824
843
  /** The tools a turn may reach: the agent's allow-list, narrowed by its persona's. */
package/dist/index.cjs CHANGED
@@ -195,6 +195,7 @@ OpenCodeMcpController = _ts_decorate([
195
195
  // src/mcp.ts
196
196
  var import_node_crypto = require("crypto");
197
197
  var import_nestjs_agent_core3 = require("@dudousxd/nestjs-agent-core");
198
+ var import_genui = require("@dudousxd/nestjs-agent-core/genui");
198
199
  var import_server = require("@modelcontextprotocol/sdk/server/index.js");
199
200
  var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
200
201
  var import_zod_json_schema_compat = require("@modelcontextprotocol/sdk/server/zod-json-schema-compat.js");
@@ -1071,6 +1072,8 @@ ${delta}` : delta;
1071
1072
  }
1072
1073
  const ttlMs = requirement.ttlMs !== void 0 && Number.isFinite(requirement.ttlMs) && requirement.ttlMs > 0 ? Math.floor(requirement.ttlMs) : void 0;
1073
1074
  const expiresAt = ttlMs !== void 0 ? new Date(Date.now() + ttlMs).toISOString() : void 0;
1075
+ const tool = this.a.approvalPresentation?.(action);
1076
+ const confirmation = tool !== void 0 ? (0, import_nestjs_agent_core2.resolveToolConfirmation)(tool.name, args, tool.presentation) : void 0;
1074
1077
  this.segment.calls.push({
1075
1078
  id: req.id,
1076
1079
  name: action,
@@ -1085,6 +1088,9 @@ ${delta}` : delta;
1085
1088
  toolType: "action",
1086
1089
  input: args,
1087
1090
  status: "pending_approval",
1091
+ ...confirmation !== void 0 ? {
1092
+ confirmation
1093
+ } : {},
1088
1094
  runId: this.a.runId,
1089
1095
  approver: requirement.approver,
1090
1096
  ...expiresAt !== void 0 ? {
@@ -1102,6 +1108,9 @@ ${delta}` : delta;
1102
1108
  kind: "approval-requested",
1103
1109
  id: req.id,
1104
1110
  approver: requirement.approver,
1111
+ ...confirmation !== void 0 ? {
1112
+ confirmation
1113
+ } : {},
1105
1114
  ...expiresAt !== void 0 ? {
1106
1115
  expiresAt
1107
1116
  } : {}
@@ -1507,6 +1516,8 @@ var OpenCodeMcpEndpoint2 = class {
1507
1516
  }
1508
1517
  turns;
1509
1518
  tokens;
1519
+ /** Genui telemetry of the UI calls OpenCode makes (its code mode calls tools here, not in the loop). */
1520
+ genui = new import_nestjs_agent_core3.GenuiRunRecorder();
1510
1521
  constructor(turns, tokens) {
1511
1522
  this.turns = turns;
1512
1523
  this.tokens = tokens;
@@ -1638,7 +1649,7 @@ var OpenCodeMcpEndpoint2 = class {
1638
1649
  if (spec.kind === "action" && !await this.turns.spendApproval(turn.runId, name, turn.input)) {
1639
1650
  throw new OpenCodeToolRefusedError(`"${name}" is an action and nobody approved this call; ask for the permission first.`);
1640
1651
  }
1641
- return registry.invoke(name, args ?? {}, {
1652
+ const invoke = /* @__PURE__ */ __name(() => registry.invoke(name, args ?? {}, {
1642
1653
  actor,
1643
1654
  threadId: turn.ctx.threadId,
1644
1655
  runId: turn.runId,
@@ -1655,7 +1666,24 @@ var OpenCodeMcpEndpoint2 = class {
1655
1666
  } : {}
1656
1667
  }, this.turns.rolesPolicyFor(turn.input), allowed !== void 0 ? {
1657
1668
  allowedTools: allowed
1658
- } : {});
1669
+ } : {}), "invoke");
1670
+ return this.genui.track({
1671
+ store: this.turns.store,
1672
+ marker: spec.genui,
1673
+ runId: turn.runId,
1674
+ threadId: turn.ctx.threadId,
1675
+ actorRef: actor.id,
1676
+ ...turn.input.agentName !== void 0 ? {
1677
+ agentName: turn.input.agentName
1678
+ } : {},
1679
+ channel: (0, import_genui.turnChannel)(turn.input.pageContext),
1680
+ modelId: turn.input.model ?? "opencode",
1681
+ call: {
1682
+ id: `mcp:${turn.runId}:${requestId}`,
1683
+ name,
1684
+ input: args ?? {}
1685
+ }
1686
+ }, invoke);
1659
1687
  }
1660
1688
  /** `remember`: one fact, at the actor's own scope only — as the loop serves it. */
1661
1689
  async remember(turn, args) {
@@ -1882,6 +1910,25 @@ var OpenCodeTurns = class {
1882
1910
  get toolsServer() {
1883
1911
  return this.settings.tools?.server ?? "aviary";
1884
1912
  }
1913
+ /** The app tool an OpenCode permission `action` names, and its presentation — `undefined` for a built-in. */
1914
+ presentationOf(action) {
1915
+ if (this.settings.tools === void 0) return void 0;
1916
+ const server = this.toolsServer;
1917
+ for (const prefix of [
1918
+ `${server}.`,
1919
+ `${server}_`
1920
+ ]) {
1921
+ if (!action.startsWith(prefix)) continue;
1922
+ const spec = this.registry.spec(action.slice(prefix.length));
1923
+ if (spec !== void 0) return {
1924
+ name: spec.name,
1925
+ ...spec.presentation !== void 0 ? {
1926
+ presentation: spec.presentation
1927
+ } : {}
1928
+ };
1929
+ }
1930
+ return void 0;
1931
+ }
1885
1932
  /** The roles policy a turn's tools are gated by (the agent's own). */
1886
1933
  rolesPolicyFor(input) {
1887
1934
  return this.deps.forAgent(input.agentName).rolesPolicy;
@@ -2614,6 +2661,9 @@ ${body}
2614
2661
  agentName: input.agentName
2615
2662
  } : {}
2616
2663
  }), "approvalFor"),
2664
+ // An app tool reaches OpenCode as `<server>.<name>` / `<server>_<name>`: its own presentation
2665
+ // words the approval card. OpenCode's built-ins (`bash`, `edit`) have none.
2666
+ approvalPresentation: /* @__PURE__ */ __name((action) => this.presentationOf(action), "approvalPresentation"),
2617
2667
  modelLabel: input.model ?? "opencode",
2618
2668
  priceStep: /* @__PURE__ */ __name((step) => this.priceStep(step), "priceStep"),
2619
2669
  ...spent !== void 0 ? {