@coool/opencode-adaptive 1.3.0 → 1.4.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.
- package/README.md +1 -0
- package/dist/adapters/opencode/command.d.ts +30 -0
- package/dist/adapters/opencode/command.js +85 -0
- package/dist/adapters/opencode/command.js.map +1 -0
- package/dist/adapters/opencode/index.d.ts +1 -0
- package/dist/adapters/opencode/index.js +1 -0
- package/dist/adapters/opencode/index.js.map +1 -1
- package/dist/health/classifier/classify.js +9 -0
- package/dist/health/classifier/classify.js.map +1 -1
- package/dist/index.js +14 -2
- package/dist/index.js.map +1 -1
- package/dist/rpc/handlers.d.ts +6 -0
- package/dist/rpc/handlers.js +24 -18
- package/dist/rpc/handlers.js.map +1 -1
- package/dist/ui/flow/menu.d.ts +2 -0
- package/dist/ui/flow/menu.js +1 -1
- package/dist/ui/flow/menu.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -86,6 +86,7 @@ An outage never moves work to a stronger tier. Work only moves up on real eviden
|
|
|
86
86
|
- A task that moved up a tier stays there. The next task is routed again from scratch.
|
|
87
87
|
- Usage figures come from response headers and are best effort. Providers that use WebSockets don't expose them.
|
|
88
88
|
- The browser/desktop settings page is experimental. No released OpenCode loads it yet, so use `/adaptive`.
|
|
89
|
+
- In the web UI (including on mobile) type `/web-adaptive` for the mode and model status, or `/web-adaptive on`, `off`, `observe`, `active` to switch the mode. It is a server command, which the web app lists in the composer's slash menu.
|
|
89
90
|
|
|
90
91
|
## Troubleshooting
|
|
91
92
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { OperatingMode } from "../../core/scheduler/types.js";
|
|
2
|
+
import type { StatusSnapshot } from "../../rpc/status.js";
|
|
3
|
+
export interface CommandSource {
|
|
4
|
+
status(): Promise<StatusSnapshot>;
|
|
5
|
+
setMode(mode: OperatingMode): Promise<{
|
|
6
|
+
readonly ok: true;
|
|
7
|
+
} | {
|
|
8
|
+
readonly ok: false;
|
|
9
|
+
readonly errors: readonly string[];
|
|
10
|
+
}>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The web UI's way to see and switch the mode (ADP-034). The host's browser
|
|
14
|
+
* app never mounts plugin GUI bundles, but it lists server commands in the
|
|
15
|
+
* composer's slash menu, so this is the one channel that reaches it. The name
|
|
16
|
+
* differs from the TUI's `/adaptive` on purpose: a same-named server command
|
|
17
|
+
* appears twice in the TUI palette. Replies are plain text posted without
|
|
18
|
+
* waking the model, one message per line: the web timeline shows each message
|
|
19
|
+
* as a single truncated row, so a long reply would hide its end.
|
|
20
|
+
*/
|
|
21
|
+
export declare function adaptiveCommand(source: CommandSource, say: (sessionID: string, text: string) => Promise<unknown>): {
|
|
22
|
+
name: string;
|
|
23
|
+
description: string;
|
|
24
|
+
execute: (input: {
|
|
25
|
+
sessionID: string;
|
|
26
|
+
prompt: {
|
|
27
|
+
text: string;
|
|
28
|
+
};
|
|
29
|
+
}) => Promise<void>;
|
|
30
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { OPERATING_MODES } from "../../core/scheduler/types.js";
|
|
2
|
+
import { logFailOpen } from "../../supervisor/log.js";
|
|
3
|
+
import { modeDescription } from "../../ui/flow/menu.js";
|
|
4
|
+
import { healthEntries, stateCell } from "../../ui/flow/status.js";
|
|
5
|
+
import { resourceLabel } from "../../ui/flow/tiers.js";
|
|
6
|
+
const COMMAND_NAME = "web-adaptive";
|
|
7
|
+
/** `on` is the everyday word for `active`. */
|
|
8
|
+
const MODE_WORDS = ["on", "off", "observe", "active"];
|
|
9
|
+
const USAGE = `Usage: /${COMMAND_NAME} [status | ${MODE_WORDS.join(" | ")}]`;
|
|
10
|
+
/** How to switch, appended to the status so the next step is on screen. */
|
|
11
|
+
const HELP = [
|
|
12
|
+
"Switch the mode:",
|
|
13
|
+
...OPERATING_MODES.map((mode) => `/${COMMAND_NAME} ${mode === "active" ? "on" : mode} — ${modeDescription(mode)}`),
|
|
14
|
+
`/${COMMAND_NAME} — show this status again`,
|
|
15
|
+
];
|
|
16
|
+
function parse(text) {
|
|
17
|
+
const argument = text
|
|
18
|
+
.trim()
|
|
19
|
+
.replace(new RegExp(`^/${COMMAND_NAME}\\b\\s*`, "i"), "")
|
|
20
|
+
.trim()
|
|
21
|
+
.toLowerCase();
|
|
22
|
+
if (argument === "" || argument === "status")
|
|
23
|
+
return { kind: "status" };
|
|
24
|
+
if (argument === "on")
|
|
25
|
+
return { kind: "mode", mode: "active" };
|
|
26
|
+
const mode = OPERATING_MODES.find((candidate) => candidate === argument);
|
|
27
|
+
return mode === undefined ? { kind: "usage" } : { kind: "mode", mode };
|
|
28
|
+
}
|
|
29
|
+
function modeLine(prefix, mode) {
|
|
30
|
+
return `${prefix} ${mode} — ${modeDescription(mode)}.`;
|
|
31
|
+
}
|
|
32
|
+
/** One model as `provider/model (tier, health[, window, usage, pin])`. */
|
|
33
|
+
function modelLine(entry, pin) {
|
|
34
|
+
const state = stateCell(entry.status, pin);
|
|
35
|
+
const detail = state === "-" ? entry.status.health : `${entry.status.health}, ${state}`;
|
|
36
|
+
return `${resourceLabel(entry.status.ref)} (${entry.tier}, ${detail})`;
|
|
37
|
+
}
|
|
38
|
+
function statusLines(snapshot) {
|
|
39
|
+
const entries = healthEntries(snapshot);
|
|
40
|
+
const models = entries.length === 0
|
|
41
|
+
? ["No models configured yet."]
|
|
42
|
+
: entries.map((entry) => modelLine(entry, snapshot.pin));
|
|
43
|
+
return [modeLine("Adaptive:", snapshot.mode), ...models, ...HELP];
|
|
44
|
+
}
|
|
45
|
+
async function reply(source, text) {
|
|
46
|
+
const action = parse(text);
|
|
47
|
+
switch (action.kind) {
|
|
48
|
+
case "usage":
|
|
49
|
+
return [USAGE];
|
|
50
|
+
case "status":
|
|
51
|
+
return statusLines(await source.status());
|
|
52
|
+
case "mode": {
|
|
53
|
+
const result = await source.setMode(action.mode);
|
|
54
|
+
return [
|
|
55
|
+
result.ok
|
|
56
|
+
? modeLine("Adaptive switched to", action.mode)
|
|
57
|
+
: `Adaptive could not switch mode: ${result.errors.join("; ")}`,
|
|
58
|
+
];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The web UI's way to see and switch the mode (ADP-034). The host's browser
|
|
64
|
+
* app never mounts plugin GUI bundles, but it lists server commands in the
|
|
65
|
+
* composer's slash menu, so this is the one channel that reaches it. The name
|
|
66
|
+
* differs from the TUI's `/adaptive` on purpose: a same-named server command
|
|
67
|
+
* appears twice in the TUI palette. Replies are plain text posted without
|
|
68
|
+
* waking the model, one message per line: the web timeline shows each message
|
|
69
|
+
* as a single truncated row, so a long reply would hide its end.
|
|
70
|
+
*/
|
|
71
|
+
export function adaptiveCommand(source, say) {
|
|
72
|
+
return {
|
|
73
|
+
name: COMMAND_NAME,
|
|
74
|
+
description: `Adaptive mode and status: /${COMMAND_NAME} | on | off | observe | active`,
|
|
75
|
+
execute: async (input) => {
|
|
76
|
+
const lines = await reply(source, input.prompt.text).catch((error) => {
|
|
77
|
+
logFailOpen("command", error);
|
|
78
|
+
return ["Adaptive could not complete that command."];
|
|
79
|
+
});
|
|
80
|
+
for (const line of lines)
|
|
81
|
+
await say(input.sessionID, line);
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command.js","sourceRoot":"","sources":["../../../src/adapters/opencode/command.ts"],"names":[],"mappings":"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","sourcesContent":["import { OPERATING_MODES } from \"../../core/scheduler/types.js\"\nimport type { OperatingMode } from \"../../core/scheduler/types.js\"\nimport type { StatusSnapshot } from \"../../rpc/status.js\"\nimport { logFailOpen } from \"../../supervisor/log.js\"\nimport { modeDescription } from \"../../ui/flow/menu.js\"\nimport { healthEntries, stateCell } from \"../../ui/flow/status.js\"\nimport { resourceLabel } from \"../../ui/flow/tiers.js\"\n\nconst COMMAND_NAME = \"web-adaptive\"\n\n/** `on` is the everyday word for `active`. */\nconst MODE_WORDS = [\"on\", \"off\", \"observe\", \"active\"] as const\n\nconst USAGE = `Usage: /${COMMAND_NAME} [status | ${MODE_WORDS.join(\" | \")}]`\n\n/** How to switch, appended to the status so the next step is on screen. */\nconst HELP = [\n \"Switch the mode:\",\n ...OPERATING_MODES.map(\n (mode) => `/${COMMAND_NAME} ${mode === \"active\" ? \"on\" : mode} — ${modeDescription(mode)}`,\n ),\n `/${COMMAND_NAME} — show this status again`,\n]\n\nexport interface CommandSource {\n status(): Promise<StatusSnapshot>\n setMode(\n mode: OperatingMode,\n ): Promise<{ readonly ok: true } | { readonly ok: false; readonly errors: readonly string[] }>\n}\n\ntype Action = { kind: \"status\" } | { kind: \"mode\"; mode: OperatingMode } | { kind: \"usage\" }\n\nfunction parse(text: string): Action {\n const argument = text\n .trim()\n .replace(new RegExp(`^/${COMMAND_NAME}\\\\b\\\\s*`, \"i\"), \"\")\n .trim()\n .toLowerCase()\n if (argument === \"\" || argument === \"status\") return { kind: \"status\" }\n if (argument === \"on\") return { kind: \"mode\", mode: \"active\" }\n const mode = OPERATING_MODES.find((candidate) => candidate === argument)\n return mode === undefined ? { kind: \"usage\" } : { kind: \"mode\", mode }\n}\n\nfunction modeLine(prefix: string, mode: OperatingMode): string {\n return `${prefix} ${mode} — ${modeDescription(mode)}.`\n}\n\n/** One model as `provider/model (tier, health[, window, usage, pin])`. */\nfunction modelLine(entry: ReturnType<typeof healthEntries>[number], pin: StatusSnapshot[\"pin\"]) {\n const state = stateCell(entry.status, pin)\n const detail = state === \"-\" ? entry.status.health : `${entry.status.health}, ${state}`\n return `${resourceLabel(entry.status.ref)} (${entry.tier}, ${detail})`\n}\n\nfunction statusLines(snapshot: StatusSnapshot): string[] {\n const entries = healthEntries(snapshot)\n const models =\n entries.length === 0\n ? [\"No models configured yet.\"]\n : entries.map((entry) => modelLine(entry, snapshot.pin))\n return [modeLine(\"Adaptive:\", snapshot.mode), ...models, ...HELP]\n}\n\nasync function reply(source: CommandSource, text: string): Promise<string[]> {\n const action = parse(text)\n switch (action.kind) {\n case \"usage\":\n return [USAGE]\n case \"status\":\n return statusLines(await source.status())\n case \"mode\": {\n const result = await source.setMode(action.mode)\n return [\n result.ok\n ? modeLine(\"Adaptive switched to\", action.mode)\n : `Adaptive could not switch mode: ${result.errors.join(\"; \")}`,\n ]\n }\n }\n}\n\n/**\n * The web UI's way to see and switch the mode (ADP-034). The host's browser\n * app never mounts plugin GUI bundles, but it lists server commands in the\n * composer's slash menu, so this is the one channel that reaches it. The name\n * differs from the TUI's `/adaptive` on purpose: a same-named server command\n * appears twice in the TUI palette. Replies are plain text posted without\n * waking the model, one message per line: the web timeline shows each message\n * as a single truncated row, so a long reply would hide its end.\n */\nexport function adaptiveCommand(\n source: CommandSource,\n say: (sessionID: string, text: string) => Promise<unknown>,\n) {\n return {\n name: COMMAND_NAME,\n description: `Adaptive mode and status: /${COMMAND_NAME} | on | off | observe | active`,\n execute: async (input: { sessionID: string; prompt: { text: string } }): Promise<void> => {\n const lines = await reply(source, input.prompt.text).catch((error: unknown) => {\n logFailOpen(\"command\", error)\n return [\"Adaptive could not complete that command.\"]\n })\n for (const line of lines) await say(input.sessionID, line)\n },\n }\n}\n"]}
|
|
@@ -13,4 +13,5 @@ export { registerSessionHooks } from "./hooks.js";
|
|
|
13
13
|
export type { SessionHookHandlers, SessionHookSource } from "./hooks.js";
|
|
14
14
|
export { ESCALATION_TOOL_NAME, escalationTool } from "./escalation-tool.js";
|
|
15
15
|
export { tierTool } from "./tier-tool.js";
|
|
16
|
+
export { adaptiveCommand } from "./command.js";
|
|
16
17
|
export type { EscalationRecorder } from "./escalation-tool.js";
|
|
@@ -6,4 +6,5 @@ export { SESSION_EVENT_TYPES, subscribeSessions } from "./events.js";
|
|
|
6
6
|
export { registerSessionHooks } from "./hooks.js";
|
|
7
7
|
export { ESCALATION_TOOL_NAME, escalationTool } from "./escalation-tool.js";
|
|
8
8
|
export { tierTool } from "./tier-tool.js";
|
|
9
|
+
export { adaptiveCommand } from "./command.js";
|
|
9
10
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/adapters/opencode/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAU/F,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAE3E,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAE7C,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAE1C,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAEpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAEjD,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAC3E,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA","sourcesContent":["export { BODY_EXCERPT_LENGTH, mapHttpResponse, mapSessionEvent, modelRefOf } from \"./report.js\"\nexport type { SessionEvent } from \"./report.js\"\nexport type {\n AdapterStructuredError,\n EventReport,\n ExecutionReport,\n HttpResponseReport,\n IdleReport,\n ModelRequestReport,\n} from \"./types.js\"\nexport { candidateFor, listCandidates, mapModelInfo } from \"./inventory.js\"\nexport type { ModelSource } from \"./inventory.js\"\nexport { sessionOps } from \"./session-ops.js\"\nexport type { SessionOpsHooks, SessionOpsSource } from \"./session-ops.js\"\nexport { storageSeam } from \"./storage.js\"\nexport type { JsonSeam, StorageSource } from \"./storage.js\"\nexport { SESSION_EVENT_TYPES, subscribeSessions } from \"./events.js\"\nexport type { EventSource, SessionSubscription } from \"./events.js\"\nexport { registerSessionHooks } from \"./hooks.js\"\nexport type { SessionHookHandlers, SessionHookSource } from \"./hooks.js\"\nexport { ESCALATION_TOOL_NAME, escalationTool } from \"./escalation-tool.js\"\nexport { tierTool } from \"./tier-tool.js\"\nexport type { EscalationRecorder } from \"./escalation-tool.js\"\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/adapters/opencode/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAU/F,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAE3E,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAE7C,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAE1C,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAEpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAEjD,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAC3E,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA","sourcesContent":["export { BODY_EXCERPT_LENGTH, mapHttpResponse, mapSessionEvent, modelRefOf } from \"./report.js\"\nexport type { SessionEvent } from \"./report.js\"\nexport type {\n AdapterStructuredError,\n EventReport,\n ExecutionReport,\n HttpResponseReport,\n IdleReport,\n ModelRequestReport,\n} from \"./types.js\"\nexport { candidateFor, listCandidates, mapModelInfo } from \"./inventory.js\"\nexport type { ModelSource } from \"./inventory.js\"\nexport { sessionOps } from \"./session-ops.js\"\nexport type { SessionOpsHooks, SessionOpsSource } from \"./session-ops.js\"\nexport { storageSeam } from \"./storage.js\"\nexport type { JsonSeam, StorageSource } from \"./storage.js\"\nexport { SESSION_EVENT_TYPES, subscribeSessions } from \"./events.js\"\nexport type { EventSource, SessionSubscription } from \"./events.js\"\nexport { registerSessionHooks } from \"./hooks.js\"\nexport type { SessionHookHandlers, SessionHookSource } from \"./hooks.js\"\nexport { ESCALATION_TOOL_NAME, escalationTool } from \"./escalation-tool.js\"\nexport { tierTool } from \"./tier-tool.js\"\nexport { adaptiveCommand } from \"./command.js\"\nexport type { EscalationRecorder } from \"./escalation-tool.js\"\n"]}
|
|
@@ -188,6 +188,9 @@ function classifyStatus(status, headers) {
|
|
|
188
188
|
if (status === 401 || status === 403) {
|
|
189
189
|
return "auth_failure";
|
|
190
190
|
}
|
|
191
|
+
if (status === 402) {
|
|
192
|
+
return "quota_exhausted";
|
|
193
|
+
}
|
|
191
194
|
if (status === 408) {
|
|
192
195
|
return "timeout";
|
|
193
196
|
}
|
|
@@ -241,6 +244,12 @@ export function classifyFailure(evidence) {
|
|
|
241
244
|
const category = classifyStatus(evidence.status, headers);
|
|
242
245
|
if (category !== undefined) {
|
|
243
246
|
const retryAfterMs = structuredRetryAfterMs ?? retryAfterHeaderMs(headers);
|
|
247
|
+
// Payment Required is the account's balance, not one model's limit:
|
|
248
|
+
// every model behind the provider ID (a gateway such as OpenRouter or
|
|
249
|
+
// Ofox) bills the same account, so a sibling model cannot help.
|
|
250
|
+
if (evidence.status === 402) {
|
|
251
|
+
return classification(category, "status", true, retryAfterMs, "provider");
|
|
252
|
+
}
|
|
244
253
|
if (category === "rate_limited" && evidence.text !== undefined) {
|
|
245
254
|
const bounded = evidence.text.slice(0, TEXT_BOUND);
|
|
246
255
|
if (classifyText(bounded) === "quota_exhausted" && !RATE_LIMIT_TEXT.test(bounded)) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"classify.js","sourceRoot":"","sources":["../../../src/health/classifier/classify.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,UAAU,GAAG,GAAG,CAAA;AAEtB,0EAA0E;AAC1E,wEAAwE;AACxE,MAAM,oBAAoB,GACxB,gJAAgJ,CAAA;AAElJ,yEAAyE;AACzE,uEAAuE;AACvE,4EAA4E;AAC5E,6EAA6E;AAC7E,0EAA0E;AAC1E,4EAA4E;AAC5E,QAAQ;AACR,MAAM,eAAe,GAAG,0BAA0B,CAAA;AAElD;;;;;;;;;;;GAWG;AACH,MAAM,eAAe,GAEjB;IACF,YAAY,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE;IACzD,eAAe,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,gBAAgB,EAAE,IAAI,EAAE;IAC7D,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE;IACnD,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE;IACrD,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE;IACpD,YAAY,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE;IAC3D,aAAa,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE;IAC5D,gBAAgB,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE;IAC/D,cAAc,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE;IAC7D,cAAc,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE;IAC7D,OAAO,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE;CACvD,CAAA;AAED;;;;;;GAMG;AACH,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAA0B;IACxD,CAAC,mBAAmB,EAAE,cAAc,CAAC;IACrC,CAAC,gBAAgB,EAAE,cAAc,CAAC;IAClC,CAAC,oBAAoB,EAAE,eAAe,CAAC;IACvC,CAAC,uBAAuB,EAAE,eAAe,CAAC;IAC1C,CAAC,yBAAyB,EAAE,QAAQ,CAAC;CACtC,CAAC,CAAA;AAEF;;;;;GAKG;AACH,MAAM,gBAAgB,GAGhB;IACJ;QACE,QAAQ,EAAE,kBAAkB;QAC5B,OAAO,EACL,+HAA+H;KAClI;IACD;QACE,wEAAwE;QACxE,sEAAsE;QACtE,wEAAwE;QACxE,sDAAsD;QACtD,mEAAmE;QACnE,yEAAyE;QACzE,sEAAsE;QACtE,yEAAyE;QACzE,mEAAmE;QACnE,QAAQ,EAAE,iBAAiB;QAC3B,OAAO,EACL,wJAAwJ;KAC3J;IACD,EAAE,QAAQ,EAAE,cAAc,EAAE,OAAO,EAAE,cAAc,EAAE;IACrD,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,sDAAsD,EAAE;IACxF;QACE,QAAQ,EAAE,cAAc;QACxB,OAAO,EACL,mGAAmG;KACtG;IACD;QACE,QAAQ,EAAE,eAAe;QACzB,OAAO,EAAE,mFAAmF;KAC7F;IACD;QACE,QAAQ,EAAE,gBAAgB;QAC1B,OAAO,EACL,wFAAwF;KAC3F;IACD,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,uCAAuC,EAAE;IAC1E,EAAE,QAAQ,EAAE,gBAAgB,EAAE,OAAO,EAAE,kDAAkD,EAAE;CAC5F,CAAA;AAED,SAAS,YAAY,CAAC,MAA0B;IAC9C,OAAO,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,GAAG,IAAI,MAAM,IAAI,GAAG,CAAA;AACjG,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,kBAAkB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;AAE9C,SAAS,mBAAmB,CAAC,YAAgC;IAC3D,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;QAC/B,OAAO,SAAS,CAAA;IAClB,CAAC;IACD,OAAO,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,kBAAkB,CAAC,CAAA;AACnD,CAAC;AAED,SAAS,oBAAoB,CAAC,KAAyB;IACrD,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,CAAA;AAC3E,CAAC;AAED,SAAS,iBAAiB,CACxB,OAAqD;IAErD,MAAM,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAA;IACzC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,OAAO,OAAO,CAAA;IAChB,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACpD,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,KAAK,CAAC,CAAA;IACxC,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC;AAED;;;;;GAKG;AACH,SAAS,kBAAkB,CAAC,OAAoC;IAC9D,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAA;IACtC,IAAI,GAAG,KAAK,SAAS,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5C,OAAO,SAAS,CAAA;IAClB,CAAC;IACD,OAAO,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,CAAA;AAC3B,CAAC;AAED;;;;GAIG;AACH,SAAS,oBAAoB,CAAC,OAAoC;IAChE,IAAI,YAAY,GAAG,KAAK,CAAA;IACxB,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,OAAO,EAAE,CAAC;QACpC,IAAI,IAAI,CAAC,UAAU,CAAC,uBAAuB,CAAC,EAAE,CAAC;YAC7C,YAAY,GAAG,IAAI,CAAA;YACnB,IAAI,KAAK,KAAK,GAAG,EAAE,CAAC;gBAClB,OAAO,KAAK,CAAA;YACd,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,YAAY,CAAA;AACrB,CAAC;AAED,SAAS,WAAW,CAAC,QAAyB,EAAE,YAAgC;IAC9E,MAAM,MAAM,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAA;IACxC,MAAM,OAAO,GAAG,mBAAmB,CAAC,YAAY,CAAC,CAAA;IACjD,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,CAAA;IACxC,CAAC;IACD,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,YAAY,EAAE,OAAO,EAAE,CAAA;AAC/D,CAAC;AAED,SAAS,cAAc,CACrB,QAAyB,EACzB,KAAqC,EACrC,UAAmB,EACnB,YAAgC,EAChC,UAAuB;IAEvB,MAAM,MAAM,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAA;IACxC,OAAO;QACL,QAAQ;QACR,KAAK;QACL,UAAU;QACV,KAAK,EAAE,WAAW,CAAC,QAAQ,EAAE,YAAY,CAAC;QAC1C,gBAAgB,EAAE,MAAM,CAAC,gBAAgB;QACzC,GAAG,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACpD,CAAA;AACH,CAAC;AAED;;;;;GAKG;AACH,SAAS,cAAc,CACrB,MAAc,EACd,OAAoC;IAEpC,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;QACnB,OAAO,oBAAoB,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,cAAc,CAAA;IAC3E,CAAC;IACD,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;QACrC,OAAO,cAAc,CAAA;IACvB,CAAC;IACD,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;QACnB,OAAO,SAAS,CAAA;IAClB,CAAC;IACD,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;QACnB,OAAO,kBAAkB,CAAA;IAC3B,CAAC;IACD,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;QACvD,OAAO,QAAQ,CAAA;IACjB,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,SAAS,YAAY,CAAC,IAAY;IAChC,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IAC3E,KAAK,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,gBAAgB,EAAE,CAAC;QACrD,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YAC1B,OAAO,QAAQ,CAAA;QACjB,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,QAAuB;IACrD,wEAAwE;IACxE,6DAA6D;IAC7D,IAAI,QAAQ,CAAC,WAAW,KAAK,MAAM,EAAE,CAAC;QACpC,OAAO,cAAc,CAAC,gBAAgB,EAAE,cAAc,EAAE,IAAI,EAAE,SAAS,CAAC,CAAA;IAC1E,CAAC;IACD,IAAI,QAAQ,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;QACvC,OAAO,cAAc,CAAC,SAAS,EAAE,cAAc,EAAE,IAAI,EAAE,SAAS,CAAC,CAAA;IACnE,CAAC;IAED,yCAAyC;IACzC,MAAM,UAAU,GAAG,QAAQ,CAAC,UAAU,CAAA;IACtC,MAAM,sBAAsB,GAAG,oBAAoB,CAAC,UAAU,EAAE,YAAY,CAAC;QAC3E,CAAC,CAAC,UAAU,CAAC,YAAY;QACzB,CAAC,CAAC,SAAS,CAAA;IACb,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC7B,MAAM,MAAM,GAAG,gBAAgB,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;QACpD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,OAAO,cAAc,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,sBAAsB,CAAC,CAAA;QAC3E,CAAC;QACD,wEAAwE;IAC1E,CAAC;IAED,oEAAoE;IACpE,MAAM,OAAO,GAAG,iBAAiB,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAA;IACnD,IAAI,YAAY,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QAClC,MAAM,QAAQ,GAAG,cAAc,CAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;QACzD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,MAAM,YAAY,GAAG,sBAAsB,IAAI,kBAAkB,CAAC,OAAO,CAAC,CAAA;YAC1E,IAAI,QAAQ,KAAK,cAAc,IAAI,QAAQ,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gBAC/D,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAA;gBAClD,IAAI,YAAY,CAAC,OAAO,CAAC,KAAK,iBAAiB,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;oBAClF,MAAM,UAAU,GAAG,oBAAoB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAA;oBAC9E,OAAO,cAAc,CAAC,iBAAiB,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,EAAE,UAAU,CAAC,CAAA;gBACpF,CAAC;YACH,CAAC;YACD,OAAO,cAAc,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,CAAC,CAAA;QAC/D,CAAC;IACH,CAAC;IAED,2EAA2E;IAC3E,MAAM,kBAAkB,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAA;IACtD,IAAI,kBAAkB,KAAK,SAAS,EAAE,CAAC;QACrC,OAAO,cAAc,CAAC,cAAc,EAAE,SAAS,EAAE,KAAK,EAAE,kBAAkB,CAAC,CAAA;IAC7E,CAAC;IAED,wEAAwE;IACxE,oCAAoC;IACpC,IAAI,QAAQ,CAAC,gBAAgB,EAAE,cAAc,KAAK,IAAI,EAAE,CAAC;QACvD,OAAO,cAAc,CAAC,iBAAiB,EAAE,mBAAmB,EAAE,IAAI,EAAE,SAAS,CAAC,CAAA;IAChF,CAAC;IACD,IAAI,QAAQ,CAAC,gBAAgB,EAAE,UAAU,KAAK,IAAI,EAAE,CAAC;QACnD,OAAO,cAAc,CAAC,UAAU,EAAE,mBAAmB,EAAE,IAAI,EAAE,sBAAsB,CAAC,CAAA;IACtF,CAAC;IAED,kEAAkE;IAClE,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAA;IAC1B,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,MAAM,QAAQ,GAAG,YAAY,CAAC,IAAI,CAAC,CAAA;QACnC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,MAAM,UAAU,GACd,QAAQ,KAAK,iBAAiB,IAAI,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;gBACpF,CAAC,CAAC,UAAU;gBACZ,CAAC,CAAC,SAAS,CAAA;YACf,OAAO,cAAc,CAAC,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,sBAAsB,EAAE,UAAU,CAAC,CAAA;QACvF,CAAC;IACH,CAAC;IAED,OAAO,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,CAAC,CAAA;AAC5D,CAAC","sourcesContent":["import type { ErrorEvidence, FailureCategory, FailureClassification, RetryAdvice } from \"./types.js\"\n\n/**\n * Text fed to the textual last-resort layer is truncated to this bound before\n * matching, so adversarial or enormous error bodies cannot dominate\n * classification cost or smuggle patterns in late bytes (ADP-008).\n */\nconst TEXT_BOUND = 512\n\n// These messages describe the subscription's shared allowance or balance,\n// unlike a generic quota error or a named model-family limit (ADP-007).\nconst SHARED_QUOTA_PATTERN =\n /\\byou['’]ve hit your (?:session|usage|weekly) limit(?=\\s*(?:$|[·.,;(:]|resets?\\b))|\\b(?:out of credits|insufficient credits|credit balance)\\b/i\n\n// A 429 whose message says the quota itself is spent (\"You exceeded your\n// current quota, please check your plan and billing details\") is quota\n// exhaustion: a burst-sized window would re-admit it every half minute. The\n// status stays the definitive evidence; text only picks the window. Messages\n// that also speak of rate limits keep the short window — Gemini words its\n// per-minute 429s as \"exceeded your current quota\" and links its rate-limit\n// docs.\nconst RATE_LIMIT_TEXT = /rate[ _-]?limit|per min/i\n\n/**\n * Retry/failover policy per category (ADP-010, ADP-011).\n *\n * - Infrastructure failures (rate limiting, quota, outage, overload, timeout)\n * are failover-eligible: horizontal recovery within the tier is exactly\n * their remedy.\n * - Permanent configuration failures (auth, invalid model, content policy)\n * and context overflow never retry and never failover: retrying the same\n * request cannot help and switching resources would reproduce the failure.\n * - User cancellation is final: no retry, no failover (ADP-010).\n * - Unknown failures defer to OpenCode's native handling.\n */\nconst CATEGORY_POLICY: Readonly<\n Record<FailureCategory, { retryable: boolean; failoverEligible: boolean }>\n> = {\n rate_limited: { retryable: true, failoverEligible: true },\n quota_exhausted: { retryable: false, failoverEligible: true },\n outage: { retryable: true, failoverEligible: true },\n overload: { retryable: true, failoverEligible: true },\n timeout: { retryable: true, failoverEligible: true },\n auth_failure: { retryable: false, failoverEligible: false },\n invalid_model: { retryable: false, failoverEligible: false },\n context_overflow: { retryable: false, failoverEligible: false },\n content_policy: { retryable: false, failoverEligible: false },\n user_cancelled: { retryable: false, failoverEligible: false },\n unknown: { retryable: false, failoverEligible: false },\n}\n\n/**\n * Error tags from the documented @opencode/protocol vocabulary that map\n * directly to a failure category. Tags absent here (`ProviderRequestError`,\n * `UnknownError`, anything unrecognized) are structured but not\n * category-specific: they never guess, and classification falls through to\n * the refining layers.\n */\nconst STRUCTURED_TYPES = new Map<string, FailureCategory>([\n [\"UnauthorizedError\", \"auth_failure\"],\n [\"ForbiddenError\", \"auth_failure\"],\n [\"ModelNotFoundError\", \"invalid_model\"],\n [\"UnsupportedModelError\", \"invalid_model\"],\n [\"ServiceUnavailableError\", \"outage\"],\n])\n\n/**\n * Bounded textual patterns, last resort only (ADP-008). Ordered most\n * specific first; the first match wins. These encode provider error-message\n * conventions documented by the research phase, not a general-purpose\n * keyword classifier.\n */\nconst TEXTUAL_PATTERNS: readonly {\n readonly category: FailureCategory\n readonly pattern: RegExp\n}[] = [\n {\n category: \"context_overflow\",\n pattern:\n /context (length|window)|maximum context|input length exceeds|prompt is too long|too many (input )?tokens|exceeds the maximum/i,\n },\n {\n // Subscription limits (\"You've hit your session/usage limit\") are quota\n // exhaustion with a provider-side reset window; the reset time in the\n // text is not parsed (the classifier has no clock) — the health layer's\n // default quota window bounds the quarantine instead.\n // Sibling fallback plugins' issue trackers document the real-world\n // phrasings that missed curated whitelists (oh-my-openagent #2393/#5815,\n // lichter's 429 lesson): snake-cased limits, \"usage exceeded\" without\n // \"limit\", credit exhaustion without \"balance\". Qualifiers stay required\n // so \"token limit reached\" (context overflow) never matches quota.\n category: \"quota_exhausted\",\n pattern:\n /\\bquota\\b|credit balance|(?:session|usage|free usage|message|plan|monthly|weekly|daily)[ _-]?limit|usage exceeded|out of credits|insufficient credits/i,\n },\n { category: \"rate_limited\", pattern: /rate ?limit/i },\n { category: \"timeout\", pattern: /\\btimeout\\b|timed? ?out|deadline exceeded|etimedout/i },\n {\n category: \"auth_failure\",\n pattern:\n /unauthorized|invalid (api|access) key|api key (expired|invalid)|authentication|permission denied/i,\n },\n {\n category: \"invalid_model\",\n pattern: /model.{0,30}(does not exist|not found)|invalid model|unknown model|no such model/i,\n },\n {\n category: \"content_policy\",\n pattern:\n /content (management )?(policy|filter)|safety system|violates?.{0,32}policy|flagged as/i,\n },\n { category: \"overload\", pattern: /overloaded|over capacity|high demand/i },\n { category: \"user_cancelled\", pattern: /cancel+(l)?ed|aborted by (the )?user|user abort/i },\n]\n\nfunction isHttpStatus(status: number | undefined): status is number {\n return typeof status === \"number\" && Number.isInteger(status) && status >= 100 && status <= 599\n}\n\n/**\n * Upper bound on a provider-claimed retry delay. `Retry-After` and structured\n * retry metadata are untrusted provider data: a hostile or misconfigured\n * value (\"999999999\" seconds) must not convert one response into a\n * near-permanent resource blacklist. The host's own retry scheduling waits\n * out the raw header verbatim — this bound only concerns the health window\n * the supervisor derives from the classification (ADP-011 boundedness).\n */\nconst MAX_RETRY_AFTER_MS = 24 * 60 * 60 * 1000\n\nfunction boundedRetryAfterMs(retryAfterMs: number | undefined): number | undefined {\n if (retryAfterMs === undefined) {\n return undefined\n }\n return Math.min(retryAfterMs, MAX_RETRY_AFTER_MS)\n}\n\nfunction isNonNegativeInteger(value: number | undefined): value is number {\n return typeof value === \"number\" && Number.isInteger(value) && value >= 0\n}\n\nfunction lowercasedHeaders(\n headers: Readonly<Record<string, string>> | undefined,\n): ReadonlyMap<string, string> {\n const lowered = new Map<string, string>()\n if (headers === undefined) {\n return lowered\n }\n for (const [name, value] of Object.entries(headers)) {\n lowered.set(name.toLowerCase(), value)\n }\n return lowered\n}\n\n/**\n * Parses a `Retry-After` header into milliseconds. Only the integer-seconds\n * form is accepted: the HTTP-date form requires a clock, which the classifier\n * deliberately does not have (reset-time handling belongs to the capacity\n * boundary, T-05/T-15).\n */\nfunction retryAfterHeaderMs(headers: ReadonlyMap<string, string>): number | undefined {\n const raw = headers.get(\"retry-after\")\n if (raw === undefined || !/^\\d+$/.test(raw)) {\n return undefined\n }\n return Number(raw) * 1000\n}\n\n/**\n * Standard rate-limit headers (case-insensitive `x-ratelimit-remaining*`).\n * All-zero remaining capacity indicates a fixed-window quota exhaustion\n * rather than a short burst limit.\n */\nfunction fixedWindowExhausted(headers: ReadonlyMap<string, string>): boolean {\n let sawRemaining = false\n for (const [name, value] of headers) {\n if (name.startsWith(\"x-ratelimit-remaining\")) {\n sawRemaining = true\n if (value !== \"0\") {\n return false\n }\n }\n }\n return sawRemaining\n}\n\nfunction retryAdvice(category: FailureCategory, retryAfterMs: number | undefined): RetryAdvice {\n const policy = CATEGORY_POLICY[category]\n const bounded = boundedRetryAfterMs(retryAfterMs)\n if (bounded === undefined) {\n return { retryable: policy.retryable }\n }\n return { retryable: policy.retryable, retryAfterMs: bounded }\n}\n\nfunction classification(\n category: FailureCategory,\n layer: FailureClassification[\"layer\"],\n definitive: boolean,\n retryAfterMs: number | undefined,\n quotaScope?: \"provider\",\n): FailureClassification {\n const policy = CATEGORY_POLICY[category]\n return {\n category,\n layer,\n definitive,\n retry: retryAdvice(category, retryAfterMs),\n failoverEligible: policy.failoverEligible,\n ...(quotaScope !== undefined ? { quotaScope } : {}),\n }\n}\n\n/**\n * Classifies an HTTP status. 429 consults the standard rate-limit headers in\n * the same step: status plus documented standard headers are one HTTP\n * semantics layer, and the quota refinement is more specific evidence than a\n * bare 429, so it cannot weaken the ADP-008 precedence.\n */\nfunction classifyStatus(\n status: number,\n headers: ReadonlyMap<string, string>,\n): FailureCategory | undefined {\n if (status === 429) {\n return fixedWindowExhausted(headers) ? \"quota_exhausted\" : \"rate_limited\"\n }\n if (status === 401 || status === 403) {\n return \"auth_failure\"\n }\n if (status === 408) {\n return \"timeout\"\n }\n if (status === 413) {\n return \"context_overflow\"\n }\n if (status === 502 || status === 503 || status === 504) {\n return \"outage\"\n }\n return undefined\n}\n\nfunction classifyText(text: string): FailureCategory | undefined {\n const bounded = text.length > TEXT_BOUND ? text.slice(0, TEXT_BOUND) : text\n for (const { category, pattern } of TEXTUAL_PATTERNS) {\n if (pattern.test(bounded)) {\n return category\n }\n }\n return undefined\n}\n\n/**\n * Total classification of layered failure evidence. Never throws and always\n * returns a category: any evidence shape outside the documented vocabularies\n * classifies as `unknown` via the `none` layer, deferring to OpenCode's\n * native failure handling (ADP-009, ADP-012).\n */\nexport function classifyFailure(evidence: ErrorEvidence): FailureClassification {\n // Layer 0: interruption evidence. Interruptions are host decisions, not\n // provider failures; a user cancellation is final (ADP-010).\n if (evidence.interrupted === \"user\") {\n return classification(\"user_cancelled\", \"interruption\", true, undefined)\n }\n if (evidence.interrupted !== undefined) {\n return classification(\"unknown\", \"interruption\", true, undefined)\n }\n\n // Layer 1: structured OpenCode metadata.\n const structured = evidence.structured\n const structuredRetryAfterMs = isNonNegativeInteger(structured?.retryAfterMs)\n ? structured.retryAfterMs\n : undefined\n if (structured !== undefined) {\n const mapped = STRUCTURED_TYPES.get(structured.type)\n if (mapped !== undefined) {\n return classification(mapped, \"structured\", true, structuredRetryAfterMs)\n }\n // Unmapped tag: fall through so HTTP semantics can refine the category.\n }\n\n // Layer 2: HTTP status (+ standard headers for the 429 refinement).\n const headers = lowercasedHeaders(evidence.headers)\n if (isHttpStatus(evidence.status)) {\n const category = classifyStatus(evidence.status, headers)\n if (category !== undefined) {\n const retryAfterMs = structuredRetryAfterMs ?? retryAfterHeaderMs(headers)\n if (category === \"rate_limited\" && evidence.text !== undefined) {\n const bounded = evidence.text.slice(0, TEXT_BOUND)\n if (classifyText(bounded) === \"quota_exhausted\" && !RATE_LIMIT_TEXT.test(bounded)) {\n const quotaScope = SHARED_QUOTA_PATTERN.test(bounded) ? \"provider\" : undefined\n return classification(\"quota_exhausted\", \"status\", true, retryAfterMs, quotaScope)\n }\n }\n return classification(category, \"status\", true, retryAfterMs)\n }\n }\n\n // Layer 3: standard headers alone (Retry-After without a decisive status).\n const headerRetryAfterMs = retryAfterHeaderMs(headers)\n if (headerRetryAfterMs !== undefined) {\n return classification(\"rate_limited\", \"headers\", false, headerRetryAfterMs)\n }\n\n // Layer 4: documented provider-specific metadata, normalized by our own\n // adapters after validation (T-15).\n if (evidence.providerMetadata?.quotaExhausted === true) {\n return classification(\"quota_exhausted\", \"provider-metadata\", true, undefined)\n }\n if (evidence.providerMetadata?.overloaded === true) {\n return classification(\"overload\", \"provider-metadata\", true, structuredRetryAfterMs)\n }\n\n // Layer 5: bounded textual classification, always non-definitive.\n const text = evidence.text\n if (text !== undefined) {\n const category = classifyText(text)\n if (category !== undefined) {\n const quotaScope =\n category === \"quota_exhausted\" && SHARED_QUOTA_PATTERN.test(text.slice(0, TEXT_BOUND))\n ? \"provider\"\n : undefined\n return classification(category, \"textual\", false, structuredRetryAfterMs, quotaScope)\n }\n }\n\n return classification(\"unknown\", \"none\", false, undefined)\n}\n"]}
|
|
1
|
+
{"version":3,"file":"classify.js","sourceRoot":"","sources":["../../../src/health/classifier/classify.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ErrorEvidence, FailureCategory, FailureClassification, RetryAdvice } from \"./types.js\"\n\n/**\n * Text fed to the textual last-resort layer is truncated to this bound before\n * matching, so adversarial or enormous error bodies cannot dominate\n * classification cost or smuggle patterns in late bytes (ADP-008).\n */\nconst TEXT_BOUND = 512\n\n// These messages describe the subscription's shared allowance or balance,\n// unlike a generic quota error or a named model-family limit (ADP-007).\nconst SHARED_QUOTA_PATTERN =\n /\\byou['’]ve hit your (?:session|usage|weekly) limit(?=\\s*(?:$|[·.,;(:]|resets?\\b))|\\b(?:out of credits|insufficient credits|credit balance)\\b/i\n\n// A 429 whose message says the quota itself is spent (\"You exceeded your\n// current quota, please check your plan and billing details\") is quota\n// exhaustion: a burst-sized window would re-admit it every half minute. The\n// status stays the definitive evidence; text only picks the window. Messages\n// that also speak of rate limits keep the short window — Gemini words its\n// per-minute 429s as \"exceeded your current quota\" and links its rate-limit\n// docs.\nconst RATE_LIMIT_TEXT = /rate[ _-]?limit|per min/i\n\n/**\n * Retry/failover policy per category (ADP-010, ADP-011).\n *\n * - Infrastructure failures (rate limiting, quota, outage, overload, timeout)\n * are failover-eligible: horizontal recovery within the tier is exactly\n * their remedy.\n * - Permanent configuration failures (auth, invalid model, content policy)\n * and context overflow never retry and never failover: retrying the same\n * request cannot help and switching resources would reproduce the failure.\n * - User cancellation is final: no retry, no failover (ADP-010).\n * - Unknown failures defer to OpenCode's native handling.\n */\nconst CATEGORY_POLICY: Readonly<\n Record<FailureCategory, { retryable: boolean; failoverEligible: boolean }>\n> = {\n rate_limited: { retryable: true, failoverEligible: true },\n quota_exhausted: { retryable: false, failoverEligible: true },\n outage: { retryable: true, failoverEligible: true },\n overload: { retryable: true, failoverEligible: true },\n timeout: { retryable: true, failoverEligible: true },\n auth_failure: { retryable: false, failoverEligible: false },\n invalid_model: { retryable: false, failoverEligible: false },\n context_overflow: { retryable: false, failoverEligible: false },\n content_policy: { retryable: false, failoverEligible: false },\n user_cancelled: { retryable: false, failoverEligible: false },\n unknown: { retryable: false, failoverEligible: false },\n}\n\n/**\n * Error tags from the documented @opencode/protocol vocabulary that map\n * directly to a failure category. Tags absent here (`ProviderRequestError`,\n * `UnknownError`, anything unrecognized) are structured but not\n * category-specific: they never guess, and classification falls through to\n * the refining layers.\n */\nconst STRUCTURED_TYPES = new Map<string, FailureCategory>([\n [\"UnauthorizedError\", \"auth_failure\"],\n [\"ForbiddenError\", \"auth_failure\"],\n [\"ModelNotFoundError\", \"invalid_model\"],\n [\"UnsupportedModelError\", \"invalid_model\"],\n [\"ServiceUnavailableError\", \"outage\"],\n])\n\n/**\n * Bounded textual patterns, last resort only (ADP-008). Ordered most\n * specific first; the first match wins. These encode provider error-message\n * conventions documented by the research phase, not a general-purpose\n * keyword classifier.\n */\nconst TEXTUAL_PATTERNS: readonly {\n readonly category: FailureCategory\n readonly pattern: RegExp\n}[] = [\n {\n category: \"context_overflow\",\n pattern:\n /context (length|window)|maximum context|input length exceeds|prompt is too long|too many (input )?tokens|exceeds the maximum/i,\n },\n {\n // Subscription limits (\"You've hit your session/usage limit\") are quota\n // exhaustion with a provider-side reset window; the reset time in the\n // text is not parsed (the classifier has no clock) — the health layer's\n // default quota window bounds the quarantine instead.\n // Sibling fallback plugins' issue trackers document the real-world\n // phrasings that missed curated whitelists (oh-my-openagent #2393/#5815,\n // lichter's 429 lesson): snake-cased limits, \"usage exceeded\" without\n // \"limit\", credit exhaustion without \"balance\". Qualifiers stay required\n // so \"token limit reached\" (context overflow) never matches quota.\n category: \"quota_exhausted\",\n pattern:\n /\\bquota\\b|credit balance|(?:session|usage|free usage|message|plan|monthly|weekly|daily)[ _-]?limit|usage exceeded|out of credits|insufficient credits/i,\n },\n { category: \"rate_limited\", pattern: /rate ?limit/i },\n { category: \"timeout\", pattern: /\\btimeout\\b|timed? ?out|deadline exceeded|etimedout/i },\n {\n category: \"auth_failure\",\n pattern:\n /unauthorized|invalid (api|access) key|api key (expired|invalid)|authentication|permission denied/i,\n },\n {\n category: \"invalid_model\",\n pattern: /model.{0,30}(does not exist|not found)|invalid model|unknown model|no such model/i,\n },\n {\n category: \"content_policy\",\n pattern:\n /content (management )?(policy|filter)|safety system|violates?.{0,32}policy|flagged as/i,\n },\n { category: \"overload\", pattern: /overloaded|over capacity|high demand/i },\n { category: \"user_cancelled\", pattern: /cancel+(l)?ed|aborted by (the )?user|user abort/i },\n]\n\nfunction isHttpStatus(status: number | undefined): status is number {\n return typeof status === \"number\" && Number.isInteger(status) && status >= 100 && status <= 599\n}\n\n/**\n * Upper bound on a provider-claimed retry delay. `Retry-After` and structured\n * retry metadata are untrusted provider data: a hostile or misconfigured\n * value (\"999999999\" seconds) must not convert one response into a\n * near-permanent resource blacklist. The host's own retry scheduling waits\n * out the raw header verbatim — this bound only concerns the health window\n * the supervisor derives from the classification (ADP-011 boundedness).\n */\nconst MAX_RETRY_AFTER_MS = 24 * 60 * 60 * 1000\n\nfunction boundedRetryAfterMs(retryAfterMs: number | undefined): number | undefined {\n if (retryAfterMs === undefined) {\n return undefined\n }\n return Math.min(retryAfterMs, MAX_RETRY_AFTER_MS)\n}\n\nfunction isNonNegativeInteger(value: number | undefined): value is number {\n return typeof value === \"number\" && Number.isInteger(value) && value >= 0\n}\n\nfunction lowercasedHeaders(\n headers: Readonly<Record<string, string>> | undefined,\n): ReadonlyMap<string, string> {\n const lowered = new Map<string, string>()\n if (headers === undefined) {\n return lowered\n }\n for (const [name, value] of Object.entries(headers)) {\n lowered.set(name.toLowerCase(), value)\n }\n return lowered\n}\n\n/**\n * Parses a `Retry-After` header into milliseconds. Only the integer-seconds\n * form is accepted: the HTTP-date form requires a clock, which the classifier\n * deliberately does not have (reset-time handling belongs to the capacity\n * boundary, T-05/T-15).\n */\nfunction retryAfterHeaderMs(headers: ReadonlyMap<string, string>): number | undefined {\n const raw = headers.get(\"retry-after\")\n if (raw === undefined || !/^\\d+$/.test(raw)) {\n return undefined\n }\n return Number(raw) * 1000\n}\n\n/**\n * Standard rate-limit headers (case-insensitive `x-ratelimit-remaining*`).\n * All-zero remaining capacity indicates a fixed-window quota exhaustion\n * rather than a short burst limit.\n */\nfunction fixedWindowExhausted(headers: ReadonlyMap<string, string>): boolean {\n let sawRemaining = false\n for (const [name, value] of headers) {\n if (name.startsWith(\"x-ratelimit-remaining\")) {\n sawRemaining = true\n if (value !== \"0\") {\n return false\n }\n }\n }\n return sawRemaining\n}\n\nfunction retryAdvice(category: FailureCategory, retryAfterMs: number | undefined): RetryAdvice {\n const policy = CATEGORY_POLICY[category]\n const bounded = boundedRetryAfterMs(retryAfterMs)\n if (bounded === undefined) {\n return { retryable: policy.retryable }\n }\n return { retryable: policy.retryable, retryAfterMs: bounded }\n}\n\nfunction classification(\n category: FailureCategory,\n layer: FailureClassification[\"layer\"],\n definitive: boolean,\n retryAfterMs: number | undefined,\n quotaScope?: \"provider\",\n): FailureClassification {\n const policy = CATEGORY_POLICY[category]\n return {\n category,\n layer,\n definitive,\n retry: retryAdvice(category, retryAfterMs),\n failoverEligible: policy.failoverEligible,\n ...(quotaScope !== undefined ? { quotaScope } : {}),\n }\n}\n\n/**\n * Classifies an HTTP status. 429 consults the standard rate-limit headers in\n * the same step: status plus documented standard headers are one HTTP\n * semantics layer, and the quota refinement is more specific evidence than a\n * bare 429, so it cannot weaken the ADP-008 precedence.\n */\nfunction classifyStatus(\n status: number,\n headers: ReadonlyMap<string, string>,\n): FailureCategory | undefined {\n if (status === 429) {\n return fixedWindowExhausted(headers) ? \"quota_exhausted\" : \"rate_limited\"\n }\n if (status === 401 || status === 403) {\n return \"auth_failure\"\n }\n if (status === 402) {\n return \"quota_exhausted\"\n }\n if (status === 408) {\n return \"timeout\"\n }\n if (status === 413) {\n return \"context_overflow\"\n }\n if (status === 502 || status === 503 || status === 504) {\n return \"outage\"\n }\n return undefined\n}\n\nfunction classifyText(text: string): FailureCategory | undefined {\n const bounded = text.length > TEXT_BOUND ? text.slice(0, TEXT_BOUND) : text\n for (const { category, pattern } of TEXTUAL_PATTERNS) {\n if (pattern.test(bounded)) {\n return category\n }\n }\n return undefined\n}\n\n/**\n * Total classification of layered failure evidence. Never throws and always\n * returns a category: any evidence shape outside the documented vocabularies\n * classifies as `unknown` via the `none` layer, deferring to OpenCode's\n * native failure handling (ADP-009, ADP-012).\n */\nexport function classifyFailure(evidence: ErrorEvidence): FailureClassification {\n // Layer 0: interruption evidence. Interruptions are host decisions, not\n // provider failures; a user cancellation is final (ADP-010).\n if (evidence.interrupted === \"user\") {\n return classification(\"user_cancelled\", \"interruption\", true, undefined)\n }\n if (evidence.interrupted !== undefined) {\n return classification(\"unknown\", \"interruption\", true, undefined)\n }\n\n // Layer 1: structured OpenCode metadata.\n const structured = evidence.structured\n const structuredRetryAfterMs = isNonNegativeInteger(structured?.retryAfterMs)\n ? structured.retryAfterMs\n : undefined\n if (structured !== undefined) {\n const mapped = STRUCTURED_TYPES.get(structured.type)\n if (mapped !== undefined) {\n return classification(mapped, \"structured\", true, structuredRetryAfterMs)\n }\n // Unmapped tag: fall through so HTTP semantics can refine the category.\n }\n\n // Layer 2: HTTP status (+ standard headers for the 429 refinement).\n const headers = lowercasedHeaders(evidence.headers)\n if (isHttpStatus(evidence.status)) {\n const category = classifyStatus(evidence.status, headers)\n if (category !== undefined) {\n const retryAfterMs = structuredRetryAfterMs ?? retryAfterHeaderMs(headers)\n // Payment Required is the account's balance, not one model's limit:\n // every model behind the provider ID (a gateway such as OpenRouter or\n // Ofox) bills the same account, so a sibling model cannot help.\n if (evidence.status === 402) {\n return classification(category, \"status\", true, retryAfterMs, \"provider\")\n }\n if (category === \"rate_limited\" && evidence.text !== undefined) {\n const bounded = evidence.text.slice(0, TEXT_BOUND)\n if (classifyText(bounded) === \"quota_exhausted\" && !RATE_LIMIT_TEXT.test(bounded)) {\n const quotaScope = SHARED_QUOTA_PATTERN.test(bounded) ? \"provider\" : undefined\n return classification(\"quota_exhausted\", \"status\", true, retryAfterMs, quotaScope)\n }\n }\n return classification(category, \"status\", true, retryAfterMs)\n }\n }\n\n // Layer 3: standard headers alone (Retry-After without a decisive status).\n const headerRetryAfterMs = retryAfterHeaderMs(headers)\n if (headerRetryAfterMs !== undefined) {\n return classification(\"rate_limited\", \"headers\", false, headerRetryAfterMs)\n }\n\n // Layer 4: documented provider-specific metadata, normalized by our own\n // adapters after validation (T-15).\n if (evidence.providerMetadata?.quotaExhausted === true) {\n return classification(\"quota_exhausted\", \"provider-metadata\", true, undefined)\n }\n if (evidence.providerMetadata?.overloaded === true) {\n return classification(\"overload\", \"provider-metadata\", true, structuredRetryAfterMs)\n }\n\n // Layer 5: bounded textual classification, always non-definitive.\n const text = evidence.text\n if (text !== undefined) {\n const category = classifyText(text)\n if (category !== undefined) {\n const quotaScope =\n category === \"quota_exhausted\" && SHARED_QUOTA_PATTERN.test(text.slice(0, TEXT_BOUND))\n ? \"provider\"\n : undefined\n return classification(category, \"textual\", false, structuredRetryAfterMs, quotaScope)\n }\n }\n\n return classification(\"unknown\", \"none\", false, undefined)\n}\n"]}
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Plugin } from "@opencode/plugin";
|
|
2
2
|
import { isCodexHeaderName } from "./adapters/codex-capacity/index.js";
|
|
3
|
-
import { escalationTool, registerSessionHooks, subscribeSessions, tierTool, } from "./adapters/opencode/index.js";
|
|
3
|
+
import { adaptiveCommand, escalationTool, registerSessionHooks, subscribeSessions, tierTool, } from "./adapters/opencode/index.js";
|
|
4
4
|
import { PLUGIN_ID } from "./plugin-id.js";
|
|
5
5
|
import { adaptiveRpc } from "./rpc/definition.js";
|
|
6
|
-
import { adaptiveRpcHandlers } from "./rpc/handlers.js";
|
|
6
|
+
import { adaptiveRpcHandlers, patchSettings } from "./rpc/handlers.js";
|
|
7
7
|
import { Supervisor, logFailOpen } from "./supervisor/index.js";
|
|
8
8
|
export { PLUGIN_ID } from "./plugin-id.js";
|
|
9
9
|
/**
|
|
@@ -92,6 +92,18 @@ export default Plugin.define({
|
|
|
92
92
|
const rpcRegistration = await context.rpc.register(adaptiveRpc, adaptiveRpcHandlers(supervisor, emit));
|
|
93
93
|
rpcHolder.registration = rpcRegistration;
|
|
94
94
|
disposals.push({ name: "rpc", dispose: () => rpcRegistration.dispose() });
|
|
95
|
+
// The web UI never mounts plugin GUI bundles, but it lists server
|
|
96
|
+
// commands in the composer's slash menu (ADP-034).
|
|
97
|
+
const commandRegistration = await context.command.transform((editor) => {
|
|
98
|
+
editor.add(adaptiveCommand({
|
|
99
|
+
status: () => supervisor.status(),
|
|
100
|
+
setMode: (mode) => patchSettings(supervisor, emit, { mode }),
|
|
101
|
+
},
|
|
102
|
+
// The web timeline shows a synthetic message as a notice only when
|
|
103
|
+
// it has a description, and labels it with that description.
|
|
104
|
+
(sessionID, text) => context.session.synthetic({ sessionID, text, description: text, resume: false })));
|
|
105
|
+
});
|
|
106
|
+
disposals.push({ name: "command", dispose: () => commandRegistration.dispose() });
|
|
95
107
|
}
|
|
96
108
|
catch (error) {
|
|
97
109
|
// Roll back everything already registered so a failure at the tool or
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { Plugin } from \"@opencode/plugin\"\nimport { isCodexHeaderName } from \"./adapters/codex-capacity/index.js\"\nimport {\n escalationTool,\n registerSessionHooks,\n subscribeSessions,\n tierTool,\n} from \"./adapters/opencode/index.js\"\nimport { PLUGIN_ID } from \"./plugin-id.js\"\nimport { adaptiveRpc } from \"./rpc/definition.js\"\nimport { adaptiveRpcHandlers } from \"./rpc/handlers.js\"\nimport type { RpcRegistration } from \"@opencode/plugin/promise/rpc\"\nimport type { AdaptiveRpcEmit } from \"./rpc/handlers.js\"\nimport { Supervisor, logFailOpen } from \"./supervisor/index.js\"\n\nexport { PLUGIN_ID } from \"./plugin-id.js\"\n\n/**\n * Entry point wired into OpenCode via `opencode.json(c)`. Setup glues the\n * adapter touchpoints (session hooks, event subscription, storage) to the\n * supervisor and returns a cleanup that disposes every registration.\n * All supervision errors are fail-open (ADP-031): the supervisor's public\n * entry points never throw; the event pump's stream errors are logged.\n */\nexport default Plugin.define({\n id: PLUGIN_ID,\n async setup(context) {\n const supervisor = new Supervisor({\n session: context.session,\n model: context.model,\n storage: context.storage,\n clock: () => Date.now(),\n })\n const hooks = await registerSessionHooks(\n context.session,\n {\n onRetry: async (input) => {\n await supervisor.handleRetry(input)\n },\n onModelRequest: async (report) => {\n await supervisor.handleReport(report)\n },\n onHttpResponse: async (report) => {\n await supervisor.handleReport(report)\n },\n onIdle: async (report) => {\n await supervisor.handleReport(report)\n },\n onPrompt: async (report) => {\n await supervisor.handleReport(report)\n },\n },\n { capture: isCodexHeaderName },\n )\n const subscription = subscribeSessions(\n context.event,\n (report) => {\n void supervisor.handleReport(report)\n },\n (error) => {\n logFailOpen(\"events\", error)\n },\n )\n // Every disposal settles independently: a failing dispose is logged, never\n // allowed to mask the other disposals or (during rollback) the original\n // setup error. Disposals are awaited so a late rejection cannot land after\n // the host considers the plugin gone.\n const disposals: { name: string; dispose: () => void | Promise<void> }[] = []\n const settled = async (name: string, dispose: () => void | Promise<void>): Promise<void> => {\n try {\n await dispose()\n } catch (error) {\n logFailOpen(name, error)\n }\n }\n const settledAll = async (): Promise<void> => {\n for (const { name, dispose } of disposals) {\n await settled(name, dispose)\n }\n }\n for (const [index, hookRegistration] of hooks.entries()) {\n disposals.push({ name: `hook ${String(index)}`, dispose: () => hookRegistration.dispose() })\n }\n disposals.push({\n name: \"events\",\n dispose: () => {\n subscription.dispose()\n },\n })\n // The typed escalation tool (T-18, ADP-015): the agent's self-report\n // surface for stronger capability, applied at the next safe boundary.\n // The tier tool (ADP-055): the agent declares the task's tier; the\n // declaration steers the session's next admissions until cleared.\n // Typed RPC (ADP-034/036): the only channel to the control surfaces.\n // The settings-changed emitter resolves the registration lazily: handlers\n // only run after `register` resolves, so the holder is always populated.\n const rpcHolder: { registration?: RpcRegistration<typeof adaptiveRpc> } = {}\n const emit: AdaptiveRpcEmit = (...args) =>\n // The correlated tuple pairing is checked by the handler types; the\n // registration forwards it to the host emitter untouched.\n rpcHolder.registration?.events.emit(...args)\n try {\n const toolRegistration = await context.tool.transform((editor) => {\n editor.add(\n escalationTool((sessionID, reason, evidence) =>\n supervisor.noteEscalationRequest(sessionID, reason, evidence),\n ),\n )\n editor.add(tierTool((sessionID, tier) => supervisor.noteTierRequest(sessionID, tier)))\n })\n disposals.push({ name: \"tools\", dispose: () => toolRegistration.dispose() })\n const rpcRegistration = await context.rpc.register(\n adaptiveRpc,\n adaptiveRpcHandlers(supervisor, emit),\n )\n rpcHolder.registration = rpcRegistration\n disposals.push({ name: \"rpc\", dispose: () => rpcRegistration.dispose() })\n } catch (error) {\n // Roll back everything already registered so a failure at the tool or\n // rpc stage does not leak live hooks and the event pump.\n await settledAll()\n throw error\n }\n return () => settledAll()\n },\n})\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { Plugin } from \"@opencode/plugin\"\nimport { isCodexHeaderName } from \"./adapters/codex-capacity/index.js\"\nimport {\n adaptiveCommand,\n escalationTool,\n registerSessionHooks,\n subscribeSessions,\n tierTool,\n} from \"./adapters/opencode/index.js\"\nimport { PLUGIN_ID } from \"./plugin-id.js\"\nimport { adaptiveRpc } from \"./rpc/definition.js\"\nimport { adaptiveRpcHandlers, patchSettings } from \"./rpc/handlers.js\"\nimport type { RpcRegistration } from \"@opencode/plugin/promise/rpc\"\nimport type { AdaptiveRpcEmit } from \"./rpc/handlers.js\"\nimport { Supervisor, logFailOpen } from \"./supervisor/index.js\"\n\nexport { PLUGIN_ID } from \"./plugin-id.js\"\n\n/**\n * Entry point wired into OpenCode via `opencode.json(c)`. Setup glues the\n * adapter touchpoints (session hooks, event subscription, storage) to the\n * supervisor and returns a cleanup that disposes every registration.\n * All supervision errors are fail-open (ADP-031): the supervisor's public\n * entry points never throw; the event pump's stream errors are logged.\n */\nexport default Plugin.define({\n id: PLUGIN_ID,\n async setup(context) {\n const supervisor = new Supervisor({\n session: context.session,\n model: context.model,\n storage: context.storage,\n clock: () => Date.now(),\n })\n const hooks = await registerSessionHooks(\n context.session,\n {\n onRetry: async (input) => {\n await supervisor.handleRetry(input)\n },\n onModelRequest: async (report) => {\n await supervisor.handleReport(report)\n },\n onHttpResponse: async (report) => {\n await supervisor.handleReport(report)\n },\n onIdle: async (report) => {\n await supervisor.handleReport(report)\n },\n onPrompt: async (report) => {\n await supervisor.handleReport(report)\n },\n },\n { capture: isCodexHeaderName },\n )\n const subscription = subscribeSessions(\n context.event,\n (report) => {\n void supervisor.handleReport(report)\n },\n (error) => {\n logFailOpen(\"events\", error)\n },\n )\n // Every disposal settles independently: a failing dispose is logged, never\n // allowed to mask the other disposals or (during rollback) the original\n // setup error. Disposals are awaited so a late rejection cannot land after\n // the host considers the plugin gone.\n const disposals: { name: string; dispose: () => void | Promise<void> }[] = []\n const settled = async (name: string, dispose: () => void | Promise<void>): Promise<void> => {\n try {\n await dispose()\n } catch (error) {\n logFailOpen(name, error)\n }\n }\n const settledAll = async (): Promise<void> => {\n for (const { name, dispose } of disposals) {\n await settled(name, dispose)\n }\n }\n for (const [index, hookRegistration] of hooks.entries()) {\n disposals.push({ name: `hook ${String(index)}`, dispose: () => hookRegistration.dispose() })\n }\n disposals.push({\n name: \"events\",\n dispose: () => {\n subscription.dispose()\n },\n })\n // The typed escalation tool (T-18, ADP-015): the agent's self-report\n // surface for stronger capability, applied at the next safe boundary.\n // The tier tool (ADP-055): the agent declares the task's tier; the\n // declaration steers the session's next admissions until cleared.\n // Typed RPC (ADP-034/036): the only channel to the control surfaces.\n // The settings-changed emitter resolves the registration lazily: handlers\n // only run after `register` resolves, so the holder is always populated.\n const rpcHolder: { registration?: RpcRegistration<typeof adaptiveRpc> } = {}\n const emit: AdaptiveRpcEmit = (...args) =>\n // The correlated tuple pairing is checked by the handler types; the\n // registration forwards it to the host emitter untouched.\n rpcHolder.registration?.events.emit(...args)\n try {\n const toolRegistration = await context.tool.transform((editor) => {\n editor.add(\n escalationTool((sessionID, reason, evidence) =>\n supervisor.noteEscalationRequest(sessionID, reason, evidence),\n ),\n )\n editor.add(tierTool((sessionID, tier) => supervisor.noteTierRequest(sessionID, tier)))\n })\n disposals.push({ name: \"tools\", dispose: () => toolRegistration.dispose() })\n const rpcRegistration = await context.rpc.register(\n adaptiveRpc,\n adaptiveRpcHandlers(supervisor, emit),\n )\n rpcHolder.registration = rpcRegistration\n disposals.push({ name: \"rpc\", dispose: () => rpcRegistration.dispose() })\n // The web UI never mounts plugin GUI bundles, but it lists server\n // commands in the composer's slash menu (ADP-034).\n const commandRegistration = await context.command.transform((editor) => {\n editor.add(\n adaptiveCommand(\n {\n status: () => supervisor.status(),\n setMode: (mode) => patchSettings(supervisor, emit, { mode }),\n },\n // The web timeline shows a synthetic message as a notice only when\n // it has a description, and labels it with that description.\n (sessionID, text) =>\n context.session.synthetic({ sessionID, text, description: text, resume: false }),\n ),\n )\n })\n disposals.push({ name: \"command\", dispose: () => commandRegistration.dispose() })\n } catch (error) {\n // Roll back everything already registered so a failure at the tool or\n // rpc stage does not leak live hooks and the event pump.\n await settledAll()\n throw error\n }\n return () => settledAll()\n },\n})\n"]}
|
package/dist/rpc/handlers.d.ts
CHANGED
|
@@ -47,6 +47,12 @@ export interface AdaptiveRpcCallContext<Name extends MethodName> {
|
|
|
47
47
|
}
|
|
48
48
|
type HandlerErrorOf<Name extends MethodName> = Rpc.HandlerError<Methods[Name]>;
|
|
49
49
|
type HandlerOf<Name extends MethodName> = (input: Rpc.Output<Methods[Name]["input"]>, context: AdaptiveRpcCallContext<Name>) => Promise<Rpc.HandlerOutput<Methods[Name]["output"]> | HandlerErrorOf<Name>>;
|
|
50
|
+
/**
|
|
51
|
+
* One settings write for every surface (RPC and the web command): persist,
|
|
52
|
+
* then announce so open views refresh. An internal failure degrades to an
|
|
53
|
+
* error result (fail-open, ADP-031).
|
|
54
|
+
*/
|
|
55
|
+
export declare function patchSettings(source: Pick<AdaptiveRpcSource, "updateSettings">, emit: AdaptiveRpcEmit | undefined, patch: SettingsPatch): Promise<PatchResult>;
|
|
50
56
|
export declare function adaptiveRpcHandlers(source: AdaptiveRpcSource, emit?: AdaptiveRpcEmit): {
|
|
51
57
|
[Name in MethodName]: HandlerOf<Name>;
|
|
52
58
|
};
|
package/dist/rpc/handlers.js
CHANGED
|
@@ -31,29 +31,35 @@ function tierRequestSetError(input) {
|
|
|
31
31
|
}
|
|
32
32
|
return undefined;
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* One settings write for every surface (RPC and the web command): persist,
|
|
36
|
+
* then announce so open views refresh. An internal failure degrades to an
|
|
37
|
+
* error result (fail-open, ADP-031).
|
|
38
|
+
*/
|
|
39
|
+
export async function patchSettings(source, emit, patch) {
|
|
40
|
+
try {
|
|
41
|
+
const result = await source.updateSettings(patch);
|
|
42
|
+
if (result.ok && emit !== undefined) {
|
|
43
|
+
try {
|
|
44
|
+
await emit("settings-changed", { version: result.settings.version });
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
logFailOpen("rpc-emit", error);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
logFailOpen("rpc-set", error);
|
|
54
|
+
return { ok: false, errors: ["internal error"] };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
34
57
|
export function adaptiveRpcHandlers(source, emit) {
|
|
35
58
|
return {
|
|
36
59
|
"settings.get": (_input, context) => source
|
|
37
60
|
.settings()
|
|
38
61
|
.catch((error) => context.error("internal", String(error).slice(0, LOG_ERROR_LENGTH))),
|
|
39
|
-
"settings.set":
|
|
40
|
-
try {
|
|
41
|
-
const result = await source.updateSettings(patch);
|
|
42
|
-
if (result.ok && emit !== undefined) {
|
|
43
|
-
try {
|
|
44
|
-
await emit("settings-changed", { version: result.settings.version });
|
|
45
|
-
}
|
|
46
|
-
catch (error) {
|
|
47
|
-
logFailOpen("rpc-emit", error);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return result;
|
|
51
|
-
}
|
|
52
|
-
catch (error) {
|
|
53
|
-
logFailOpen("rpc-set", error);
|
|
54
|
-
return { ok: false, errors: ["internal error"] };
|
|
55
|
-
}
|
|
56
|
-
},
|
|
62
|
+
"settings.set": (patch) => patchSettings(source, emit, patch),
|
|
57
63
|
"status.get": (_input, context) => source
|
|
58
64
|
.status()
|
|
59
65
|
.catch((error) => context.error("internal", String(error).slice(0, LOG_ERROR_LENGTH))),
|
package/dist/rpc/handlers.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handlers.js","sourceRoot":"","sources":["../../src/rpc/handlers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * RPC handler binding (ADP-034/036): adapts the supervisor's state API to the\n * typed RPC methods. Handlers never throw for expected outcomes — an invalid\n * patch is a per-field `PatchResult`, and an internal failure degrades to\n * `{ ok: false }` (fail-open, ADP-031). Read methods declare internal failures\n * through the call context's error factory: a plain throw becomes a server\n * defect whose generic `rpc.internal` message hides the cause (verified on the\n * sibling @coool plugins).\n */\nimport { OPERATING_MODES } from \"../core/scheduler/types.js\"\nimport type { OperatingMode } from \"../core/scheduler/types.js\"\nimport { TIERS } from \"../core/tier-policy/types.js\"\nimport type { TierName } from \"../core/tier-policy/types.js\"\nimport { LOG_ERROR_LENGTH, logFailOpen } from \"../supervisor/log.js\"\nimport { isValidSessionID } from \"../state/session-modes/index.js\"\nimport type { PatchResult } from \"../state/settings/types.js\"\nimport type { SettingsPatch, SettingsV1 } from \"../state/settings/index.js\"\nimport type { Rpc } from \"@opencode/schema/rpc\"\nimport type { Receipt } from \"../state/receipts/types.js\"\nimport type { StatusSnapshot } from \"./status.js\"\nimport type { AdaptiveRpc } from \"./definition.js\"\n\nexport interface AdaptiveRpcSource {\n settings(): Promise<SettingsV1>\n updateSettings(patch: SettingsPatch): Promise<PatchResult>\n status(): Promise<StatusSnapshot>\n receipts(): Promise<Receipt[]>\n getSessionMode(sessionID: string): Promise<OperatingMode | null>\n setSessionMode(sessionID: string, mode: OperatingMode | null): Promise<void>\n getTierRequest(sessionID: string): Promise<TierName | null>\n noteTierRequest(sessionID: string, tier: string): boolean\n setTierRequest(sessionID: string, tier: TierName | null): Promise<void>\n}\n\nexport type SettingsChangedEmit = (name: \"settings-changed\", data: { version: number }) => unknown\n\n/**\n * Correlated emit: each call site pairs the event name with its own payload\n * shape — an invalid pairing is a type error, and the registration site can\n * forward the tuple to the host emitter untouched.\n */\nexport type AdaptiveRpcEmit = (\n ...args:\n | [\"settings-changed\", { version: number }]\n | [\"session-mode-changed\", { sessionID: string }]\n | [\"tier-request-changed\", { sessionID: string }]\n) => unknown\n\n/** A successful tier-request write; mirrors the SessionModeSetResult convention. */\nexport type TierRequestSetResult =\n { readonly ok: true } | { readonly ok: false; readonly errors: readonly string[] }\n\n/** Per-field errors for a session-mode patch, mirroring the settings contract. */\nfunction sessionModeSetError(input: {\n sessionID: string\n mode: OperatingMode | null\n}): string | undefined {\n if (!isValidSessionID(input.sessionID)) {\n return \"sessionID must be a non-empty string of at most 128 characters\"\n }\n if (input.mode !== null && !(OPERATING_MODES as readonly string[]).includes(input.mode)) {\n return `mode must be one of ${OPERATING_MODES.join(\", \")} or null`\n }\n return undefined\n}\n\n/** Same contract for a tier-request patch (ADP-055): vocabulary-gated tier. */\nfunction tierRequestSetError(input: {\n sessionID: string\n tier: string | null\n}): string | undefined {\n if (!isValidSessionID(input.sessionID)) {\n return \"sessionID must be a non-empty string of at most 128 characters\"\n }\n if (input.tier !== null && !(TIERS as readonly string[]).includes(input.tier)) {\n return `tier must be one of ${TIERS.join(\", \")} or null`\n }\n return undefined\n}\n\ntype Methods = AdaptiveRpc[\"methods\"]\ntype MethodName = Extract<keyof Methods, string>\n\n/** The subset of the host's RpcCallContext the handlers use (no plugin import). */\nexport interface AdaptiveRpcCallContext<Name extends MethodName> {\n readonly error: Rpc.ErrorFactory<Methods[Name]>\n}\n\ntype HandlerErrorOf<Name extends MethodName> = Rpc.HandlerError<Methods[Name]>\n\ntype HandlerOf<Name extends MethodName> = (\n input: Rpc.Output<Methods[Name][\"input\"]>,\n context: AdaptiveRpcCallContext<Name>,\n) => Promise<Rpc.HandlerOutput<Methods[Name][\"output\"]> | HandlerErrorOf<Name>>\n\nexport function adaptiveRpcHandlers(\n source: AdaptiveRpcSource,\n emit?: AdaptiveRpcEmit,\n): { [Name in MethodName]: HandlerOf<Name> } {\n return {\n \"settings.get\": (_input, context) =>\n source\n .settings()\n .catch((error: unknown) =>\n context.error(\"internal\", String(error).slice(0, LOG_ERROR_LENGTH)),\n ),\n \"settings.set\": async (patch) => {\n try {\n const result = await source.updateSettings(patch)\n if (result.ok && emit !== undefined) {\n try {\n await emit(\"settings-changed\", { version: result.settings.version })\n } catch (error) {\n logFailOpen(\"rpc-emit\", error)\n }\n }\n return result\n } catch (error) {\n logFailOpen(\"rpc-set\", error)\n return { ok: false, errors: [\"internal error\"] }\n }\n },\n \"status.get\": (_input, context) =>\n source\n .status()\n .catch((error: unknown) =>\n context.error(\"internal\", String(error).slice(0, LOG_ERROR_LENGTH)),\n ),\n \"receipts.list\": (_input, context) =>\n source\n .receipts()\n .catch((error: unknown) =>\n context.error(\"internal\", String(error).slice(0, LOG_ERROR_LENGTH)),\n ),\n \"session-mode.get\": (input, context) =>\n source\n .getSessionMode(input.sessionID)\n .then((mode) => ({ mode }))\n .catch((error: unknown) =>\n context.error(\"internal\", String(error).slice(0, LOG_ERROR_LENGTH)),\n ),\n \"session-mode.set\": async (input) => {\n const invalid = sessionModeSetError(input)\n if (invalid !== undefined) return { ok: false, errors: [invalid] }\n try {\n await source.setSessionMode(input.sessionID, input.mode)\n } catch (error) {\n logFailOpen(\"rpc-set\", error)\n return { ok: false, errors: [\"internal error\"] }\n }\n if (emit !== undefined) {\n try {\n await emit(\"session-mode-changed\", { sessionID: input.sessionID })\n } catch (error) {\n logFailOpen(\"rpc-emit\", error)\n }\n }\n return { ok: true }\n },\n \"tier-request.get\": (input, context) =>\n source\n .getTierRequest(input.sessionID)\n .then((tier) => ({ tier }))\n .catch((error: unknown) =>\n context.error(\"internal\", String(error).slice(0, LOG_ERROR_LENGTH)),\n ),\n \"tier-request.set\": async (input) => {\n const invalid = tierRequestSetError(input)\n if (invalid !== undefined) return { ok: false, errors: [invalid] }\n if (input.tier !== null) {\n // A non-null tier is a sticky record: noteTierRequest re-validates the\n // vocabulary (post-gate this cannot fail, so treat `false` as internal).\n if (!source.noteTierRequest(input.sessionID, input.tier)) {\n logFailOpen(\"rpc-set\", new Error(\"tier request rejected\"))\n return { ok: false, errors: [\"internal error\"] }\n }\n } else {\n try {\n await source.setTierRequest(input.sessionID, null)\n } catch (error) {\n logFailOpen(\"rpc-set\", error)\n return { ok: false, errors: [\"internal error\"] }\n }\n }\n if (emit !== undefined) {\n try {\n await emit(\"tier-request-changed\", { sessionID: input.sessionID })\n } catch (error) {\n logFailOpen(\"rpc-emit\", error)\n }\n }\n return { ok: true }\n },\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"handlers.js","sourceRoot":"","sources":["../../src/rpc/handlers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * RPC handler binding (ADP-034/036): adapts the supervisor's state API to the\n * typed RPC methods. Handlers never throw for expected outcomes — an invalid\n * patch is a per-field `PatchResult`, and an internal failure degrades to\n * `{ ok: false }` (fail-open, ADP-031). Read methods declare internal failures\n * through the call context's error factory: a plain throw becomes a server\n * defect whose generic `rpc.internal` message hides the cause (verified on the\n * sibling @coool plugins).\n */\nimport { OPERATING_MODES } from \"../core/scheduler/types.js\"\nimport type { OperatingMode } from \"../core/scheduler/types.js\"\nimport { TIERS } from \"../core/tier-policy/types.js\"\nimport type { TierName } from \"../core/tier-policy/types.js\"\nimport { LOG_ERROR_LENGTH, logFailOpen } from \"../supervisor/log.js\"\nimport { isValidSessionID } from \"../state/session-modes/index.js\"\nimport type { PatchResult } from \"../state/settings/types.js\"\nimport type { SettingsPatch, SettingsV1 } from \"../state/settings/index.js\"\nimport type { Rpc } from \"@opencode/schema/rpc\"\nimport type { Receipt } from \"../state/receipts/types.js\"\nimport type { StatusSnapshot } from \"./status.js\"\nimport type { AdaptiveRpc } from \"./definition.js\"\n\nexport interface AdaptiveRpcSource {\n settings(): Promise<SettingsV1>\n updateSettings(patch: SettingsPatch): Promise<PatchResult>\n status(): Promise<StatusSnapshot>\n receipts(): Promise<Receipt[]>\n getSessionMode(sessionID: string): Promise<OperatingMode | null>\n setSessionMode(sessionID: string, mode: OperatingMode | null): Promise<void>\n getTierRequest(sessionID: string): Promise<TierName | null>\n noteTierRequest(sessionID: string, tier: string): boolean\n setTierRequest(sessionID: string, tier: TierName | null): Promise<void>\n}\n\nexport type SettingsChangedEmit = (name: \"settings-changed\", data: { version: number }) => unknown\n\n/**\n * Correlated emit: each call site pairs the event name with its own payload\n * shape — an invalid pairing is a type error, and the registration site can\n * forward the tuple to the host emitter untouched.\n */\nexport type AdaptiveRpcEmit = (\n ...args:\n | [\"settings-changed\", { version: number }]\n | [\"session-mode-changed\", { sessionID: string }]\n | [\"tier-request-changed\", { sessionID: string }]\n) => unknown\n\n/** A successful tier-request write; mirrors the SessionModeSetResult convention. */\nexport type TierRequestSetResult =\n { readonly ok: true } | { readonly ok: false; readonly errors: readonly string[] }\n\n/** Per-field errors for a session-mode patch, mirroring the settings contract. */\nfunction sessionModeSetError(input: {\n sessionID: string\n mode: OperatingMode | null\n}): string | undefined {\n if (!isValidSessionID(input.sessionID)) {\n return \"sessionID must be a non-empty string of at most 128 characters\"\n }\n if (input.mode !== null && !(OPERATING_MODES as readonly string[]).includes(input.mode)) {\n return `mode must be one of ${OPERATING_MODES.join(\", \")} or null`\n }\n return undefined\n}\n\n/** Same contract for a tier-request patch (ADP-055): vocabulary-gated tier. */\nfunction tierRequestSetError(input: {\n sessionID: string\n tier: string | null\n}): string | undefined {\n if (!isValidSessionID(input.sessionID)) {\n return \"sessionID must be a non-empty string of at most 128 characters\"\n }\n if (input.tier !== null && !(TIERS as readonly string[]).includes(input.tier)) {\n return `tier must be one of ${TIERS.join(\", \")} or null`\n }\n return undefined\n}\n\ntype Methods = AdaptiveRpc[\"methods\"]\ntype MethodName = Extract<keyof Methods, string>\n\n/** The subset of the host's RpcCallContext the handlers use (no plugin import). */\nexport interface AdaptiveRpcCallContext<Name extends MethodName> {\n readonly error: Rpc.ErrorFactory<Methods[Name]>\n}\n\ntype HandlerErrorOf<Name extends MethodName> = Rpc.HandlerError<Methods[Name]>\n\ntype HandlerOf<Name extends MethodName> = (\n input: Rpc.Output<Methods[Name][\"input\"]>,\n context: AdaptiveRpcCallContext<Name>,\n) => Promise<Rpc.HandlerOutput<Methods[Name][\"output\"]> | HandlerErrorOf<Name>>\n\n/**\n * One settings write for every surface (RPC and the web command): persist,\n * then announce so open views refresh. An internal failure degrades to an\n * error result (fail-open, ADP-031).\n */\nexport async function patchSettings(\n source: Pick<AdaptiveRpcSource, \"updateSettings\">,\n emit: AdaptiveRpcEmit | undefined,\n patch: SettingsPatch,\n): Promise<PatchResult> {\n try {\n const result = await source.updateSettings(patch)\n if (result.ok && emit !== undefined) {\n try {\n await emit(\"settings-changed\", { version: result.settings.version })\n } catch (error) {\n logFailOpen(\"rpc-emit\", error)\n }\n }\n return result\n } catch (error) {\n logFailOpen(\"rpc-set\", error)\n return { ok: false, errors: [\"internal error\"] }\n }\n}\n\nexport function adaptiveRpcHandlers(\n source: AdaptiveRpcSource,\n emit?: AdaptiveRpcEmit,\n): { [Name in MethodName]: HandlerOf<Name> } {\n return {\n \"settings.get\": (_input, context) =>\n source\n .settings()\n .catch((error: unknown) =>\n context.error(\"internal\", String(error).slice(0, LOG_ERROR_LENGTH)),\n ),\n \"settings.set\": (patch) => patchSettings(source, emit, patch),\n \"status.get\": (_input, context) =>\n source\n .status()\n .catch((error: unknown) =>\n context.error(\"internal\", String(error).slice(0, LOG_ERROR_LENGTH)),\n ),\n \"receipts.list\": (_input, context) =>\n source\n .receipts()\n .catch((error: unknown) =>\n context.error(\"internal\", String(error).slice(0, LOG_ERROR_LENGTH)),\n ),\n \"session-mode.get\": (input, context) =>\n source\n .getSessionMode(input.sessionID)\n .then((mode) => ({ mode }))\n .catch((error: unknown) =>\n context.error(\"internal\", String(error).slice(0, LOG_ERROR_LENGTH)),\n ),\n \"session-mode.set\": async (input) => {\n const invalid = sessionModeSetError(input)\n if (invalid !== undefined) return { ok: false, errors: [invalid] }\n try {\n await source.setSessionMode(input.sessionID, input.mode)\n } catch (error) {\n logFailOpen(\"rpc-set\", error)\n return { ok: false, errors: [\"internal error\"] }\n }\n if (emit !== undefined) {\n try {\n await emit(\"session-mode-changed\", { sessionID: input.sessionID })\n } catch (error) {\n logFailOpen(\"rpc-emit\", error)\n }\n }\n return { ok: true }\n },\n \"tier-request.get\": (input, context) =>\n source\n .getTierRequest(input.sessionID)\n .then((tier) => ({ tier }))\n .catch((error: unknown) =>\n context.error(\"internal\", String(error).slice(0, LOG_ERROR_LENGTH)),\n ),\n \"tier-request.set\": async (input) => {\n const invalid = tierRequestSetError(input)\n if (invalid !== undefined) return { ok: false, errors: [invalid] }\n if (input.tier !== null) {\n // A non-null tier is a sticky record: noteTierRequest re-validates the\n // vocabulary (post-gate this cannot fail, so treat `false` as internal).\n if (!source.noteTierRequest(input.sessionID, input.tier)) {\n logFailOpen(\"rpc-set\", new Error(\"tier request rejected\"))\n return { ok: false, errors: [\"internal error\"] }\n }\n } else {\n try {\n await source.setTierRequest(input.sessionID, null)\n } catch (error) {\n logFailOpen(\"rpc-set\", error)\n return { ok: false, errors: [\"internal error\"] }\n }\n }\n if (emit !== undefined) {\n try {\n await emit(\"tier-request-changed\", { sessionID: input.sessionID })\n } catch (error) {\n logFailOpen(\"rpc-emit\", error)\n }\n }\n return { ok: true }\n },\n }\n}\n"]}
|
package/dist/ui/flow/menu.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export declare function statusSummary(status: StatusSnapshot | undefined): strin
|
|
|
11
11
|
export declare function modelsSummary(settings: SettingsV1): string;
|
|
12
12
|
export declare function mainMenuOptions(settings: SettingsV1, status: StatusSnapshot | undefined, tierRequest?: TierName | null, effectiveMode?: OperatingMode): SelectOption<MainMenuChoice>[];
|
|
13
13
|
export declare function modeOptions(current: OperatingMode): SelectOption<OperatingMode>[];
|
|
14
|
+
/** What one mode does — shared by the global and the per-session menus. */
|
|
15
|
+
export declare function modeDescription(mode: OperatingMode): string;
|
|
14
16
|
/** One per-session mode-menu pick: follow the global mode, or set an override. */
|
|
15
17
|
export type SessionModeChoice = {
|
|
16
18
|
kind: "global";
|
package/dist/ui/flow/menu.js
CHANGED
|
@@ -86,7 +86,7 @@ export function modeOptions(current) {
|
|
|
86
86
|
}));
|
|
87
87
|
}
|
|
88
88
|
/** What one mode does — shared by the global and the per-session menus. */
|
|
89
|
-
function modeDescription(mode) {
|
|
89
|
+
export function modeDescription(mode) {
|
|
90
90
|
switch (mode) {
|
|
91
91
|
case "off":
|
|
92
92
|
return "Adaptive does nothing";
|
package/dist/ui/flow/menu.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu.js","sourceRoot":"","sources":["../../../src/ui/flow/menu.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Main-menu and mode-menu view models for `/adaptive` (ADP-033): pure data\n * that the controller feeds into `ui.dialog.select`. Descriptions are dynamic\n * where state makes them more informative (health counters, tier contents).\n */\nimport { OPERATING_MODES } from \"../../core/scheduler/types.js\"\nimport type { OperatingMode } from \"../../core/scheduler/types.js\"\nimport type { TierName } from \"../../core/tier-policy/types.js\"\nimport { TIERS } from \"../../core/tier-policy/types.js\"\nimport type { StatusSnapshot } from \"../../rpc/status.js\"\nimport type { SettingsV1 } from \"../../state/settings/index.js\"\nimport type { SelectOption } from \"./options.js\"\nimport { resourceLabel } from \"./tiers.js\"\n\nexport const MAIN_MENU_ITEMS = [\"status\", \"models\", \"mode\", \"tier\", \"pin\", \"receipts\"] as const\nexport type MainMenuChoice = (typeof MAIN_MENU_ITEMS)[number]\n\n/** \"3 resources · 1 windowed\" — the status line under the menu entry. */\nexport function statusSummary(status: StatusSnapshot | undefined): string {\n if (status === undefined) return \"how every configured model is doing\"\n const total = TIERS.reduce((sum, tier) => sum + status.tiers[tier].length, 0)\n if (total === 0) return \"no models configured yet\"\n const windowed = TIERS.reduce(\n (sum, tier) => sum + status.tiers[tier].filter((entry) => entry.windowed).length,\n 0,\n )\n const windowedNote = windowed === 0 ? \"\" : ` · ${String(windowed)} windowed`\n return `${String(total)} resource${total === 1 ? \"\" : \"s\"}${windowedNote}`\n}\n\n/** \"quick: 1 · standard: 0 · advanced: 2\" — what is configured where. */\nexport function modelsSummary(settings: SettingsV1): string {\n const parts = TIERS.map((tier: TierName) => `${tier}: ${String(settings.tiers[tier].length)}`)\n return parts.join(\" · \")\n}\n\nfunction tierRequestDescription(tierRequest: TierName | null | undefined): string {\n if (tierRequest === undefined) return \"pin a tier for the current session\"\n if (tierRequest === null) return \"not pinned — the routing ladder decides\"\n return `pinned to ${tierRequest} — steers this session`\n}\n\nfunction modeMarker(\n mode: OperatingMode,\n input: { override: OperatingMode | null; effective: OperatingMode },\n): \"override\" | \"current\" | undefined {\n if (mode === input.override) return \"override\"\n if (mode === input.effective) return \"current\"\n return undefined\n}\n\nexport function mainMenuOptions(\n settings: SettingsV1,\n status: StatusSnapshot | undefined,\n tierRequest?: TierName | null,\n effectiveMode: OperatingMode = settings.mode,\n): SelectOption<MainMenuChoice>[] {\n const optionFor = (item: MainMenuChoice): SelectOption<MainMenuChoice> => {\n switch (item) {\n case \"status\":\n return {\n title: \"Status\",\n value: item,\n description: statusSummary(status),\n }\n case \"models\":\n return {\n title: \"Models\",\n value: item,\n description: modelsSummary(settings),\n }\n case \"mode\":\n return {\n title: \"Mode\",\n value: item,\n description: `currently ${effectiveMode}`,\n }\n case \"tier\":\n return {\n title: \"Tier\",\n value: item,\n description: tierRequestDescription(tierRequest),\n }\n case \"pin\":\n return {\n title: \"Pin\",\n value: item,\n description:\n settings.pin === null\n ? \"force one resource\"\n : `pinned to ${resourceLabel(settings.pin)}`,\n }\n case \"receipts\":\n return { title: \"Receipts\", value: item, description: \"why each turn ran the model it ran\" }\n }\n }\n return MAIN_MENU_ITEMS.map(optionFor)\n}\n\nexport function modeOptions(current: OperatingMode): SelectOption<OperatingMode>[] {\n return OPERATING_MODES.map((mode) => ({\n title: mode === current ? `${mode} (current)` : mode,\n value: mode,\n description: modeDescription(mode),\n }))\n}\n\n/** What one mode does — shared by the global and the per-session menus. */\nfunction modeDescription(mode: OperatingMode): string {\n switch (mode) {\n case \"off\":\n return \"Adaptive does nothing\"\n case \"observe\":\n return \"Record decisions without applying them\"\n case \"active\":\n return \"Apply routing and recovery decisions\"\n }\n}\n\n/** One per-session mode-menu pick: follow the global mode, or set an override. */\nexport type SessionModeChoice = { kind: \"global\" } | { kind: \"mode\"; mode: OperatingMode }\n\n/** Which mode the user steers inside a session: the global default or the session override. */\nexport type ModeTarget = \"global\" | \"session\"\n\n/**\n * The first select of the in-session mode flow (ADP-054): ask which mode to\n * steer before showing any mode list — the global default or this session's\n * override. Each row names the current value so the user can tell at a\n * glance which side needs changing.\n */\nexport function modeTargetOptions(\n global: OperatingMode,\n effective: OperatingMode,\n): SelectOption<ModeTarget>[] {\n return [\n {\n title: \"Global mode…\",\n description: `applies to every session without an override (now ${global})`,\n value: \"global\",\n },\n {\n title: \"This session…\",\n description: `override for this session only (now ${effective})`,\n value: \"session\",\n },\n ]\n}\n\n/**\n * The per-session mode menu (ADP-054): first \"Follow global\" (clearing the\n * override), then every mode. An existing override is marked \"(override)\" —\n * distinct from the effective mode's \"(current)\" — so the user can tell a\n * set override apart from a session merely inheriting the global mode.\n */\nexport function sessionModeOptions(input: {\n global: OperatingMode\n override: OperatingMode | null\n effective: OperatingMode\n}): SelectOption<SessionModeChoice>[] {\n const options: SelectOption<SessionModeChoice>[] = [\n {\n title: `Follow global (${input.global})`,\n description: \"clear this session's override\",\n value: { kind: \"global\" },\n },\n ]\n for (const mode of OPERATING_MODES) {\n const marker = modeMarker(mode, input)\n options.push({\n title: marker === undefined ? mode : `${mode} (${marker})`,\n value: { kind: \"mode\", mode },\n description: modeDescription(mode),\n })\n }\n return options\n}\n\n/** What one tier is for — shared by the tier editor and the per-session menu. */\nfunction tierDescription(tier: TierName): string {\n switch (tier) {\n case \"quick\":\n return \"routine edits, mechanical steps\"\n case \"standard\":\n return \"the default\"\n case \"advanced\":\n return \"multi-step design, broad refactors\"\n }\n}\n\n/** One per-session tier-menu pick: follow the routing ladder, or pin a tier. */\nexport type SessionTierChoice = { kind: \"ladder\" } | { kind: \"tier\"; tier: TierName }\n\n/**\n * The per-session tier menu (ADP-055): first \"Follow routing ladder\" (clearing\n * the request), then every tier. A pinned request is marked \"(pinned)\" so the\n * user can tell the sticky request apart from the ordinary ladder.\n */\nexport function sessionTierOptions(input: {\n pinned: TierName | null\n}): SelectOption<SessionTierChoice>[] {\n const options: SelectOption<SessionTierChoice>[] = [\n {\n title: \"Follow routing ladder\",\n description: \"clear this session's tier request\",\n value: { kind: \"ladder\" },\n },\n ]\n for (const tier of TIERS) {\n options.push({\n title: tier === input.pinned ? `${tier} (pinned)` : tier,\n value: { kind: \"tier\", tier },\n description: tierDescription(tier),\n })\n }\n return options\n}\n"]}
|
|
1
|
+
{"version":3,"file":"menu.js","sourceRoot":"","sources":["../../../src/ui/flow/menu.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Main-menu and mode-menu view models for `/adaptive` (ADP-033): pure data\n * that the controller feeds into `ui.dialog.select`. Descriptions are dynamic\n * where state makes them more informative (health counters, tier contents).\n */\nimport { OPERATING_MODES } from \"../../core/scheduler/types.js\"\nimport type { OperatingMode } from \"../../core/scheduler/types.js\"\nimport type { TierName } from \"../../core/tier-policy/types.js\"\nimport { TIERS } from \"../../core/tier-policy/types.js\"\nimport type { StatusSnapshot } from \"../../rpc/status.js\"\nimport type { SettingsV1 } from \"../../state/settings/index.js\"\nimport type { SelectOption } from \"./options.js\"\nimport { resourceLabel } from \"./tiers.js\"\n\nexport const MAIN_MENU_ITEMS = [\"status\", \"models\", \"mode\", \"tier\", \"pin\", \"receipts\"] as const\nexport type MainMenuChoice = (typeof MAIN_MENU_ITEMS)[number]\n\n/** \"3 resources · 1 windowed\" — the status line under the menu entry. */\nexport function statusSummary(status: StatusSnapshot | undefined): string {\n if (status === undefined) return \"how every configured model is doing\"\n const total = TIERS.reduce((sum, tier) => sum + status.tiers[tier].length, 0)\n if (total === 0) return \"no models configured yet\"\n const windowed = TIERS.reduce(\n (sum, tier) => sum + status.tiers[tier].filter((entry) => entry.windowed).length,\n 0,\n )\n const windowedNote = windowed === 0 ? \"\" : ` · ${String(windowed)} windowed`\n return `${String(total)} resource${total === 1 ? \"\" : \"s\"}${windowedNote}`\n}\n\n/** \"quick: 1 · standard: 0 · advanced: 2\" — what is configured where. */\nexport function modelsSummary(settings: SettingsV1): string {\n const parts = TIERS.map((tier: TierName) => `${tier}: ${String(settings.tiers[tier].length)}`)\n return parts.join(\" · \")\n}\n\nfunction tierRequestDescription(tierRequest: TierName | null | undefined): string {\n if (tierRequest === undefined) return \"pin a tier for the current session\"\n if (tierRequest === null) return \"not pinned — the routing ladder decides\"\n return `pinned to ${tierRequest} — steers this session`\n}\n\nfunction modeMarker(\n mode: OperatingMode,\n input: { override: OperatingMode | null; effective: OperatingMode },\n): \"override\" | \"current\" | undefined {\n if (mode === input.override) return \"override\"\n if (mode === input.effective) return \"current\"\n return undefined\n}\n\nexport function mainMenuOptions(\n settings: SettingsV1,\n status: StatusSnapshot | undefined,\n tierRequest?: TierName | null,\n effectiveMode: OperatingMode = settings.mode,\n): SelectOption<MainMenuChoice>[] {\n const optionFor = (item: MainMenuChoice): SelectOption<MainMenuChoice> => {\n switch (item) {\n case \"status\":\n return {\n title: \"Status\",\n value: item,\n description: statusSummary(status),\n }\n case \"models\":\n return {\n title: \"Models\",\n value: item,\n description: modelsSummary(settings),\n }\n case \"mode\":\n return {\n title: \"Mode\",\n value: item,\n description: `currently ${effectiveMode}`,\n }\n case \"tier\":\n return {\n title: \"Tier\",\n value: item,\n description: tierRequestDescription(tierRequest),\n }\n case \"pin\":\n return {\n title: \"Pin\",\n value: item,\n description:\n settings.pin === null\n ? \"force one resource\"\n : `pinned to ${resourceLabel(settings.pin)}`,\n }\n case \"receipts\":\n return { title: \"Receipts\", value: item, description: \"why each turn ran the model it ran\" }\n }\n }\n return MAIN_MENU_ITEMS.map(optionFor)\n}\n\nexport function modeOptions(current: OperatingMode): SelectOption<OperatingMode>[] {\n return OPERATING_MODES.map((mode) => ({\n title: mode === current ? `${mode} (current)` : mode,\n value: mode,\n description: modeDescription(mode),\n }))\n}\n\n/** What one mode does — shared by the global and the per-session menus. */\nexport function modeDescription(mode: OperatingMode): string {\n switch (mode) {\n case \"off\":\n return \"Adaptive does nothing\"\n case \"observe\":\n return \"Record decisions without applying them\"\n case \"active\":\n return \"Apply routing and recovery decisions\"\n }\n}\n\n/** One per-session mode-menu pick: follow the global mode, or set an override. */\nexport type SessionModeChoice = { kind: \"global\" } | { kind: \"mode\"; mode: OperatingMode }\n\n/** Which mode the user steers inside a session: the global default or the session override. */\nexport type ModeTarget = \"global\" | \"session\"\n\n/**\n * The first select of the in-session mode flow (ADP-054): ask which mode to\n * steer before showing any mode list — the global default or this session's\n * override. Each row names the current value so the user can tell at a\n * glance which side needs changing.\n */\nexport function modeTargetOptions(\n global: OperatingMode,\n effective: OperatingMode,\n): SelectOption<ModeTarget>[] {\n return [\n {\n title: \"Global mode…\",\n description: `applies to every session without an override (now ${global})`,\n value: \"global\",\n },\n {\n title: \"This session…\",\n description: `override for this session only (now ${effective})`,\n value: \"session\",\n },\n ]\n}\n\n/**\n * The per-session mode menu (ADP-054): first \"Follow global\" (clearing the\n * override), then every mode. An existing override is marked \"(override)\" —\n * distinct from the effective mode's \"(current)\" — so the user can tell a\n * set override apart from a session merely inheriting the global mode.\n */\nexport function sessionModeOptions(input: {\n global: OperatingMode\n override: OperatingMode | null\n effective: OperatingMode\n}): SelectOption<SessionModeChoice>[] {\n const options: SelectOption<SessionModeChoice>[] = [\n {\n title: `Follow global (${input.global})`,\n description: \"clear this session's override\",\n value: { kind: \"global\" },\n },\n ]\n for (const mode of OPERATING_MODES) {\n const marker = modeMarker(mode, input)\n options.push({\n title: marker === undefined ? mode : `${mode} (${marker})`,\n value: { kind: \"mode\", mode },\n description: modeDescription(mode),\n })\n }\n return options\n}\n\n/** What one tier is for — shared by the tier editor and the per-session menu. */\nfunction tierDescription(tier: TierName): string {\n switch (tier) {\n case \"quick\":\n return \"routine edits, mechanical steps\"\n case \"standard\":\n return \"the default\"\n case \"advanced\":\n return \"multi-step design, broad refactors\"\n }\n}\n\n/** One per-session tier-menu pick: follow the routing ladder, or pin a tier. */\nexport type SessionTierChoice = { kind: \"ladder\" } | { kind: \"tier\"; tier: TierName }\n\n/**\n * The per-session tier menu (ADP-055): first \"Follow routing ladder\" (clearing\n * the request), then every tier. A pinned request is marked \"(pinned)\" so the\n * user can tell the sticky request apart from the ordinary ladder.\n */\nexport function sessionTierOptions(input: {\n pinned: TierName | null\n}): SelectOption<SessionTierChoice>[] {\n const options: SelectOption<SessionTierChoice>[] = [\n {\n title: \"Follow routing ladder\",\n description: \"clear this session's tier request\",\n value: { kind: \"ladder\" },\n },\n ]\n for (const tier of TIERS) {\n options.push({\n title: tier === input.pinned ? `${tier} (pinned)` : tier,\n value: { kind: \"tier\", tier },\n description: tierDescription(tier),\n })\n }\n return options\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coool/opencode-adaptive",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "Execution supervisor plugin for OpenCode v2: capability-tier routing, structured-evidence escalation, horizontal failure recovery, and resource health tracking.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|