@oxbit/cli 0.11.0 → 0.11.2
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/agent/dist/assets/{index-T_6HaJx-.js → index-hc_7c2Gx.js} +2 -2
- package/agent/dist/index.js +598 -167
- package/package.json +1 -1
- package/runtime/dist/index.js +80 -18
- package/runtime/package.json +1 -1
- package/web/dist/assets/{index-W9bbEyD_.js → index-BW3klpDi.js} +261 -162
- package/web/dist/assets/{index-TC7_qqA1.css → index-CMDbkm04.css} +1 -1
- package/web/dist/assets/{typescript-DTI4y3t9.js → typescript-_U0BOK1e.js} +2 -2
- package/web/dist/index.html +2 -2
- package/web/dist/schemas/settings.v1.schema.json +44 -2
- package/web/dist/sdk/index.js +48 -2
package/agent/dist/index.js
CHANGED
|
@@ -7,33 +7,6 @@ import { randomBytes, createHash, randomUUID } from "node:crypto";
|
|
|
7
7
|
import { homedir } from "node:os";
|
|
8
8
|
import { spawn } from "node:child_process";
|
|
9
9
|
import { createServer } from "node:http";
|
|
10
|
-
const count = (...values) => values.find((value2) => Number.isSafeInteger(value2) && value2 > 0);
|
|
11
|
-
async function readText$1(models2, request, signal) {
|
|
12
|
-
let text = "";
|
|
13
|
-
for await (const chunk of models2.stream(request, signal)) text += chunk;
|
|
14
|
-
return text;
|
|
15
|
-
}
|
|
16
|
-
async function listModels(models2, endpoint, signal) {
|
|
17
|
-
const body = JSON.parse(await readText$1(models2, { ...endpoint, method: "GET", path: "/v1/models" }, signal));
|
|
18
|
-
if (!Array.isArray(body?.data)) throw new Error("The model list has no data array");
|
|
19
|
-
return body.data.filter((item) => typeof item?.id === "string" && item.id).slice(0, 1e3).map((item) => ({
|
|
20
|
-
id: item.id,
|
|
21
|
-
name: typeof item.display_name === "string" ? item.display_name : typeof item.name === "string" ? item.name : item.id,
|
|
22
|
-
contextWindow: count(item.max_input_tokens, item.context_length),
|
|
23
|
-
maxOutput: count(item.max_tokens, item.top_provider?.max_completion_tokens)
|
|
24
|
-
}));
|
|
25
|
-
}
|
|
26
|
-
function modelError(error) {
|
|
27
|
-
const { status: status2, body } = error ?? {};
|
|
28
|
-
if (typeof status2 !== "number") return error instanceof Error ? error : new Error(String(error));
|
|
29
|
-
let detail = typeof body === "string" ? body.trim().slice(0, 1e3) : "";
|
|
30
|
-
try {
|
|
31
|
-
const parsed = JSON.parse(detail);
|
|
32
|
-
detail = parsed?.error?.message ?? parsed?.message ?? detail;
|
|
33
|
-
} catch {
|
|
34
|
-
}
|
|
35
|
-
return new Error(`Model API error ${status2}${detail ? `: ${detail}` : ""}`);
|
|
36
|
-
}
|
|
37
10
|
async function* lines(chunks) {
|
|
38
11
|
let buffer = "";
|
|
39
12
|
for await (const chunk of chunks) {
|
|
@@ -64,10 +37,10 @@ async function* parseSSE(chunks) {
|
|
|
64
37
|
}
|
|
65
38
|
if (line.startsWith(":")) continue;
|
|
66
39
|
const colon = line.indexOf(":");
|
|
67
|
-
const
|
|
40
|
+
const field2 = colon < 0 ? line : line.slice(0, colon);
|
|
68
41
|
const value2 = colon < 0 ? "" : line.slice(colon + 1).replace(/^ /, "");
|
|
69
|
-
if (
|
|
70
|
-
else if (
|
|
42
|
+
if (field2 === "event") event = value2;
|
|
43
|
+
else if (field2 === "data") data.push(value2);
|
|
71
44
|
}
|
|
72
45
|
}
|
|
73
46
|
async function* jsonEvents(chunks) {
|
|
@@ -76,6 +49,210 @@ async function* jsonEvents(chunks) {
|
|
|
76
49
|
if (value2 && typeof value2 === "object") yield { ...event && !value2.type ? { type: event } : {}, ...value2 };
|
|
77
50
|
}
|
|
78
51
|
}
|
|
52
|
+
function replayable(items) {
|
|
53
|
+
const kept = items.filter((item) => item.type !== "reasoning" || item.encrypted_content);
|
|
54
|
+
return kept.length === items.length ? items : kept.map((item) => item.type === "reasoning" ? item : Object.fromEntries(Object.entries(item).filter(([key]) => key !== "id")));
|
|
55
|
+
}
|
|
56
|
+
function responsesBody({ model, system, turns, tools, effort }, replay2) {
|
|
57
|
+
const input2 = [];
|
|
58
|
+
for (const turn of turns) {
|
|
59
|
+
if (turn.role === "user") input2.push({ type: "message", role: "user", content: [{ type: "input_text", text: turn.text }] });
|
|
60
|
+
else if (turn.role === "tool")
|
|
61
|
+
for (const result of turn.results) input2.push({ type: "function_call_output", call_id: result.id, output: result.output });
|
|
62
|
+
else if (turn.native && replay2.includes(turn.native.api)) input2.push(...replayable(turn.native.items));
|
|
63
|
+
else {
|
|
64
|
+
if (turn.text) input2.push({ type: "message", role: "assistant", content: turn.text });
|
|
65
|
+
for (const call of turn.calls) input2.push({ type: "function_call", call_id: call.id, name: call.name, arguments: call.arguments });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
model,
|
|
70
|
+
instructions: system,
|
|
71
|
+
input: input2,
|
|
72
|
+
stream: true,
|
|
73
|
+
store: false,
|
|
74
|
+
...tools.length ? { tools: tools.map((tool) => ({ type: "function", ...tool, strict: false })) } : {},
|
|
75
|
+
...effort ? { reasoning: { effort, summary: "auto" } } : {}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const openaiResponses = {
|
|
79
|
+
request: (input2) => ({ method: "POST", path: "/v1/responses", body: responsesBody(input2, ["openai"]) }),
|
|
80
|
+
async *events(chunks) {
|
|
81
|
+
const items = [];
|
|
82
|
+
for await (const event of jsonEvents(chunks)) {
|
|
83
|
+
switch (event.type) {
|
|
84
|
+
case "response.output_text.delta":
|
|
85
|
+
if (event.delta) yield { type: "text", text: event.delta };
|
|
86
|
+
break;
|
|
87
|
+
case "response.reasoning_summary_text.delta":
|
|
88
|
+
case "response.reasoning_text.delta":
|
|
89
|
+
if (event.delta) yield { type: "reasoning", text: event.delta };
|
|
90
|
+
break;
|
|
91
|
+
case "response.reasoning_summary_part.added":
|
|
92
|
+
if (event.summary_index > 0) yield { type: "reasoning", text: "\n\n" };
|
|
93
|
+
break;
|
|
94
|
+
case "response.output_item.done":
|
|
95
|
+
items[event.output_index ?? items.length] = event.item;
|
|
96
|
+
if (event.item?.type === "function_call")
|
|
97
|
+
yield { type: "tool_call", call: { id: event.item.call_id, name: event.item.name, arguments: event.item.arguments ?? "" } };
|
|
98
|
+
break;
|
|
99
|
+
case "response.completed":
|
|
100
|
+
case "response.incomplete": {
|
|
101
|
+
const { usage, incomplete_details: incomplete } = event.response ?? {};
|
|
102
|
+
if (usage) yield { type: "usage", input: usage.input_tokens ?? 0, output: usage.output_tokens ?? 0 };
|
|
103
|
+
const native = items.filter(Boolean);
|
|
104
|
+
const reason = native.some((item) => item.type === "function_call") ? "tool_use" : event.type === "response.completed" ? "end_turn" : incomplete?.reason === "content_filter" ? "refusal" : "max_tokens";
|
|
105
|
+
yield { type: "stop", reason, native };
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
case "response.failed":
|
|
109
|
+
throw new Error(event.response?.error?.message ?? "The model response failed");
|
|
110
|
+
case "error":
|
|
111
|
+
throw new Error(event.message ?? "The model stream failed");
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
const CHATGPT_CLIENT_VERSION = "0.162.1";
|
|
117
|
+
const CHATGPT_MODELS_PATH = `/codex/models?client_version=${CHATGPT_CLIENT_VERSION}`;
|
|
118
|
+
const XHIGH = ["low", "medium", "high", "xhigh"];
|
|
119
|
+
const MAX = { contextWindow: 272e3, efforts: [...XHIGH, "max"] };
|
|
120
|
+
const CHATGPT_MODELS = [
|
|
121
|
+
{ id: "gpt-6.1-sol", name: "GPT-6.1-Sol", ...MAX },
|
|
122
|
+
{ id: "gpt-6-astra", name: "GPT-6-Astra", ...MAX },
|
|
123
|
+
{ id: "gpt-6-sol", name: "GPT-6-Sol", ...MAX },
|
|
124
|
+
{ id: "gpt-6-luna", name: "GPT-6-Luna", ...MAX },
|
|
125
|
+
{ id: "gpt-5.6-sol", name: "GPT-5.6-Sol", ...MAX },
|
|
126
|
+
{ id: "gpt-5.6-terra", name: "GPT-5.6-Terra", ...MAX },
|
|
127
|
+
{ id: "gpt-5.6-luna", name: "GPT-5.6-Luna", ...MAX },
|
|
128
|
+
{ id: "gpt-5.5", name: "GPT-5.5", contextWindow: 272e3, efforts: XHIGH }
|
|
129
|
+
];
|
|
130
|
+
const chatgptResponses = {
|
|
131
|
+
request: (input2) => ({
|
|
132
|
+
method: "POST",
|
|
133
|
+
path: "/codex/responses",
|
|
134
|
+
body: {
|
|
135
|
+
...responsesBody({ ...input2, system: input2.system || "You are a helpful assistant." }, ["openai", "chatgpt"]),
|
|
136
|
+
include: ["reasoning.encrypted_content"],
|
|
137
|
+
tool_choice: "auto"
|
|
138
|
+
}
|
|
139
|
+
}),
|
|
140
|
+
events: openaiResponses.events
|
|
141
|
+
};
|
|
142
|
+
const ORDER = ["none", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
143
|
+
const LABELS = { none: "None", minimal: "Minimal", low: "Low", medium: "Medium", high: "High", xhigh: "Extra high", max: "Max" };
|
|
144
|
+
const UNKNOWN_EFFORTS = ["low", "medium", "high"];
|
|
145
|
+
const FIVE = ["low", "medium", "high", "xhigh", "max"];
|
|
146
|
+
const NONE_TO_MAX = ["none", ...FIVE];
|
|
147
|
+
const NONE_TO_XHIGH = ["none", "low", "medium", "high", "xhigh"];
|
|
148
|
+
const OPENAI = [
|
|
149
|
+
["gpt-6-astra", FIVE],
|
|
150
|
+
["gpt-6.1-sol", FIVE],
|
|
151
|
+
["gpt-6-sol", NONE_TO_MAX],
|
|
152
|
+
["gpt-6-luna", NONE_TO_MAX],
|
|
153
|
+
["gpt-5.6", FIVE],
|
|
154
|
+
["gpt-5.5", NONE_TO_XHIGH],
|
|
155
|
+
["gpt-5.4", NONE_TO_XHIGH],
|
|
156
|
+
["gpt-5.2", NONE_TO_XHIGH],
|
|
157
|
+
["gpt-5.1", ["none", "low", "medium", "high"]],
|
|
158
|
+
["gpt-5", ["minimal", "low", "medium", "high"]],
|
|
159
|
+
["gpt-4", []],
|
|
160
|
+
["gpt-4o", []],
|
|
161
|
+
["gpt-4.1", []],
|
|
162
|
+
["gpt-4.5", []],
|
|
163
|
+
["gpt-3.5", []],
|
|
164
|
+
["chatgpt-4o", []]
|
|
165
|
+
];
|
|
166
|
+
const ANTHROPIC = [
|
|
167
|
+
["claude-fable-5-1", FIVE],
|
|
168
|
+
["claude-mythos-5-1", FIVE],
|
|
169
|
+
["claude-fable-5", FIVE],
|
|
170
|
+
["claude-mythos-5", FIVE],
|
|
171
|
+
["claude-opus-5-5", FIVE],
|
|
172
|
+
["claude-sonnet-5-5", FIVE],
|
|
173
|
+
["claude-haiku-5-5", FIVE],
|
|
174
|
+
["claude-opus-5", FIVE],
|
|
175
|
+
["claude-sonnet-5", FIVE],
|
|
176
|
+
["claude-opus-4-8", FIVE],
|
|
177
|
+
["claude-opus-4-7", FIVE],
|
|
178
|
+
["claude-mythos-preview", ["low", "medium", "high", "max"]],
|
|
179
|
+
["claude-opus-4-6", ["low", "medium", "high", "max"]],
|
|
180
|
+
["claude-sonnet-4-6", ["low", "medium", "high", "max"]],
|
|
181
|
+
["claude-opus-4-5", ["low", "medium", "high"]]
|
|
182
|
+
];
|
|
183
|
+
const CODEX_CLIENT = /* @__PURE__ */ new Set(["ultra", "persistent"]);
|
|
184
|
+
const family = (table, id2) => table.find(([prefix]) => id2 === prefix || id2.startsWith(`${prefix}-`))?.[1];
|
|
185
|
+
const rank = (level) => ORDER.indexOf(level) + 1 || ORDER.length + 1;
|
|
186
|
+
const sorted = (levels) => [...new Set(levels)].sort((a, b) => rank(a) - rank(b));
|
|
187
|
+
const effortLabel = (level) => LABELS[level] ?? level.charAt(0).toUpperCase() + level.slice(1);
|
|
188
|
+
function modelEfforts(api, model) {
|
|
189
|
+
if (model.efforts) return [...model.efforts];
|
|
190
|
+
const id2 = model.id.toLowerCase();
|
|
191
|
+
if (api === "anthropic") return [...family(ANTHROPIC, id2) ?? []];
|
|
192
|
+
if (api === "openai" || api === "chatgpt") return [...family(OPENAI, id2) ?? UNKNOWN_EFFORTS];
|
|
193
|
+
return [...UNKNOWN_EFFORTS];
|
|
194
|
+
}
|
|
195
|
+
function listedEfforts(item) {
|
|
196
|
+
const effort = item?.capabilities?.effort;
|
|
197
|
+
if (effort && typeof effort === "object")
|
|
198
|
+
return effort.supported === false ? [] : FIVE.filter((level) => effort[level]?.supported === true);
|
|
199
|
+
if (Array.isArray(item?.supported_reasoning_levels))
|
|
200
|
+
return sorted(item.supported_reasoning_levels.map((preset) => preset?.effort).filter((level) => typeof level === "string" && !!level && !CODEX_CLIENT.has(level)));
|
|
201
|
+
const reasoning = item?.reasoning;
|
|
202
|
+
if (reasoning && typeof reasoning === "object") {
|
|
203
|
+
const levels = Array.isArray(reasoning.supported_efforts) ? reasoning.supported_efforts.filter((level) => typeof level === "string" && !!level) : ORDER;
|
|
204
|
+
return sorted(reasoning.mandatory ? levels.filter((level) => level !== "none") : levels);
|
|
205
|
+
}
|
|
206
|
+
if (Array.isArray(item?.supported_parameters))
|
|
207
|
+
return item.supported_parameters.some((name) => name === "reasoning" || name === "reasoning_effort") ? void 0 : [];
|
|
208
|
+
return void 0;
|
|
209
|
+
}
|
|
210
|
+
const count = (...values) => values.find((value2) => Number.isSafeInteger(value2) && value2 > 0);
|
|
211
|
+
async function readText$1(models2, request, signal) {
|
|
212
|
+
let text = "";
|
|
213
|
+
for await (const chunk of models2.stream(request, signal)) text += chunk;
|
|
214
|
+
return text;
|
|
215
|
+
}
|
|
216
|
+
const withEfforts = (info, item) => {
|
|
217
|
+
const efforts = listedEfforts(item);
|
|
218
|
+
return efforts ? { ...info, efforts } : info;
|
|
219
|
+
};
|
|
220
|
+
async function listModels(models2, endpoint, signal) {
|
|
221
|
+
if (endpoint.api === "chatgpt") return listChatGPTModels(models2, signal);
|
|
222
|
+
const body = JSON.parse(await readText$1(models2, { ...endpoint, method: "GET", path: "/v1/models" }, signal));
|
|
223
|
+
if (!Array.isArray(body?.data)) throw new Error("The model list has no data array");
|
|
224
|
+
return body.data.filter((item) => typeof item?.id === "string" && item.id).slice(0, 1e3).map((item) => withEfforts({
|
|
225
|
+
id: item.id,
|
|
226
|
+
name: typeof item.display_name === "string" ? item.display_name : typeof item.name === "string" ? item.name : item.id,
|
|
227
|
+
contextWindow: count(item.max_input_tokens, item.context_length),
|
|
228
|
+
maxOutput: count(item.max_tokens, item.top_provider?.max_completion_tokens)
|
|
229
|
+
}, item));
|
|
230
|
+
}
|
|
231
|
+
async function listChatGPTModels(models2, signal) {
|
|
232
|
+
try {
|
|
233
|
+
const body = JSON.parse(await readText$1(models2, { api: "chatgpt", method: "GET", path: CHATGPT_MODELS_PATH }, signal));
|
|
234
|
+
const listed = (Array.isArray(body?.models) ? body.models : []).filter((item) => typeof item?.slug === "string" && item.slug && item.visibility === "list").sort((a, b) => (Number(a.priority) || 0) - (Number(b.priority) || 0)).map((item) => withEfforts({
|
|
235
|
+
id: item.slug,
|
|
236
|
+
name: typeof item.display_name === "string" && item.display_name ? item.display_name : item.slug,
|
|
237
|
+
contextWindow: count(item.context_window)
|
|
238
|
+
}, item));
|
|
239
|
+
if (listed.length) return listed;
|
|
240
|
+
} catch (error) {
|
|
241
|
+
if (signal?.aborted) throw error;
|
|
242
|
+
}
|
|
243
|
+
return CHATGPT_MODELS.map((model) => ({ ...model, ...model.efforts ? { efforts: [...model.efforts] } : {} }));
|
|
244
|
+
}
|
|
245
|
+
function modelError(error) {
|
|
246
|
+
const { status: status2, body } = error ?? {};
|
|
247
|
+
if (typeof status2 !== "number") return error instanceof Error ? error : new Error(String(error));
|
|
248
|
+
let detail = typeof body === "string" ? body.trim().slice(0, 1e3) : "";
|
|
249
|
+
try {
|
|
250
|
+
const parsed = JSON.parse(detail);
|
|
251
|
+
detail = parsed?.error?.message ?? parsed?.message ?? detail;
|
|
252
|
+
} catch {
|
|
253
|
+
}
|
|
254
|
+
return new Error(`Model API error ${status2}${detail ? `: ${detail}` : ""}`);
|
|
255
|
+
}
|
|
79
256
|
const OUTPUT_LIMIT$1 = 1e5;
|
|
80
257
|
const BASH_TIMEOUT = 12e4;
|
|
81
258
|
const BASH_MAX_TIMEOUT = 6e5;
|
|
@@ -341,6 +518,7 @@ async function runTool(call, context, tools = TOOLS, gate = {}) {
|
|
|
341
518
|
const toolCallId = call.id;
|
|
342
519
|
const update = (status2, output2) => context.update({ sessionUpdate: "tool_call_update", toolCallId, status: status2, rawOutput: output2.slice(0, 8e3) });
|
|
343
520
|
const where = locations(context.root, args);
|
|
521
|
+
const meta = tool?.meta?.(args);
|
|
344
522
|
context.update({
|
|
345
523
|
sessionUpdate: "tool_call",
|
|
346
524
|
toolCallId,
|
|
@@ -348,7 +526,8 @@ async function runTool(call, context, tools = TOOLS, gate = {}) {
|
|
|
348
526
|
kind: tool?.kind ?? "other",
|
|
349
527
|
status: "in_progress",
|
|
350
528
|
rawInput: args,
|
|
351
|
-
...where ? { locations: where } : {}
|
|
529
|
+
...where ? { locations: where } : {},
|
|
530
|
+
...meta ? { _meta: meta } : {}
|
|
352
531
|
});
|
|
353
532
|
try {
|
|
354
533
|
if (typeof parsed === "string") throw new Error(parsed);
|
|
@@ -418,13 +597,15 @@ function replay(turns, tools, update) {
|
|
|
418
597
|
}
|
|
419
598
|
const tool = tools.find((item) => item.spec.name === call.name);
|
|
420
599
|
const args = rawInput && typeof rawInput === "object" ? rawInput : {};
|
|
600
|
+
const meta = tool?.meta?.(args);
|
|
421
601
|
update({
|
|
422
602
|
sessionUpdate: "tool_call",
|
|
423
603
|
toolCallId: call.id,
|
|
424
604
|
title: (tool?.title(args) ?? call.name).slice(0, 200),
|
|
425
605
|
kind: tool?.kind ?? "other",
|
|
426
606
|
status: "in_progress",
|
|
427
|
-
rawInput
|
|
607
|
+
rawInput,
|
|
608
|
+
...meta ? { _meta: meta } : {}
|
|
428
609
|
});
|
|
429
610
|
}
|
|
430
611
|
} else
|
|
@@ -437,6 +618,56 @@ function replay(turns, tools, update) {
|
|
|
437
618
|
});
|
|
438
619
|
}
|
|
439
620
|
}
|
|
621
|
+
const sleep$1 = (ms, signal) => new Promise((resolve2) => {
|
|
622
|
+
if (signal.aborted) return resolve2(false);
|
|
623
|
+
const abort = () => {
|
|
624
|
+
clearTimeout(timer);
|
|
625
|
+
resolve2(false);
|
|
626
|
+
};
|
|
627
|
+
const timer = setTimeout(() => {
|
|
628
|
+
signal.removeEventListener("abort", abort);
|
|
629
|
+
resolve2(true);
|
|
630
|
+
}, ms);
|
|
631
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
632
|
+
});
|
|
633
|
+
const DEFAULT_RETRY = { retries: 4, baseMs: 1e3, maxMs: 3e4, sleep: sleep$1 };
|
|
634
|
+
const NETWORK = /network|connection|connect to|timed out|timeout|load failed|failed to fetch|fetch failed|offline|socket|ECONNRESET|ECONNREFUSED|ETIMEDOUT|EPIPE|EAI_AGAIN|stream ended before the response finished/i;
|
|
635
|
+
const BUSY = /overloaded|rate.?limit/i;
|
|
636
|
+
const QUOTA = /insufficient_quota|exceeded your current quota|billing/i;
|
|
637
|
+
const header = (headers, name) => {
|
|
638
|
+
if (!headers || typeof headers !== "object") return void 0;
|
|
639
|
+
if (typeof headers.get === "function") return headers.get(name) ?? void 0;
|
|
640
|
+
const entry = Object.entries(headers).find(([key]) => key.toLowerCase() === name);
|
|
641
|
+
return typeof entry?.[1] === "string" ? entry[1] : void 0;
|
|
642
|
+
};
|
|
643
|
+
function retryAfterMs(error) {
|
|
644
|
+
const value2 = error ?? {};
|
|
645
|
+
if (typeof value2.retryAfterMs === "number" && value2.retryAfterMs >= 0) return value2.retryAfterMs;
|
|
646
|
+
const ms = header(value2.headers, "retry-after-ms");
|
|
647
|
+
if (ms?.trim() && Number(ms) >= 0) return Number(ms);
|
|
648
|
+
const raw = value2.retryAfter ?? header(value2.headers, "retry-after");
|
|
649
|
+
if (typeof raw === "number") return raw >= 0 ? raw * 1e3 : void 0;
|
|
650
|
+
if (typeof raw !== "string" || !raw.trim()) return void 0;
|
|
651
|
+
const seconds = Number(raw);
|
|
652
|
+
if (Number.isFinite(seconds)) return seconds >= 0 ? seconds * 1e3 : void 0;
|
|
653
|
+
const date = Date.parse(raw);
|
|
654
|
+
return Number.isNaN(date) ? void 0 : Math.max(0, date - Date.now());
|
|
655
|
+
}
|
|
656
|
+
function isTransient(error) {
|
|
657
|
+
const { status: status2, body } = error ?? {};
|
|
658
|
+
const message2 = error instanceof Error ? error.message : String(error);
|
|
659
|
+
const text = `${typeof body === "string" ? body : ""} ${message2}`;
|
|
660
|
+
if (QUOTA.test(text)) return false;
|
|
661
|
+
if (typeof status2 === "number")
|
|
662
|
+
return status2 === 408 || status2 === 409 || status2 === 429 || status2 >= 500 && status2 !== 501 && status2 !== 505 || BUSY.test(text);
|
|
663
|
+
return BUSY.test(message2) || NETWORK.test(message2) || error instanceof TypeError && /fetch|load/i.test(message2);
|
|
664
|
+
}
|
|
665
|
+
function retryDelay(error, attempt, policy) {
|
|
666
|
+
if (attempt >= policy.retries || !isTransient(error)) return void 0;
|
|
667
|
+
const after = retryAfterMs(error);
|
|
668
|
+
if (after !== void 0) return after <= policy.maxMs ? after : void 0;
|
|
669
|
+
return Math.min(policy.maxMs, policy.baseMs * 2 ** attempt);
|
|
670
|
+
}
|
|
440
671
|
async function* abortable(source, signal) {
|
|
441
672
|
const iterator = source[Symbol.asyncIterator]();
|
|
442
673
|
const aborted = new Promise((_, reject) => {
|
|
@@ -456,7 +687,29 @@ async function* abortable(source, signal) {
|
|
|
456
687
|
});
|
|
457
688
|
}
|
|
458
689
|
}
|
|
459
|
-
async function streamStep(
|
|
690
|
+
async function streamStep(options) {
|
|
691
|
+
const policy = { ...DEFAULT_RETRY, ...options.retry };
|
|
692
|
+
for (let attempt = 0; ; attempt++) {
|
|
693
|
+
const progress = { streamed: false };
|
|
694
|
+
try {
|
|
695
|
+
return await attemptStep(options, progress);
|
|
696
|
+
} catch (error) {
|
|
697
|
+
const delay = progress.streamed ? void 0 : retryDelay(error, attempt, policy);
|
|
698
|
+
if (delay === void 0) throw modelError(error);
|
|
699
|
+
const seconds = Math.round(delay / 100) / 10;
|
|
700
|
+
options.update({ sessionUpdate: "agent_message_chunk", content: {
|
|
701
|
+
type: "text",
|
|
702
|
+
text: `
|
|
703
|
+
|
|
704
|
+
${modelError(error).message}. Retrying in ${seconds} s (${attempt + 1} of ${policy.retries}).
|
|
705
|
+
|
|
706
|
+
`
|
|
707
|
+
} });
|
|
708
|
+
if (!await policy.sleep(delay, options.signal)) return { turn: { role: "assistant", text: "", calls: [] } };
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
async function attemptStep({ adapter, models: models2, endpoint, input: input2, signal, turns, update, contextWindow }, progress) {
|
|
460
713
|
const calls = [];
|
|
461
714
|
const turn = { role: "assistant", text: "", calls };
|
|
462
715
|
const result = { turn };
|
|
@@ -464,12 +717,16 @@ async function streamStep({ adapter, models: models2, endpoint, input: input2, s
|
|
|
464
717
|
const request = adapter.request(input2);
|
|
465
718
|
for await (const event of adapter.events(abortable(models2.stream({ ...endpoint, ...request }, signal), signal))) {
|
|
466
719
|
if (event.type === "text") {
|
|
720
|
+
progress.streamed = true;
|
|
467
721
|
turn.text += event.text;
|
|
468
722
|
update({ sessionUpdate: "agent_message_chunk", content: { type: "text", text: event.text } });
|
|
469
|
-
} else if (event.type === "reasoning")
|
|
723
|
+
} else if (event.type === "reasoning") {
|
|
724
|
+
progress.streamed = true;
|
|
470
725
|
update({ sessionUpdate: "agent_thought_chunk", content: { type: "text", text: event.text } });
|
|
471
|
-
else if (event.type === "tool_call")
|
|
472
|
-
|
|
726
|
+
} else if (event.type === "tool_call") {
|
|
727
|
+
progress.streamed = true;
|
|
728
|
+
calls.push(event.call);
|
|
729
|
+
} else if (event.type === "usage") {
|
|
473
730
|
result.usage = { input: event.input, output: event.output };
|
|
474
731
|
if (contextWindow) update({ sessionUpdate: "usage_update", used: event.input + event.output, size: contextWindow });
|
|
475
732
|
} else if (event.type === "stop") {
|
|
@@ -479,80 +736,13 @@ async function streamStep({ adapter, models: models2, endpoint, input: input2, s
|
|
|
479
736
|
}
|
|
480
737
|
if (!result.stop && !signal.aborted) throw new Error("The model stream ended before the response finished");
|
|
481
738
|
} catch (error) {
|
|
482
|
-
if (!signal.aborted) throw
|
|
739
|
+
if (!signal.aborted) throw error;
|
|
483
740
|
} finally {
|
|
484
741
|
if (!result.stop) calls.length = 0;
|
|
485
742
|
if (turn.text || calls.length) turns.push(turn);
|
|
486
743
|
}
|
|
487
744
|
return result;
|
|
488
745
|
}
|
|
489
|
-
function replayable(items) {
|
|
490
|
-
const kept = items.filter((item) => item.type !== "reasoning" || item.encrypted_content);
|
|
491
|
-
return kept.length === items.length ? items : kept.map((item) => item.type === "reasoning" ? item : Object.fromEntries(Object.entries(item).filter(([key]) => key !== "id")));
|
|
492
|
-
}
|
|
493
|
-
const openaiResponses = {
|
|
494
|
-
request({ model, system, turns, tools, effort }) {
|
|
495
|
-
const input2 = [];
|
|
496
|
-
for (const turn of turns) {
|
|
497
|
-
if (turn.role === "user") input2.push({ type: "message", role: "user", content: [{ type: "input_text", text: turn.text }] });
|
|
498
|
-
else if (turn.role === "tool")
|
|
499
|
-
for (const result of turn.results) input2.push({ type: "function_call_output", call_id: result.id, output: result.output });
|
|
500
|
-
else if (turn.native?.api === "openai") input2.push(...replayable(turn.native.items));
|
|
501
|
-
else {
|
|
502
|
-
if (turn.text) input2.push({ type: "message", role: "assistant", content: turn.text });
|
|
503
|
-
for (const call of turn.calls) input2.push({ type: "function_call", call_id: call.id, name: call.name, arguments: call.arguments });
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
return {
|
|
507
|
-
method: "POST",
|
|
508
|
-
path: "/v1/responses",
|
|
509
|
-
body: {
|
|
510
|
-
model,
|
|
511
|
-
instructions: system,
|
|
512
|
-
input: input2,
|
|
513
|
-
stream: true,
|
|
514
|
-
store: false,
|
|
515
|
-
...tools.length ? { tools: tools.map((tool) => ({ type: "function", ...tool, strict: false })) } : {},
|
|
516
|
-
...effort ? { reasoning: { effort, summary: "auto" } } : {}
|
|
517
|
-
}
|
|
518
|
-
};
|
|
519
|
-
},
|
|
520
|
-
async *events(chunks) {
|
|
521
|
-
const items = [];
|
|
522
|
-
for await (const event of jsonEvents(chunks)) {
|
|
523
|
-
switch (event.type) {
|
|
524
|
-
case "response.output_text.delta":
|
|
525
|
-
if (event.delta) yield { type: "text", text: event.delta };
|
|
526
|
-
break;
|
|
527
|
-
case "response.reasoning_summary_text.delta":
|
|
528
|
-
case "response.reasoning_text.delta":
|
|
529
|
-
if (event.delta) yield { type: "reasoning", text: event.delta };
|
|
530
|
-
break;
|
|
531
|
-
case "response.reasoning_summary_part.added":
|
|
532
|
-
if (event.summary_index > 0) yield { type: "reasoning", text: "\n\n" };
|
|
533
|
-
break;
|
|
534
|
-
case "response.output_item.done":
|
|
535
|
-
items[event.output_index ?? items.length] = event.item;
|
|
536
|
-
if (event.item?.type === "function_call")
|
|
537
|
-
yield { type: "tool_call", call: { id: event.item.call_id, name: event.item.name, arguments: event.item.arguments ?? "" } };
|
|
538
|
-
break;
|
|
539
|
-
case "response.completed":
|
|
540
|
-
case "response.incomplete": {
|
|
541
|
-
const { usage, incomplete_details: incomplete } = event.response ?? {};
|
|
542
|
-
if (usage) yield { type: "usage", input: usage.input_tokens ?? 0, output: usage.output_tokens ?? 0 };
|
|
543
|
-
const native = items.filter(Boolean);
|
|
544
|
-
const reason = native.some((item) => item.type === "function_call") ? "tool_use" : event.type === "response.completed" ? "end_turn" : incomplete?.reason === "content_filter" ? "refusal" : "max_tokens";
|
|
545
|
-
yield { type: "stop", reason, native };
|
|
546
|
-
return;
|
|
547
|
-
}
|
|
548
|
-
case "response.failed":
|
|
549
|
-
throw new Error(event.response?.error?.message ?? "The model response failed");
|
|
550
|
-
case "error":
|
|
551
|
-
throw new Error(event.message ?? "The model stream failed");
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
};
|
|
556
746
|
const STOP$1 = { stop: "end_turn", tool_calls: "tool_use", function_call: "tool_use", length: "max_tokens", content_filter: "refusal" };
|
|
557
747
|
function mergeDetails(details, fragments) {
|
|
558
748
|
if (!Array.isArray(fragments)) return;
|
|
@@ -934,12 +1124,181 @@ Additional focus: ${focus}` : "");
|
|
|
934
1124
|
}
|
|
935
1125
|
}
|
|
936
1126
|
}
|
|
1127
|
+
const OXBIT_SUBAGENT_TOOL = "task";
|
|
1128
|
+
const FORWARDED_HOOKS = /* @__PURE__ */ new Set(["PreToolUse", "PostToolUse", "PostToolUseFailure", "PreCompact", "PostCompact"]);
|
|
1129
|
+
const childCallId = (spawnId, id2) => `${spawnId}/${id2}`;
|
|
1130
|
+
function childExtensions(host, { parent, childId }) {
|
|
1131
|
+
const source = host.extensions;
|
|
1132
|
+
if (!source) return {};
|
|
1133
|
+
const view = () => ({ id: parent.id, cwd: parent.cwd, model: parent.model, turns: [], meta: { mcpServers: parent.meta.mcpServers } });
|
|
1134
|
+
const runner = source.hooks;
|
|
1135
|
+
const hooks = runner && {
|
|
1136
|
+
run: async (event, input2, signal) => FORWARDED_HOOKS.has(event) ? runner.run(event, { ...input2, session_id: parent.id, agent_id: childId }, signal) : {}
|
|
1137
|
+
};
|
|
1138
|
+
return {
|
|
1139
|
+
tools: source.tools?.map((provider2) => ({ tools: () => provider2.tools(view()) })),
|
|
1140
|
+
context: source.context?.map((provider2) => ({ label: provider2.label, system: () => provider2.system(view()) })),
|
|
1141
|
+
...hooks ? { hooks } : {},
|
|
1142
|
+
...source.compactor ? { compactor: source.compactor } : {}
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
function childUpdate(host, { parent, spawnId, native }) {
|
|
1146
|
+
if (native) return (sessionId, update) => host.update(sessionId, update);
|
|
1147
|
+
return (_sessionId, update) => {
|
|
1148
|
+
if (update.sessionUpdate !== "tool_call" && update.sessionUpdate !== "tool_call_update") return;
|
|
1149
|
+
host.update(parent.id, {
|
|
1150
|
+
...update,
|
|
1151
|
+
toolCallId: childCallId(spawnId, String(update.toolCallId)),
|
|
1152
|
+
_meta: { ...update._meta, oxbit: { ...update._meta?.oxbit, subagentOf: spawnId } }
|
|
1153
|
+
});
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
1156
|
+
const forwarded = (params, { parent, spawnId }) => ({
|
|
1157
|
+
...params,
|
|
1158
|
+
..."sessionId" in params ? { sessionId: parent.id } : {},
|
|
1159
|
+
...typeof params.toolCallId === "string" ? { toolCallId: childCallId(spawnId, params.toolCallId) } : {},
|
|
1160
|
+
...typeof params.toolCall?.toolCallId === "string" ? { toolCall: { ...params.toolCall, toolCallId: childCallId(spawnId, params.toolCall.toolCallId) } } : {}
|
|
1161
|
+
});
|
|
1162
|
+
function childRequest(host, link) {
|
|
1163
|
+
const aborted = new Promise((_, reject) => {
|
|
1164
|
+
const fail = () => reject(new Error("Cancelled by the user"));
|
|
1165
|
+
if (link.signal.aborted) fail();
|
|
1166
|
+
else link.signal.addEventListener("abort", fail, { once: true });
|
|
1167
|
+
});
|
|
1168
|
+
aborted.catch(() => {
|
|
1169
|
+
});
|
|
1170
|
+
return (method, params) => {
|
|
1171
|
+
const raw = host.request(method, link.native ? params : forwarded(params, link));
|
|
1172
|
+
raw.catch(() => {
|
|
1173
|
+
});
|
|
1174
|
+
return Promise.race([raw, aborted]);
|
|
1175
|
+
};
|
|
1176
|
+
}
|
|
1177
|
+
const childHost = (host, link) => ({
|
|
1178
|
+
...host,
|
|
1179
|
+
subagents: void 0,
|
|
1180
|
+
generateTitles: false,
|
|
1181
|
+
update: childUpdate(host, link),
|
|
1182
|
+
request: childRequest(host, link),
|
|
1183
|
+
extensions: childExtensions(host, link)
|
|
1184
|
+
});
|
|
1185
|
+
const RESULT_LIMIT$1 = 4e4;
|
|
1186
|
+
const PROMPT_LIMIT = 1e5;
|
|
1187
|
+
const TASK_META_LIMIT = 4096;
|
|
1188
|
+
const CANCELLED = "Cancelled by the user";
|
|
1189
|
+
const SUBAGENT_PROMPT = [
|
|
1190
|
+
"You are a subagent. Another agent delegated the task in the user message to you.",
|
|
1191
|
+
"You cannot see that agent's conversation and cannot ask the user questions. Work until the task is done or you are blocked.",
|
|
1192
|
+
"End with a self-contained report: what you found or changed, with workspace-relative paths, and what is left open. The other agent sees only that report."
|
|
1193
|
+
].join("\n");
|
|
1194
|
+
const DESCRIPTION = `Delegate a self-contained task to a subagent. It starts with a fresh context and the same tools except ${OXBIT_SUBAGENT_TOOL}, works until done, and returns its final message as this tool's result. The call blocks until the subagent finishes. The user does not see that message, so summarize what matters. Use it for broad searches, multi-file investigations or independent changes, to keep this conversation's context small. Several ${OXBIT_SUBAGENT_TOOL} calls in one response run in parallel. Its file edits and commands go through the same user review as yours.`;
|
|
1195
|
+
const field = (args, key) => typeof args[key] === "string" ? args[key] : "";
|
|
1196
|
+
function report(child, stopReason) {
|
|
1197
|
+
const last = [...child.turns].reverse().find((turn) => turn.role === "assistant");
|
|
1198
|
+
let output = (last?.role === "assistant" ? last.text.trim() : "") || "The subagent finished without a final message.";
|
|
1199
|
+
if (stopReason !== "end_turn") output += `
|
|
1200
|
+
|
|
1201
|
+
[The subagent stopped: ${stopReason}]`;
|
|
1202
|
+
return output.length > RESULT_LIMIT$1 ? `${output.slice(0, RESULT_LIMIT$1)}
|
|
1203
|
+
[Truncated at ${RESULT_LIMIT$1} characters.]` : output;
|
|
1204
|
+
}
|
|
1205
|
+
function taskTool({ host, parent, runChild }) {
|
|
1206
|
+
const meta = (args, state, sessionId) => {
|
|
1207
|
+
const subagent = {
|
|
1208
|
+
name: field(args, "description").trim().slice(0, 200),
|
|
1209
|
+
task: field(args, "prompt").slice(0, TASK_META_LIMIT),
|
|
1210
|
+
model: parent.model,
|
|
1211
|
+
...sessionId ? { sessionId } : {},
|
|
1212
|
+
state
|
|
1213
|
+
};
|
|
1214
|
+
return { oxbit: { subagent } };
|
|
1215
|
+
};
|
|
1216
|
+
return {
|
|
1217
|
+
spec: { name: OXBIT_SUBAGENT_TOOL, description: DESCRIPTION, parameters: {
|
|
1218
|
+
type: "object",
|
|
1219
|
+
additionalProperties: false,
|
|
1220
|
+
required: ["description", "prompt"],
|
|
1221
|
+
properties: {
|
|
1222
|
+
description: { type: "string", maxLength: 200, description: "The task in 3 to 8 words" },
|
|
1223
|
+
prompt: {
|
|
1224
|
+
type: "string",
|
|
1225
|
+
minLength: 1,
|
|
1226
|
+
maxLength: PROMPT_LIMIT,
|
|
1227
|
+
description: "The full task: the goal, relevant paths and facts from this conversation, constraints, and what to report back"
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
} },
|
|
1231
|
+
kind: "other",
|
|
1232
|
+
mutates: true,
|
|
1233
|
+
parallel: true,
|
|
1234
|
+
title: (args) => `Task: ${field(args, "description").trim() || "subagent"}`,
|
|
1235
|
+
meta: (args) => meta(args, "pending"),
|
|
1236
|
+
async run(args, context) {
|
|
1237
|
+
const description = field(args, "description").trim();
|
|
1238
|
+
const prompt = field(args, "prompt");
|
|
1239
|
+
if (!description || description.length > 200) throw new Error("description must be a non-empty string of at most 200 characters");
|
|
1240
|
+
if (!prompt.trim() || prompt.length > PROMPT_LIMIT) throw new Error(`prompt must be 1 to ${PROMPT_LIMIT} characters`);
|
|
1241
|
+
if (context.signal.aborted) throw new Error(CANCELLED);
|
|
1242
|
+
const childId = crypto.randomUUID();
|
|
1243
|
+
const native = !!host.capabilities.childSessions;
|
|
1244
|
+
const sessionId = native ? childId : void 0;
|
|
1245
|
+
const abort = new AbortController();
|
|
1246
|
+
const child = {
|
|
1247
|
+
id: childId,
|
|
1248
|
+
cwd: parent.cwd,
|
|
1249
|
+
model: parent.model,
|
|
1250
|
+
effort: parent.effort,
|
|
1251
|
+
catalog: parent.catalog,
|
|
1252
|
+
turns: [{ role: "user", text: prompt }],
|
|
1253
|
+
context: [SUBAGENT_PROMPT],
|
|
1254
|
+
meta: { subagent: { parentSessionId: parent.id, toolCallId: context.toolCallId }, get mode() {
|
|
1255
|
+
return parent.meta.mode;
|
|
1256
|
+
} }
|
|
1257
|
+
};
|
|
1258
|
+
let state;
|
|
1259
|
+
const finish = (next) => {
|
|
1260
|
+
if (state) return;
|
|
1261
|
+
state = next;
|
|
1262
|
+
if (native) host.update(parent.id, { sessionUpdate: "subagent_state_update", subagentSessionId: childId, state: next });
|
|
1263
|
+
context.update({ sessionUpdate: "tool_call_update", toolCallId: context.toolCallId, _meta: meta(args, next, sessionId) });
|
|
1264
|
+
};
|
|
1265
|
+
const cancel = () => {
|
|
1266
|
+
abort.abort();
|
|
1267
|
+
finish("cancelled");
|
|
1268
|
+
};
|
|
1269
|
+
context.signal.addEventListener("abort", cancel, { once: true });
|
|
1270
|
+
try {
|
|
1271
|
+
if (native) {
|
|
1272
|
+
host.update(parent.id, { sessionUpdate: "subagent_spawned", subagentSessionId: childId, name: description, task: prompt });
|
|
1273
|
+
host.update(childId, { sessionUpdate: "user_message_chunk", content: { type: "text", text: prompt } });
|
|
1274
|
+
}
|
|
1275
|
+
context.update({ sessionUpdate: "tool_call_update", toolCallId: context.toolCallId, _meta: meta(args, "running", sessionId) });
|
|
1276
|
+
const link = { parent, childId, spawnId: context.toolCallId, native, signal: abort.signal };
|
|
1277
|
+
const { stopReason } = await runChild(childHost(host, link), child, abort.signal);
|
|
1278
|
+
if (abort.signal.aborted) throw new Error(CANCELLED);
|
|
1279
|
+
finish("completed");
|
|
1280
|
+
return report(child, stopReason);
|
|
1281
|
+
} catch (error) {
|
|
1282
|
+
if (abort.signal.aborted) {
|
|
1283
|
+
finish("cancelled");
|
|
1284
|
+
throw new Error(CANCELLED);
|
|
1285
|
+
}
|
|
1286
|
+
finish("failed");
|
|
1287
|
+
throw error;
|
|
1288
|
+
} finally {
|
|
1289
|
+
context.signal.removeEventListener("abort", cancel);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
};
|
|
1293
|
+
}
|
|
937
1294
|
const ADAPTERS = {
|
|
938
1295
|
openai: openaiResponses,
|
|
939
1296
|
"openai-compatible": chatCompletions,
|
|
940
|
-
anthropic: anthropicMessages
|
|
1297
|
+
anthropic: anthropicMessages,
|
|
1298
|
+
chatgpt: chatgptResponses
|
|
941
1299
|
};
|
|
942
1300
|
const MAX_STEPS = 64;
|
|
1301
|
+
const STOP_BLOCK_LIMIT = 10;
|
|
943
1302
|
const CONTEXT_LIMIT$1 = 1e4;
|
|
944
1303
|
const basePrompt = (root, reviewed, shell) => [
|
|
945
1304
|
`You are Oxbit Agent, a coding agent inside the Oxbit editor. You work in the workspace "${root.split("/").pop()}" through tools.`,
|
|
@@ -962,17 +1321,24 @@ class PromptTurn {
|
|
|
962
1321
|
/** Set by a hook that returns continue: false. */
|
|
963
1322
|
halt;
|
|
964
1323
|
stopHookActive = false;
|
|
965
|
-
|
|
1324
|
+
stopBlocks = 0;
|
|
1325
|
+
snapshotting;
|
|
966
1326
|
async hook(event, input2) {
|
|
967
1327
|
const runner = this.host.extensions?.hooks;
|
|
968
1328
|
if (!runner) return {};
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
1329
|
+
let outcome;
|
|
1330
|
+
try {
|
|
1331
|
+
outcome = await runner.run(event, {
|
|
1332
|
+
session_id: this.session.id,
|
|
1333
|
+
cwd: this.session.cwd,
|
|
1334
|
+
hook_event_name: event,
|
|
1335
|
+
permission_mode: CLAUDE_PERMISSION_MODES[modeOf(this.session.meta)],
|
|
1336
|
+
...input2
|
|
1337
|
+
}, this.signal);
|
|
1338
|
+
} catch (error) {
|
|
1339
|
+
if (!this.signal.aborted) this.notice(`The ${event} hooks failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1340
|
+
return {};
|
|
1341
|
+
}
|
|
976
1342
|
if (outcome.systemMessage) this.notice(outcome.systemMessage);
|
|
977
1343
|
if (outcome.continue === false) this.halt ??= outcome.stopReason || "A hook stopped the agent";
|
|
978
1344
|
return outcome;
|
|
@@ -992,6 +1358,8 @@ ${text}
|
|
|
992
1358
|
async tools() {
|
|
993
1359
|
const provided = await Promise.all((this.host.extensions?.tools ?? []).map((provider2) => provider2.tools(this.session)));
|
|
994
1360
|
const tools = [...builtinTools(this.host.capabilities)];
|
|
1361
|
+
if (this.host.subagents && !this.session.meta.subagent)
|
|
1362
|
+
tools.push(taskTool({ host: this.host, parent: this.session, runChild: (host, child, signal) => new PromptTurn(host, child, signal).run(host.models(), host.endpoint()) }));
|
|
995
1363
|
for (const tool of provided.flat()) if (!tools.some((item) => item.spec.name === tool.spec.name)) tools.push(tool);
|
|
996
1364
|
return tools;
|
|
997
1365
|
}
|
|
@@ -1018,7 +1386,7 @@ ${text}
|
|
|
1018
1386
|
let steps = 0;
|
|
1019
1387
|
while (steps++ < MAX_STEPS) {
|
|
1020
1388
|
const tools = await this.tools();
|
|
1021
|
-
this.
|
|
1389
|
+
this.snapshotting = void 0;
|
|
1022
1390
|
const result = await streamStep({
|
|
1023
1391
|
adapter,
|
|
1024
1392
|
models: models2,
|
|
@@ -1027,6 +1395,7 @@ ${text}
|
|
|
1027
1395
|
update,
|
|
1028
1396
|
turns: session.turns,
|
|
1029
1397
|
contextWindow: info?.contextWindow,
|
|
1398
|
+
retry: this.host.retry,
|
|
1030
1399
|
input: {
|
|
1031
1400
|
model: session.model,
|
|
1032
1401
|
system: prompt,
|
|
@@ -1048,11 +1417,7 @@ ${text}
|
|
|
1048
1417
|
request: (method, params) => this.host.request(method, params),
|
|
1049
1418
|
filesystem: session.cwd === normalize(this.host.root) ? this.host.filesystem : this.host.filesystemFor(session.cwd)
|
|
1050
1419
|
};
|
|
1051
|
-
const results =
|
|
1052
|
-
for (const call of calls) {
|
|
1053
|
-
const skipped = signal.aborted ? "Cancelled by the user" : this.halt;
|
|
1054
|
-
results.push(skipped ? { id: call.id, name: call.name, output: skipped, error: true } : await runTool(call, context, tools, gate));
|
|
1055
|
-
}
|
|
1420
|
+
const results = await this.runCalls(calls, context, tools, gate);
|
|
1056
1421
|
if (results.length) session.turns.push({ role: "tool", results });
|
|
1057
1422
|
if (signal.aborted) return { stopReason: "cancelled" };
|
|
1058
1423
|
try {
|
|
@@ -1075,12 +1440,42 @@ ${text}
|
|
|
1075
1440
|
}
|
|
1076
1441
|
return { stopReason: "max_turn_requests" };
|
|
1077
1442
|
}
|
|
1443
|
+
/** Runs calls in order; consecutive calls to parallel tools run together, up to the host's subagent concurrency. */
|
|
1444
|
+
async runCalls(calls, context, tools, gate) {
|
|
1445
|
+
const results = [];
|
|
1446
|
+
const one = async (index) => {
|
|
1447
|
+
const call = calls[index];
|
|
1448
|
+
const skipped = this.signal.aborted ? "Cancelled by the user" : this.halt;
|
|
1449
|
+
results[index] = skipped ? { id: call.id, name: call.name, output: skipped, error: true } : await runTool(call, context, tools, gate);
|
|
1450
|
+
};
|
|
1451
|
+
const parallel = (call) => !!tools.find((tool) => tool.spec.name === call.name)?.parallel;
|
|
1452
|
+
const limit = Math.max(1, this.host.subagents?.concurrency ?? 4);
|
|
1453
|
+
for (let index = 0; index < calls.length; ) {
|
|
1454
|
+
let end = index;
|
|
1455
|
+
while (end < calls.length && parallel(calls[end])) end++;
|
|
1456
|
+
if (end === index) {
|
|
1457
|
+
await one(index++);
|
|
1458
|
+
continue;
|
|
1459
|
+
}
|
|
1460
|
+
let next = index;
|
|
1461
|
+
const worker = async () => {
|
|
1462
|
+
while (next < end) await one(next++);
|
|
1463
|
+
};
|
|
1464
|
+
await Promise.all(Array.from({ length: Math.min(limit, end - index) }, worker));
|
|
1465
|
+
index = end;
|
|
1466
|
+
}
|
|
1467
|
+
return results;
|
|
1468
|
+
}
|
|
1078
1469
|
/** Stop hooks, then continuation providers. Returns the text of the next user message, if any. */
|
|
1079
1470
|
async afterStop() {
|
|
1080
1471
|
const last = this.session.turns.at(-1);
|
|
1081
1472
|
const outcome = await this.hook("Stop", { stop_hook_active: this.stopHookActive, last_assistant_message: last?.role === "assistant" ? last.text : "" });
|
|
1082
1473
|
if (this.halt) return void 0;
|
|
1083
1474
|
if (outcome.block) {
|
|
1475
|
+
if (++this.stopBlocks > STOP_BLOCK_LIMIT) {
|
|
1476
|
+
this.notice(`Stop hooks blocked the end of this turn ${STOP_BLOCK_LIMIT} times, so the turn ends here.`);
|
|
1477
|
+
return void 0;
|
|
1478
|
+
}
|
|
1084
1479
|
this.stopHookActive = true;
|
|
1085
1480
|
return outcome.reason || "A stop hook asked you to continue.";
|
|
1086
1481
|
}
|
|
@@ -1101,6 +1496,7 @@ ${text}
|
|
|
1101
1496
|
update: () => {
|
|
1102
1497
|
},
|
|
1103
1498
|
turns: [],
|
|
1499
|
+
retry: this.host.retry,
|
|
1104
1500
|
input: { model: this.session.model, system: prompt, turns, tools: [] }
|
|
1105
1501
|
});
|
|
1106
1502
|
if (!result.stop) throw new Error("Cancelled");
|
|
@@ -1119,10 +1515,15 @@ ${text}
|
|
|
1119
1515
|
await this.hook("PostCompact", { trigger });
|
|
1120
1516
|
return true;
|
|
1121
1517
|
}
|
|
1122
|
-
|
|
1518
|
+
/** The first mutating call of a step captures; concurrent calls wait for that capture. */
|
|
1519
|
+
snapshot(label) {
|
|
1123
1520
|
const store = this.host.extensions?.snapshots;
|
|
1124
|
-
if (!store
|
|
1125
|
-
this.
|
|
1521
|
+
if (!store) return Promise.resolve();
|
|
1522
|
+
if (this.snapshotting) return this.snapshotting.catch(() => {
|
|
1523
|
+
});
|
|
1524
|
+
return this.snapshotting = this.capture(store, label);
|
|
1525
|
+
}
|
|
1526
|
+
async capture(store, label) {
|
|
1126
1527
|
const snapshot2 = await store.capture(label, this.session.cwd);
|
|
1127
1528
|
const list2 = Array.isArray(this.session.meta.snapshots) ? this.session.meta.snapshots : [];
|
|
1128
1529
|
if (snapshot2) this.session.meta.snapshots = [...list2, { id: snapshot2, label, turn: this.session.turns.length }];
|
|
@@ -1251,13 +1652,13 @@ function contextLegend(usage) {
|
|
|
1251
1652
|
}
|
|
1252
1653
|
function contextNotes(usage) {
|
|
1253
1654
|
const model = usage.model || "no model";
|
|
1254
|
-
const
|
|
1655
|
+
const header2 = usage.window ? `Context usage for ${model}: ${formatTokens(usage.used)} of ${formatTokens(usage.window)} tokens (${formatPercent(usage.used, usage.window)}).` : `Context usage for ${model}: about ${formatTokens(usage.used)} tokens. The context window of this model is unknown.`;
|
|
1255
1656
|
const footer = [
|
|
1256
1657
|
...usage.window ? [usage.threshold === void 0 ? "Auto compaction is off." : `Auto compaction runs at ${formatTokens(usage.threshold)} tokens (${formatPercent(usage.threshold, usage.window)}).`] : [],
|
|
1257
1658
|
...usage.reported ? [`Provider count for the last request: ${formatTokens(usage.reported.input + usage.reported.output)} tokens (${formatTokens(usage.reported.input)} input, ${formatTokens(usage.reported.output)} output).`] : [],
|
|
1258
1659
|
"Category counts are estimates at 4 characters per token."
|
|
1259
1660
|
];
|
|
1260
|
-
return { header, footer };
|
|
1661
|
+
return { header: header2, footer };
|
|
1261
1662
|
}
|
|
1262
1663
|
function renderContextUsage(usage) {
|
|
1263
1664
|
const legend = contextLegend(usage);
|
|
@@ -1269,8 +1670,8 @@ function renderContextUsage(usage) {
|
|
|
1269
1670
|
const counts = legend.map((row) => formatTokens(row.tokens));
|
|
1270
1671
|
const digits = Math.max(...counts.map((count2) => count2.length));
|
|
1271
1672
|
const lines2 = legend.map((row, index) => `${row.glyph} ${row.label.padEnd(width)} ${counts[index].padStart(digits)}` + (usage.window ? ` ${formatPercent(row.tokens, usage.window).padStart(6)}` : ""));
|
|
1272
|
-
const { header, footer } = contextNotes(usage);
|
|
1273
|
-
return [
|
|
1673
|
+
const { header: header2, footer } = contextNotes(usage);
|
|
1674
|
+
return [header2, "", "```text", ...rows.length ? [...rows, ""] : [], ...lines2, "```", "", footer.join(" ")].join("\n");
|
|
1274
1675
|
}
|
|
1275
1676
|
function contextCommand(host, session) {
|
|
1276
1677
|
return {
|
|
@@ -1283,7 +1684,6 @@ function contextCommand(host, session) {
|
|
|
1283
1684
|
}
|
|
1284
1685
|
};
|
|
1285
1686
|
}
|
|
1286
|
-
const EFFORTS = ["default", "low", "medium", "high"];
|
|
1287
1687
|
const COMMAND = /^\/([\w:.-]+)(?:\s+([\s\S]*))?$/;
|
|
1288
1688
|
const id = (params, key) => {
|
|
1289
1689
|
if (typeof params[key] !== "string" || !params[key]) throw new Error(`Invalid ${key}`);
|
|
@@ -1303,7 +1703,7 @@ class Agent {
|
|
|
1303
1703
|
protocolVersion: 1,
|
|
1304
1704
|
agentCapabilities: {
|
|
1305
1705
|
loadSession: saved,
|
|
1306
|
-
sessionCapabilities: { fork: {}, ...saved ? { list: {} } : {} },
|
|
1706
|
+
sessionCapabilities: { fork: {}, ...saved ? { list: {} } : {}, ...this.host.subagents ? { subagents: {} } : {} },
|
|
1307
1707
|
promptCapabilities: { embeddedContext: true },
|
|
1308
1708
|
...this.host.capabilities.mcp ? { mcpCapabilities: this.host.capabilities.mcp } : {}
|
|
1309
1709
|
},
|
|
@@ -1347,6 +1747,12 @@ class Agent {
|
|
|
1347
1747
|
if (!session) throw new Error("Unknown ACP session");
|
|
1348
1748
|
return session;
|
|
1349
1749
|
}
|
|
1750
|
+
/** The session model's reasoning levels; empty when the model has no reasoning control. */
|
|
1751
|
+
efforts(session) {
|
|
1752
|
+
const model = session.catalog.find((item) => item.id === session.model) ?? { id: session.model, name: session.model };
|
|
1753
|
+
return modelEfforts(this.host.endpoint().api, model);
|
|
1754
|
+
}
|
|
1755
|
+
/** A model without reasoning levels gets no Reasoning option. */
|
|
1350
1756
|
options(session) {
|
|
1351
1757
|
const models2 = {
|
|
1352
1758
|
id: "model",
|
|
@@ -1356,13 +1762,14 @@ class Agent {
|
|
|
1356
1762
|
currentValue: session.model,
|
|
1357
1763
|
options: session.catalog.map((model) => ({ value: model.id, name: model.name }))
|
|
1358
1764
|
};
|
|
1765
|
+
const efforts = this.efforts(session);
|
|
1359
1766
|
const reasoning = {
|
|
1360
1767
|
id: "reasoning",
|
|
1361
1768
|
name: "Reasoning",
|
|
1362
1769
|
category: "thought_level",
|
|
1363
1770
|
type: "select",
|
|
1364
1771
|
currentValue: session.effort,
|
|
1365
|
-
options:
|
|
1772
|
+
options: [{ value: "default", name: "Model default" }, ...efforts.map((value2) => ({ value: value2, name: effortLabel(value2) }))]
|
|
1366
1773
|
};
|
|
1367
1774
|
const mode = {
|
|
1368
1775
|
id: "mode",
|
|
@@ -1372,7 +1779,7 @@ class Agent {
|
|
|
1372
1779
|
currentValue: modeOf(session.meta),
|
|
1373
1780
|
options: MODES.map(({ id: id2, name }) => ({ value: id2, name }))
|
|
1374
1781
|
};
|
|
1375
|
-
return [...session.catalog.length ? [models2] : [], reasoning, mode, ...this.host.configOptions?.(session) ?? []];
|
|
1782
|
+
return [...session.catalog.length ? [models2] : [], ...efforts.length ? [reasoning] : [], mode, ...this.host.configOptions?.(session) ?? []];
|
|
1376
1783
|
}
|
|
1377
1784
|
state(session) {
|
|
1378
1785
|
return {
|
|
@@ -1392,7 +1799,7 @@ class Agent {
|
|
|
1392
1799
|
if (cwd !== normalize(this.host.root) && !this.host.filesystemFor) throw new Error(`This agent serves ${this.host.root} only`);
|
|
1393
1800
|
return cwd;
|
|
1394
1801
|
}
|
|
1395
|
-
/** The host catalog or `
|
|
1802
|
+
/** The host catalog or `listModels`, with `model` first when the list lacks it. */
|
|
1396
1803
|
async models(model) {
|
|
1397
1804
|
const listed = this.host.catalog ? Promise.resolve().then(() => this.host.catalog()) : this.catalog ??= listModels(this.service(), this.host.endpoint());
|
|
1398
1805
|
const catalog = await listed.catch(() => {
|
|
@@ -1483,10 +1890,12 @@ class Agent {
|
|
|
1483
1890
|
void this.end(session, "clear");
|
|
1484
1891
|
}
|
|
1485
1892
|
}
|
|
1893
|
+
/** A reasoning level the new model lacks falls back to the model default. */
|
|
1486
1894
|
setModel(sessionId, modelId) {
|
|
1487
1895
|
const session = this.session(sessionId);
|
|
1488
1896
|
if (!session.catalog.some((model) => model.id === modelId)) throw new Error("This model is not in the model list");
|
|
1489
1897
|
session.model = modelId;
|
|
1898
|
+
if (!this.efforts(session).includes(session.effort)) session.effort = "default";
|
|
1490
1899
|
this.host.saveModel?.(modelId);
|
|
1491
1900
|
return { configOptions: this.options(session) };
|
|
1492
1901
|
}
|
|
@@ -1506,7 +1915,8 @@ class Agent {
|
|
|
1506
1915
|
return { configOptions: this.options(session) };
|
|
1507
1916
|
}
|
|
1508
1917
|
if (configId !== "reasoning" && await this.host.setConfigOption?.(session, configId, value2)) return { configOptions: this.options(session) };
|
|
1509
|
-
if (configId !== "reasoning"
|
|
1918
|
+
if (configId !== "reasoning") throw new Error("Unknown agent setting");
|
|
1919
|
+
if (value2 !== "default" && !this.efforts(session).includes(value2)) throw new Error("This model does not support that reasoning level");
|
|
1510
1920
|
session.effort = value2;
|
|
1511
1921
|
return { configOptions: this.options(session) };
|
|
1512
1922
|
}
|
|
@@ -1559,8 +1969,11 @@ class Agent {
|
|
|
1559
1969
|
const endpoint = this.host.endpoint();
|
|
1560
1970
|
let text = promptText(blocks);
|
|
1561
1971
|
const submit = await turn.hook("UserPromptSubmit", { prompt: text });
|
|
1562
|
-
if (submit.block) throw new Error(submit.reason || "A hook blocked this prompt");
|
|
1563
1972
|
if (turn.halt) return turn.halted();
|
|
1973
|
+
if (submit.block) {
|
|
1974
|
+
turn.notice(submit.reason || "A hook blocked this prompt");
|
|
1975
|
+
return { stopReason: "end_turn" };
|
|
1976
|
+
}
|
|
1564
1977
|
if (submit.additionalContext) text += `
|
|
1565
1978
|
|
|
1566
1979
|
${submit.additionalContext.slice(0, CONTEXT_LIMIT$1)}`;
|
|
@@ -1615,7 +2028,11 @@ ${submit.additionalContext.slice(0, CONTEXT_LIMIT$1)}`;
|
|
|
1615
2028
|
const key = JSON.stringify(available);
|
|
1616
2029
|
if (key === (session.commands ?? "[]")) return;
|
|
1617
2030
|
session.commands = key;
|
|
1618
|
-
|
|
2031
|
+
try {
|
|
2032
|
+
this.host.update(session.id, { sessionUpdate: "available_commands_update", availableCommands: available });
|
|
2033
|
+
} catch {
|
|
2034
|
+
session.commands = void 0;
|
|
2035
|
+
}
|
|
1619
2036
|
}
|
|
1620
2037
|
}
|
|
1621
2038
|
const CLAUDE_TOOL_NAMES = {
|
|
@@ -1852,8 +2269,8 @@ function createHookRunner(options) {
|
|
|
1852
2269
|
if (sources.some((source) => isObject$1(source.config) && source.config.disableAllHooks === true)) return {};
|
|
1853
2270
|
const tool = TOOL_EVENTS.has(event) && typeof input2.tool_name === "string" ? input2.tool_name : void 0;
|
|
1854
2271
|
const names = tool ? [tool, ...CLAUDE_TOOL_NAMES[tool] ?? []] : void 0;
|
|
1855
|
-
const
|
|
1856
|
-
const values = names ?? (
|
|
2272
|
+
const field2 = MATCH_FIELD[event];
|
|
2273
|
+
const values = names ?? (field2 && typeof input2[field2] === "string" ? [input2[field2]] : void 0);
|
|
1857
2274
|
const selected = /* @__PURE__ */ new Map();
|
|
1858
2275
|
for (const handler2 of sources.flatMap(handlersOf)) {
|
|
1859
2276
|
if (handler2.event !== event || values && !matchesHook(handler2.matcher, values) || options.skip?.(handler2)) continue;
|
|
@@ -3204,11 +3621,11 @@ function headerParams(schema) {
|
|
|
3204
3621
|
const walk = (node, path2) => {
|
|
3205
3622
|
for (const [key, property] of Object.entries(node?.properties ?? {})) {
|
|
3206
3623
|
if (!property || typeof property !== "object") continue;
|
|
3207
|
-
const
|
|
3208
|
-
if (
|
|
3209
|
-
if (typeof
|
|
3210
|
-
seen.add(
|
|
3211
|
-
found.push({ path: [...path2, key], header });
|
|
3624
|
+
const header2 = property["x-mcp-header"];
|
|
3625
|
+
if (header2 !== void 0) {
|
|
3626
|
+
if (typeof header2 !== "string" || !HEADER_TOKEN.test(header2) || !PRIMITIVES.has(property.type) || seen.has(header2.toLowerCase())) return false;
|
|
3627
|
+
seen.add(header2.toLowerCase());
|
|
3628
|
+
found.push({ path: [...path2, key], header: header2 });
|
|
3212
3629
|
}
|
|
3213
3630
|
if (property.type === "object" && !walk(property, [...path2, key])) return false;
|
|
3214
3631
|
}
|
|
@@ -3218,11 +3635,11 @@ function headerParams(schema) {
|
|
|
3218
3635
|
}
|
|
3219
3636
|
function paramHeaders(params, args) {
|
|
3220
3637
|
const headers = {};
|
|
3221
|
-
for (const { path: path2, header } of params) {
|
|
3638
|
+
for (const { path: path2, header: header2 } of params) {
|
|
3222
3639
|
let value2 = args;
|
|
3223
3640
|
for (const key of path2) value2 = value2 && typeof value2 === "object" ? value2[key] : void 0;
|
|
3224
|
-
if (typeof value2 === "string") headers[`Mcp-Param-${
|
|
3225
|
-
else if (typeof value2 === "number" || typeof value2 === "boolean") headers[`Mcp-Param-${
|
|
3641
|
+
if (typeof value2 === "string") headers[`Mcp-Param-${header2}`] = headerValue(value2);
|
|
3642
|
+
else if (typeof value2 === "number" || typeof value2 === "boolean") headers[`Mcp-Param-${header2}`] = String(value2);
|
|
3226
3643
|
}
|
|
3227
3644
|
return headers;
|
|
3228
3645
|
}
|
|
@@ -3556,7 +3973,9 @@ function httpModels(options) {
|
|
|
3556
3973
|
}
|
|
3557
3974
|
if (!response.ok) {
|
|
3558
3975
|
const body = await bodyText(response);
|
|
3559
|
-
|
|
3976
|
+
const error = httpError(response.status, options.describe ? options.describe(response.status, body) : body);
|
|
3977
|
+
const after = retryAfterMs({ headers: response.headers });
|
|
3978
|
+
throw after === void 0 ? error : Object.assign(error, { retryAfterMs: after });
|
|
3560
3979
|
}
|
|
3561
3980
|
yield* textChunks(response.body);
|
|
3562
3981
|
},
|
|
@@ -3667,12 +4086,12 @@ const json = (part) => {
|
|
|
3667
4086
|
async function verifyIdToken(token, check) {
|
|
3668
4087
|
const parts = token.split(".");
|
|
3669
4088
|
if (parts.length !== 3) throw new Error("The ID token is malformed");
|
|
3670
|
-
const
|
|
4089
|
+
const header2 = json(parts[0]);
|
|
3671
4090
|
const claims = json(parts[1]);
|
|
3672
|
-
if (
|
|
4091
|
+
if (header2?.alg !== "RS256") throw new Error(`The ID token uses an unsupported algorithm: ${header2?.alg}`);
|
|
3673
4092
|
const signed = new TextEncoder().encode(`${parts[0]}.${parts[1]}`);
|
|
3674
4093
|
const signature = fromBase64url(parts[2]);
|
|
3675
|
-
const matches = (keys) => keys.filter((key) => key.kty === "RSA" && (!key.use || key.use === "sig") && (!key.alg || key.alg === "RS256") && (
|
|
4094
|
+
const matches = (keys) => keys.filter((key) => key.kty === "RSA" && (!key.use || key.use === "sig") && (!key.alg || key.alg === "RS256") && (header2.kid === void 0 || key.kid === header2.kid));
|
|
3676
4095
|
let candidates = matches(await check.jwks.keys(false));
|
|
3677
4096
|
if (!candidates.length) candidates = matches(await check.jwks.keys(true));
|
|
3678
4097
|
if (!candidates.length) throw new Error("No signing key matches the ID token");
|
|
@@ -4011,11 +4430,15 @@ const providerSpec = (id2) => PROVIDERS.find((spec) => spec.id === id2);
|
|
|
4011
4430
|
const apiKeySlot = (provider2, baseUrl) => provider2 === "openai-compatible" ? `apikey:openai-compatible ${new URL(baseUrl ?? "").origin}` : `apikey:${provider2}`;
|
|
4012
4431
|
function planModels(body) {
|
|
4013
4432
|
if (!Array.isArray(body?.models)) throw new Error("The ChatGPT model list has no models array");
|
|
4014
|
-
return body.models.filter((model) => typeof model?.slug === "string" && model.slug && model.visibility === "list" && model.supported_in_api !== false).map((model) =>
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4433
|
+
return body.models.filter((model) => typeof model?.slug === "string" && model.slug && model.visibility === "list" && model.supported_in_api !== false).map((model) => {
|
|
4434
|
+
const efforts = listedEfforts(model);
|
|
4435
|
+
return {
|
|
4436
|
+
id: model.slug,
|
|
4437
|
+
name: typeof model.display_name === "string" ? model.display_name : model.slug,
|
|
4438
|
+
...Number.isSafeInteger(model.context_window) && model.context_window > 0 ? { contextWindow: model.context_window } : {},
|
|
4439
|
+
...efforts ? { efforts } : {}
|
|
4440
|
+
};
|
|
4441
|
+
});
|
|
4019
4442
|
}
|
|
4020
4443
|
function provider(selection, host) {
|
|
4021
4444
|
const spec = providerSpec(selection.provider);
|
|
@@ -4373,7 +4796,7 @@ class FileCatalogCache {
|
|
|
4373
4796
|
}
|
|
4374
4797
|
}
|
|
4375
4798
|
function agentVersion() {
|
|
4376
|
-
return "0.11.
|
|
4799
|
+
return "0.11.2";
|
|
4377
4800
|
}
|
|
4378
4801
|
const clone = (message2) => JSON.parse(JSON.stringify(message2));
|
|
4379
4802
|
class MemoryConnection {
|
|
@@ -4868,13 +5291,13 @@ async function firstLine$2(file2) {
|
|
|
4868
5291
|
await handle.close();
|
|
4869
5292
|
}
|
|
4870
5293
|
}
|
|
4871
|
-
const summary = (
|
|
4872
|
-
id:
|
|
4873
|
-
cwd:
|
|
4874
|
-
title: String(
|
|
4875
|
-
model: String(
|
|
4876
|
-
meta:
|
|
4877
|
-
updatedAt: String(
|
|
5294
|
+
const summary = (header2) => header2?.type === "session" && typeof header2.id === "string" && typeof header2.cwd === "string" ? {
|
|
5295
|
+
id: header2.id,
|
|
5296
|
+
cwd: header2.cwd,
|
|
5297
|
+
title: String(header2.title ?? ""),
|
|
5298
|
+
model: String(header2.model ?? ""),
|
|
5299
|
+
meta: header2.meta && typeof header2.meta === "object" ? header2.meta : {},
|
|
5300
|
+
updatedAt: String(header2.updatedAt ?? "")
|
|
4878
5301
|
} : void 0;
|
|
4879
5302
|
class JsonlSessionStore {
|
|
4880
5303
|
constructor(dir) {
|
|
@@ -4888,8 +5311,8 @@ class JsonlSessionStore {
|
|
|
4888
5311
|
async save(record) {
|
|
4889
5312
|
if (!VALID_ID.test(record.id)) throw new Error("Invalid session ID");
|
|
4890
5313
|
const file2 = this.fileFor(record);
|
|
4891
|
-
const { turns, ...
|
|
4892
|
-
const lines2 = [JSON.stringify({ type: "session", version: 1, ...
|
|
5314
|
+
const { turns, ...header2 } = record;
|
|
5315
|
+
const lines2 = [JSON.stringify({ type: "session", version: 1, ...header2 }), ...turns.map((turn) => JSON.stringify({ type: "turn", turn }))];
|
|
4893
5316
|
await writeFileAtomic(file2, `${lines2.join("\n")}
|
|
4894
5317
|
`, 384);
|
|
4895
5318
|
const previous = this.files.get(record.id);
|
|
@@ -6292,7 +6715,8 @@ function nodeHost(setup, link) {
|
|
|
6292
6715
|
config.saveSettings({ providers: { ...providers, [provider2.id]: { ...providers[provider2.id], model } } }).catch((error) => log?.(`Could not save the model: ${error.message}`));
|
|
6293
6716
|
},
|
|
6294
6717
|
get capabilities() {
|
|
6295
|
-
|
|
6718
|
+
const client = link.capabilities();
|
|
6719
|
+
return { fs: true, terminal: true, diagnostics: !!client._meta?.oxbit?.diagnostics, mcp: { http: true, sse: true }, childSessions: !!client.subagents };
|
|
6296
6720
|
},
|
|
6297
6721
|
filesystem: filesystemFor(root),
|
|
6298
6722
|
filesystemFor,
|
|
@@ -6302,7 +6726,8 @@ function nodeHost(setup, link) {
|
|
|
6302
6726
|
generateTitles: setup.generateTitles,
|
|
6303
6727
|
configOptions: compactOptions2.configOptions,
|
|
6304
6728
|
setConfigOption: (_session, configId, value2) => compactOptions2.setConfigOption(configId, value2),
|
|
6305
|
-
extensions
|
|
6729
|
+
extensions,
|
|
6730
|
+
subagents: {}
|
|
6306
6731
|
};
|
|
6307
6732
|
const dispose = async () => {
|
|
6308
6733
|
local.dispose();
|
|
@@ -6447,7 +6872,13 @@ ${USAGE}`);
|
|
|
6447
6872
|
const settings = createSettingsManager(deps);
|
|
6448
6873
|
const setup = (generateTitles, quiet = false) => ({ root: io.cwd, config, generateTitles, log: quiet ? void 0 : log, provider: provider2, settings });
|
|
6449
6874
|
if (command === "acp") {
|
|
6450
|
-
|
|
6875
|
+
const rejected = (error) => log(`Unhandled rejection: ${error instanceof Error ? error.stack ?? error.message : String(error)}`);
|
|
6876
|
+
process.on("unhandledRejection", rejected);
|
|
6877
|
+
try {
|
|
6878
|
+
await serveStdio({ input: io.stdin, output: io.stdout, factory: agentFactory(setup(true)), log });
|
|
6879
|
+
} finally {
|
|
6880
|
+
process.off("unhandledRejection", rejected);
|
|
6881
|
+
}
|
|
6451
6882
|
return 0;
|
|
6452
6883
|
}
|
|
6453
6884
|
const sessionId = await resumeId(config, values, io.cwd);
|
|
@@ -6468,7 +6899,7 @@ ${USAGE}`);
|
|
|
6468
6899
|
}
|
|
6469
6900
|
}
|
|
6470
6901
|
if (!io.stdout.isTTY) throw new UsageError("stdout is not a terminal. Use -p <prompt> for one reply or `oxbit-agent acp` for ACP");
|
|
6471
|
-
const [tui, memory] = await Promise.all([import("./assets/index-
|
|
6902
|
+
const [tui, memory] = await Promise.all([import("./assets/index-hc_7c2Gx.js"), import("./assets/memory-Bdy8hvTl.js")]);
|
|
6472
6903
|
const label = () => ({ provider: provider2.runtime.label, usingChatGPTPlan: provider2.runtime.method === "chatgpt" });
|
|
6473
6904
|
const panels = createSettingsManager({ ...deps, rebuild: async (id2) => {
|
|
6474
6905
|
await config.reload();
|