mcp-server-opencode 1.1.4 → 1.2.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.
Files changed (36) hide show
  1. package/CHANGELOG.md +100 -0
  2. package/build/src/index.js +14 -10
  3. package/build/src/index.js.map +1 -1
  4. package/build/src/modules/shared/instructions.d.ts +8 -1
  5. package/build/src/modules/shared/instructions.d.ts.map +1 -1
  6. package/build/src/modules/shared/instructions.js +26 -27
  7. package/build/src/modules/shared/instructions.js.map +1 -1
  8. package/build/src/modules/shared/opencode-client.d.ts +31 -3
  9. package/build/src/modules/shared/opencode-client.d.ts.map +1 -1
  10. package/build/src/modules/shared/opencode-client.js +118 -25
  11. package/build/src/modules/shared/opencode-client.js.map +1 -1
  12. package/build/src/modules/shared/permissions.d.ts +108 -0
  13. package/build/src/modules/shared/permissions.d.ts.map +1 -0
  14. package/build/src/modules/shared/permissions.js +193 -0
  15. package/build/src/modules/shared/permissions.js.map +1 -0
  16. package/build/src/modules/shared/task-registry.d.ts +11 -0
  17. package/build/src/modules/shared/task-registry.d.ts.map +1 -1
  18. package/build/src/modules/shared/task-registry.js +10 -0
  19. package/build/src/modules/shared/task-registry.js.map +1 -1
  20. package/build/src/modules/shared/usage-limits.d.ts +70 -0
  21. package/build/src/modules/shared/usage-limits.d.ts.map +1 -0
  22. package/build/src/modules/shared/usage-limits.js +296 -0
  23. package/build/src/modules/shared/usage-limits.js.map +1 -0
  24. package/build/src/modules/tools/cancel_task.d.ts.map +1 -1
  25. package/build/src/modules/tools/cancel_task.js +5 -0
  26. package/build/src/modules/tools/cancel_task.js.map +1 -1
  27. package/build/src/modules/tools/get_task_result.d.ts.map +1 -1
  28. package/build/src/modules/tools/get_task_result.js +42 -7
  29. package/build/src/modules/tools/get_task_result.js.map +1 -1
  30. package/build/src/modules/tools/list_agents.d.ts.map +1 -1
  31. package/build/src/modules/tools/list_agents.js +22 -4
  32. package/build/src/modules/tools/list_agents.js.map +1 -1
  33. package/build/src/modules/tools/start_server.d.ts.map +1 -1
  34. package/build/src/modules/tools/start_server.js +22 -2
  35. package/build/src/modules/tools/start_server.js.map +1 -1
  36. package/package.json +3 -2
@@ -0,0 +1,108 @@
1
+ import type { Config, OpencodeClient } from "@opencode-ai/sdk";
2
+ /**
3
+ * A pending permission request, as the OpenCode server actually emits it.
4
+ *
5
+ * The SDK's generated `Permission` type is stale relative to the running
6
+ * binary (verified against opencode 1.18.18 / `@opencode-ai/sdk` 1.17.20):
7
+ * the live event is `permission.asked` rather than `permission.updated`, the
8
+ * permission kind arrives as `permission` rather than `type`, and the call id
9
+ * is nested under `tool`. Both spellings are accepted so this keeps working
10
+ * whichever side moves first.
11
+ */
12
+ export interface PermissionAsk {
13
+ id: string;
14
+ sessionID: string;
15
+ /** Live server field. */
16
+ permission?: string;
17
+ /** Generated-SDK field. */
18
+ type?: string;
19
+ patterns?: string[];
20
+ always?: string[];
21
+ metadata?: Record<string, unknown>;
22
+ /** Live server nests the originating call here. */
23
+ tool?: {
24
+ messageID?: string;
25
+ callID?: string;
26
+ };
27
+ /** Generated-SDK field. */
28
+ callID?: string;
29
+ }
30
+ /** The permission kind, reading whichever field this server version emits. */
31
+ export declare function permissionKind(ask: PermissionAsk): string | undefined;
32
+ /** The originating tool call id, reading whichever field this server emits. */
33
+ export declare function permissionCallID(ask: PermissionAsk): string | undefined;
34
+ /**
35
+ * How the delegated agent may touch paths outside the directory the server was
36
+ * launched in.
37
+ *
38
+ * - `read-only` (default): reads outside the cwd are approved, writes are rejected.
39
+ * - `allow`: no boundary at all — anything outside the cwd is approved.
40
+ * - `deny`: nothing outside the cwd is reachable.
41
+ */
42
+ export type ExternalDirectoryPolicy = "read-only" | "allow" | "deny";
43
+ export declare const DEFAULT_EXTERNAL_DIRECTORY_POLICY: ExternalDirectoryPolicy;
44
+ /** The permission type OpenCode raises for any path outside the server cwd. */
45
+ export declare const EXTERNAL_DIRECTORY = "external_directory";
46
+ /**
47
+ * Resolve the external-directory policy.
48
+ *
49
+ * Precedence: the `OPENCODE_MCP_EXTERNAL_DIR` environment variable wins over an
50
+ * `OPENCODE_MCP_EXTERNAL_DIR=<policy>` CLI arg, which wins over `read-only`.
51
+ * Unrecognised values fall back to the default.
52
+ *
53
+ * Reads `process.env` / `process.argv` lazily on each call so tests can vary
54
+ * them between invocations without module re-import tricks.
55
+ */
56
+ export declare function getExternalDirectoryPolicy(): ExternalDirectoryPolicy;
57
+ /**
58
+ * Build the config handed to `createOpencodeServer`.
59
+ *
60
+ * OpenCode deep-merges this over the user's global config per permission key,
61
+ * so only `external_directory` is touched — existing `read` / `edit` / `bash`
62
+ * rules (including deny globs for secrets) survive untouched.
63
+ *
64
+ * `read-only` keeps the key on `ask` on purpose: the permission responder is
65
+ * what applies the read/write split, and it can only do that if OpenCode still
66
+ * raises the request.
67
+ */
68
+ export declare function buildServerConfig(policy: ExternalDirectoryPolicy): Config;
69
+ export type PermissionResponse = "once" | "always" | "reject";
70
+ export interface PermissionDecision {
71
+ response: PermissionResponse;
72
+ reason: string;
73
+ }
74
+ /**
75
+ * Decide how to answer a pending permission request.
76
+ *
77
+ * Anything that is not `external_directory` targets the directory the server
78
+ * was launched in, which the caller already owns — approved with `always` so
79
+ * repeat calls stop round-tripping.
80
+ *
81
+ * External reads are approved with `once` rather than `always` on purpose: an
82
+ * `always` reply persists the approval keyed by directory pattern, not by
83
+ * operation, which would silently let a later write into that same directory
84
+ * through and defeat the read-only boundary.
85
+ */
86
+ export declare function decidePermission(client: OpencodeClient, ask: PermissionAsk, policy: ExternalDirectoryPolicy, sleep?: (ms: number) => Promise<void>): Promise<PermissionDecision>;
87
+ export interface PermissionResponderOptions {
88
+ policy?: ExternalDirectoryPolicy;
89
+ /** Called on stream/reply failures so they never bubble into the MCP server. */
90
+ onError?: (error: unknown) => void;
91
+ /** Called after each answered request; useful for diagnostics and tests. */
92
+ onDecision?: (ask: PermissionAsk, decision: PermissionDecision) => void;
93
+ sleep?: (ms: number) => Promise<void>;
94
+ }
95
+ export interface PermissionResponder {
96
+ stop: () => void;
97
+ /** Resolves once the responder loop has exited. */
98
+ done: Promise<void>;
99
+ }
100
+ /**
101
+ * Answer permission requests for a headless server.
102
+ *
103
+ * Without this, any request configured as `ask` blocks the agent forever: there
104
+ * is no interactive approver behind an MCP-spawned server, so the session stays
105
+ * genuinely busy and the task never completes or fails.
106
+ */
107
+ export declare function startPermissionResponder(client: OpencodeClient, options?: PermissionResponderOptions): PermissionResponder;
108
+ //# sourceMappingURL=permissions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../../../src/modules/shared/permissions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAE/D;;;;;;;;;GASG;AACH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,yBAAyB;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2BAA2B;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,mDAAmD;IACnD,IAAI,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/C,2BAA2B;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAKD,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,GAAG,SAAS,CAErE;AAED,+EAA+E;AAC/E,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,GAAG,SAAS,CAEvE;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,uBAAuB,GAAG,WAAW,GAAG,OAAO,GAAG,MAAM,CAAC;AAErE,eAAO,MAAM,iCAAiC,EAAE,uBAAqC,CAAC;AAEtF,+EAA+E;AAC/E,eAAO,MAAM,kBAAkB,uBAAuB,CAAC;AAyBvD;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,IAAI,uBAAuB,CAYpE;AASD;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,uBAAuB,GAAG,MAAM,CAGzE;AAED,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE9D,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC;CAChB;AAsCD;;;;;;;;;;;GAWG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,cAAc,EACtB,GAAG,EAAE,aAAa,EAClB,MAAM,EAAE,uBAAuB,EAC/B,KAAK,GAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAgB,GAClD,OAAO,CAAC,kBAAkB,CAAC,CAyB7B;AAED,MAAM,WAAW,0BAA0B;IACzC,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC,gFAAgF;IAChF,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,4EAA4E;IAC5E,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,aAAa,EAAE,QAAQ,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACxE,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,mDAAmD;IACnD,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;CACrB;AAMD;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,cAAc,EACtB,OAAO,GAAE,0BAA+B,GACvC,mBAAmB,CAmCrB"}
@@ -0,0 +1,193 @@
1
+ /** Event types that carry a pending permission request. */
2
+ const PERMISSION_EVENTS = new Set(["permission.asked", "permission.updated"]);
3
+ /** The permission kind, reading whichever field this server version emits. */
4
+ export function permissionKind(ask) {
5
+ return ask.permission ?? ask.type;
6
+ }
7
+ /** The originating tool call id, reading whichever field this server emits. */
8
+ export function permissionCallID(ask) {
9
+ return ask.tool?.callID ?? ask.callID;
10
+ }
11
+ export const DEFAULT_EXTERNAL_DIRECTORY_POLICY = "read-only";
12
+ /** The permission type OpenCode raises for any path outside the server cwd. */
13
+ export const EXTERNAL_DIRECTORY = "external_directory";
14
+ /**
15
+ * Tools that only observe. OpenCode raises a single `external_directory`
16
+ * permission for reads and writes alike, so the operation has to be recovered
17
+ * from the tool that triggered it. Names verified against the live catalog
18
+ * (`client.tool.ids()` on opencode 1.18.18) — there is no `list` tool.
19
+ */
20
+ const READ_ONLY_TOOLS = new Set(["read", "grep", "glob", "webfetch", "websearch"]);
21
+ /** Delay before re-subscribing after the event stream drops. */
22
+ const RECONNECT_DELAY_MS = 1_000;
23
+ /** Attempts to correlate a permission to its tool call before giving up. */
24
+ const CORRELATION_ATTEMPTS = 3;
25
+ /** Delay between correlation attempts, covering the event/message write race. */
26
+ const CORRELATION_DELAY_MS = 100;
27
+ const POLICIES = new Set([
28
+ "read-only",
29
+ "allow",
30
+ "deny",
31
+ ]);
32
+ /**
33
+ * Resolve the external-directory policy.
34
+ *
35
+ * Precedence: the `OPENCODE_MCP_EXTERNAL_DIR` environment variable wins over an
36
+ * `OPENCODE_MCP_EXTERNAL_DIR=<policy>` CLI arg, which wins over `read-only`.
37
+ * Unrecognised values fall back to the default.
38
+ *
39
+ * Reads `process.env` / `process.argv` lazily on each call so tests can vary
40
+ * them between invocations without module re-import tricks.
41
+ */
42
+ export function getExternalDirectoryPolicy() {
43
+ const fromEnv = parsePolicy(process.env.OPENCODE_MCP_EXTERNAL_DIR);
44
+ if (fromEnv !== undefined)
45
+ return fromEnv;
46
+ const argPrefix = "OPENCODE_MCP_EXTERNAL_DIR=";
47
+ const argEntry = process.argv.find((arg) => arg.startsWith(argPrefix));
48
+ if (argEntry !== undefined) {
49
+ const fromArg = parsePolicy(argEntry.slice(argPrefix.length));
50
+ if (fromArg !== undefined)
51
+ return fromArg;
52
+ }
53
+ return DEFAULT_EXTERNAL_DIRECTORY_POLICY;
54
+ }
55
+ function parsePolicy(value) {
56
+ if (value === undefined)
57
+ return undefined;
58
+ const normalized = value.trim().toLowerCase();
59
+ if (!POLICIES.has(normalized))
60
+ return undefined;
61
+ return normalized;
62
+ }
63
+ /**
64
+ * Build the config handed to `createOpencodeServer`.
65
+ *
66
+ * OpenCode deep-merges this over the user's global config per permission key,
67
+ * so only `external_directory` is touched — existing `read` / `edit` / `bash`
68
+ * rules (including deny globs for secrets) survive untouched.
69
+ *
70
+ * `read-only` keeps the key on `ask` on purpose: the permission responder is
71
+ * what applies the read/write split, and it can only do that if OpenCode still
72
+ * raises the request.
73
+ */
74
+ export function buildServerConfig(policy) {
75
+ const external_directory = policy === "read-only" ? "ask" : policy;
76
+ return { permission: { external_directory } };
77
+ }
78
+ /**
79
+ * Call ids arrive as `<tool>_<n>` (e.g. `read_0`, `write_0`), so the tool name
80
+ * is readable straight off the event with no round-trip and no race.
81
+ */
82
+ const CALL_ID_TOOL = /^(.+)_\d+$/;
83
+ /**
84
+ * Find the tool that raised a permission.
85
+ *
86
+ * The call id encodes the tool name, which is the fast path. If a future
87
+ * server stops encoding it, fall back to matching the id against the session's
88
+ * tool parts — retried, because the event can land before the part is readable.
89
+ */
90
+ async function resolveToolName(client, ask, sleep) {
91
+ const callID = permissionCallID(ask);
92
+ if (callID === undefined)
93
+ return undefined;
94
+ const encoded = CALL_ID_TOOL.exec(callID);
95
+ if (encoded)
96
+ return encoded[1];
97
+ for (let attempt = 0; attempt < CORRELATION_ATTEMPTS; attempt++) {
98
+ if (attempt > 0)
99
+ await sleep(CORRELATION_DELAY_MS);
100
+ const res = await client.session.messages({ path: { id: ask.sessionID } });
101
+ for (const message of res.data ?? []) {
102
+ for (const part of message.parts) {
103
+ if (part.type === "tool" && part.callID === callID)
104
+ return part.tool;
105
+ }
106
+ }
107
+ }
108
+ return undefined;
109
+ }
110
+ /**
111
+ * Decide how to answer a pending permission request.
112
+ *
113
+ * Anything that is not `external_directory` targets the directory the server
114
+ * was launched in, which the caller already owns — approved with `always` so
115
+ * repeat calls stop round-tripping.
116
+ *
117
+ * External reads are approved with `once` rather than `always` on purpose: an
118
+ * `always` reply persists the approval keyed by directory pattern, not by
119
+ * operation, which would silently let a later write into that same directory
120
+ * through and defeat the read-only boundary.
121
+ */
122
+ export async function decidePermission(client, ask, policy, sleep = defaultSleep) {
123
+ if (permissionKind(ask) !== EXTERNAL_DIRECTORY) {
124
+ return { response: "always", reason: "inside the server working directory" };
125
+ }
126
+ if (policy === "allow") {
127
+ return { response: "always", reason: "external_directory policy is 'allow'" };
128
+ }
129
+ if (policy === "deny") {
130
+ return { response: "reject", reason: "external_directory policy is 'deny'" };
131
+ }
132
+ // Shell commands carry their own metadata shape and can write through any
133
+ // number of paths, so they never qualify as read-only.
134
+ if (ask.metadata?.command !== undefined) {
135
+ return { response: "reject", reason: "shell command outside the working directory" };
136
+ }
137
+ const tool = await resolveToolName(client, ask, sleep);
138
+ if (tool === undefined) {
139
+ return { response: "reject", reason: "could not identify the tool behind the request" };
140
+ }
141
+ if (!READ_ONLY_TOOLS.has(tool)) {
142
+ return { response: "reject", reason: `'${tool}' writes outside the working directory` };
143
+ }
144
+ return { response: "once", reason: `'${tool}' only reads` };
145
+ }
146
+ function defaultSleep(ms) {
147
+ return new Promise((resolve) => setTimeout(resolve, ms));
148
+ }
149
+ /**
150
+ * Answer permission requests for a headless server.
151
+ *
152
+ * Without this, any request configured as `ask` blocks the agent forever: there
153
+ * is no interactive approver behind an MCP-spawned server, so the session stays
154
+ * genuinely busy and the task never completes or fails.
155
+ */
156
+ export function startPermissionResponder(client, options = {}) {
157
+ const policy = options.policy ?? getExternalDirectoryPolicy();
158
+ const onError = options.onError ?? (() => { });
159
+ const onDecision = options.onDecision ?? (() => { });
160
+ const sleep = options.sleep ?? defaultSleep;
161
+ let stopped = false;
162
+ const stop = () => {
163
+ stopped = true;
164
+ };
165
+ const done = (async () => {
166
+ while (!stopped) {
167
+ try {
168
+ const { stream } = await client.event.subscribe();
169
+ for await (const event of stream) {
170
+ if (stopped)
171
+ return;
172
+ if (!PERMISSION_EVENTS.has(event.type))
173
+ continue;
174
+ const ask = event.properties;
175
+ const decision = await decidePermission(client, ask, policy, sleep);
176
+ await client.postSessionIdPermissionsPermissionId({
177
+ path: { id: ask.sessionID, permissionID: ask.id },
178
+ body: { response: decision.response },
179
+ });
180
+ onDecision(ask, decision);
181
+ }
182
+ }
183
+ catch (error) {
184
+ onError(error);
185
+ }
186
+ if (stopped)
187
+ return;
188
+ await sleep(RECONNECT_DELAY_MS);
189
+ }
190
+ })();
191
+ return { stop, done };
192
+ }
193
+ //# sourceMappingURL=permissions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissions.js","sourceRoot":"","sources":["../../../../src/modules/shared/permissions.ts"],"names":[],"mappings":"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"}
@@ -4,8 +4,19 @@ export interface TaskRecord {
4
4
  sessionId: string;
5
5
  /** Epoch ms when the task was started; used to detect tasks that never produced output. */
6
6
  createdAt?: number;
7
+ /**
8
+ * Epoch ms when the task was cancelled. Without it an aborted session whose
9
+ * last assistant message carries a completed timestamp reports `completed`,
10
+ * which is indistinguishable from a task that finished on its own.
11
+ */
12
+ cancelledAt?: number;
7
13
  }
8
14
  export declare function registerTask(task: TaskRecord): void;
9
15
  export declare function getTask(taskId: string): TaskRecord | undefined;
16
+ /**
17
+ * Record that a task was cancelled. No-ops for an unknown id so callers do not
18
+ * have to re-check the registry after aborting.
19
+ */
20
+ export declare function markTaskCancelled(taskId: string, at?: number): void;
10
21
  export declare function removeTask(taskId: string): void;
11
22
  //# sourceMappingURL=task-registry.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"task-registry.d.ts","sourceRoot":"","sources":["../../../../src/modules/shared/task-registry.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,2FAA2F;IAC3F,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAID,wBAAgB,YAAY,CAAC,IAAI,EAAE,UAAU,QAE5C;AAED,wBAAgB,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAE9D;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,QAExC"}
1
+ {"version":3,"file":"task-registry.d.ts","sourceRoot":"","sources":["../../../../src/modules/shared/task-registry.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,2FAA2F;IAC3F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAID,wBAAgB,YAAY,CAAC,IAAI,EAAE,UAAU,QAE5C;AAED,wBAAgB,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAE9D;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,GAAE,MAAmB,QAIxE;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,QAExC"}
@@ -5,6 +5,16 @@ export function registerTask(task) {
5
5
  export function getTask(taskId) {
6
6
  return tasks.get(taskId);
7
7
  }
8
+ /**
9
+ * Record that a task was cancelled. No-ops for an unknown id so callers do not
10
+ * have to re-check the registry after aborting.
11
+ */
12
+ export function markTaskCancelled(taskId, at = Date.now()) {
13
+ const task = tasks.get(taskId);
14
+ if (!task)
15
+ return;
16
+ task.cancelledAt = at;
17
+ }
8
18
  export function removeTask(taskId) {
9
19
  tasks.delete(taskId);
10
20
  }
@@ -1 +1 @@
1
- {"version":3,"file":"task-registry.js","sourceRoot":"","sources":["../../../../src/modules/shared/task-registry.ts"],"names":[],"mappings":"AAQA,MAAM,KAAK,GAAG,IAAI,GAAG,EAAsB,CAAC;AAE5C,MAAM,UAAU,YAAY,CAAC,IAAgB;IAC3C,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,MAAc;IACpC,OAAO,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAAc;IACvC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;AACvB,CAAC"}
1
+ {"version":3,"file":"task-registry.js","sourceRoot":"","sources":["../../../../src/modules/shared/task-registry.ts"],"names":[],"mappings":"AAcA,MAAM,KAAK,GAAG,IAAI,GAAG,EAAsB,CAAC;AAE5C,MAAM,UAAU,YAAY,CAAC,IAAgB;IAC3C,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,MAAc;IACpC,OAAO,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AAC3B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAc,EAAE,EAAE,GAAW,IAAI,CAAC,GAAG,EAAE;IACvE,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC/B,IAAI,CAAC,IAAI;QAAE,OAAO;IAClB,IAAI,CAAC,WAAW,GAAG,EAAE,CAAC;AACxB,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAAc;IACvC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;AACvB,CAAC"}
@@ -0,0 +1,70 @@
1
+ /** Same page, anchored at the section we read — shown as the citation. */
2
+ export declare const USAGE_LIMITS_SOURCE_URL = "https://opencode.ai/docs/es/go/#l\u00EDmites-de-uso";
3
+ export type ModelQuota = {
4
+ model: string;
5
+ /** `null` when the docs list the quota as unmetered or unknown. */
6
+ perFiveHours: number | null;
7
+ perWeek: number | null;
8
+ perMonth: number | null;
9
+ };
10
+ export type SpendBudget = {
11
+ fiveHours: string | null;
12
+ weekly: string | null;
13
+ monthly: string | null;
14
+ };
15
+ export type UsageLimits = {
16
+ source: string;
17
+ /** ISO timestamp of the fetch that produced this snapshot. */
18
+ fetchedAt: string;
19
+ spendBudget: SpendBudget;
20
+ models: ModelQuota[];
21
+ };
22
+ /**
23
+ * Resolve where the daily snapshot is persisted.
24
+ *
25
+ * Precedence: `OPENCODE_MCP_CACHE_DIR` > `XDG_CACHE_HOME` > `~/.cache`, always
26
+ * under an `opencode-mcp/` subdirectory. Read lazily so tests can redirect it.
27
+ */
28
+ export declare function getCachePath(): string;
29
+ /** Snapshot lifetime in ms; `OPENCODE_MCP_LIMITS_TTL_MS` overrides the 24h default. */
30
+ export declare function getCacheTtlMs(): number;
31
+ /**
32
+ * Return the OpenCode Go usage limits, fetching the docs at most once a day.
33
+ *
34
+ * A fresh cache entry short-circuits the network. On a fetch or parse failure
35
+ * a stale cache entry is still returned, so an offline start degrades to
36
+ * yesterday's numbers instead of no numbers at all. Returns `null` only when
37
+ * there is neither a usable cache nor a usable response.
38
+ */
39
+ export declare function getUsageLimits(): Promise<UsageLimits | null>;
40
+ /**
41
+ * Pull the spend budget and the per-model request estimates out of the docs
42
+ * HTML. Returns `null` when the quota table is missing or unrecognisable, so a
43
+ * page redesign falls back to the cache instead of injecting garbage.
44
+ */
45
+ export declare function parseUsageLimits(html: string): Omit<UsageLimits, "source" | "fetchedAt"> | null;
46
+ /** How costly one request is against the shared budget, cheapest tier first. */
47
+ export type QuotaTier = "high-volume" | "balanced" | "scarce" | "unlisted";
48
+ /** Bucket a model by its 5h request estimate. Thresholds live here only. */
49
+ export declare function quotaTier(perFiveHours: number | null): QuotaTier;
50
+ /**
51
+ * Index the snapshot by slugified display name so a live model id from an
52
+ * OpenCode server (`mimo-v2.5`) joins onto the docs row (`MiMo-V2.5`).
53
+ *
54
+ * Verified against a real server: all 22 `opencode-go` model ids match this
55
+ * way. Ids with no row (the free/preview models on the `opencode` provider)
56
+ * are simply absent, which callers report as "no published quota".
57
+ */
58
+ export declare function quotaByModelId(limits: UsageLimits): Map<string, ModelQuota>;
59
+ /**
60
+ * Render the live half of the quota guidance: where the numbers came from and
61
+ * the shared dollar budget every model spends against.
62
+ *
63
+ * The per-model numbers deliberately stay OUT of the instructions —
64
+ * opencode_list_agents reports them per model, keyed by the real model id,
65
+ * which is the only form the agent can act on.
66
+ */
67
+ export declare function formatUsageLimits(limits: UsageLimits): string;
68
+ /** Render the static tier legend, one line per `tier` value list_agents emits. */
69
+ export declare function formatTierGuide(): string;
70
+ //# sourceMappingURL=usage-limits.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage-limits.d.ts","sourceRoot":"","sources":["../../../../src/modules/shared/usage-limits.ts"],"names":[],"mappings":"AAMA,0EAA0E;AAC1E,eAAO,MAAM,uBAAuB,wDAAmD,CAAC;AAsBxF,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,8DAA8D;IAC9D,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,WAAW,CAAC;IACzB,MAAM,EAAE,UAAU,EAAE,CAAC;CACtB,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAOrC;AAED,uFAAuF;AACvF,wBAAgB,aAAa,IAAI,MAAM,CAMtC;AAED;;;;;;;GAOG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CASlE;AA+BD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC,WAAW,EAAE,QAAQ,GAAG,WAAW,CAAC,GAAG,IAAI,CAS/F;AAkID,gFAAgF;AAChF,MAAM,MAAM,SAAS,GAAG,aAAa,GAAG,UAAU,GAAG,QAAQ,GAAG,UAAU,CAAC;AAe3E,4EAA4E;AAC5E,wBAAgB,SAAS,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAKhE;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAE3E;AAMD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAK7D;AAED,kFAAkF;AAClF,wBAAgB,eAAe,IAAI,MAAM,CAExC"}