turbollm 1.10.4 → 1.10.6
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/dist/cli.js +433 -137
- package/dist/webdist/assets/{AgentEditPage-DZU5Ps30.js → AgentEditPage-Cjrahm7d.js} +1 -1
- package/dist/webdist/assets/{ChatScreen-CWjg1zrs.js → ChatScreen-BE2j_M7n.js} +9 -9
- package/dist/webdist/assets/{CodeComposer-C8c0eQe4.js → CodeComposer-CB0nORiZ.js} +1 -1
- package/dist/webdist/assets/CodeHomeScreen-Bh0BY6aY.js +1 -0
- package/dist/webdist/assets/{CodeSessionScreen-DOVBiJIZ.js → CodeSessionScreen-MM4gt_kH.js} +11 -11
- package/dist/webdist/assets/{CustomizeScreen-Dg69A2mU.js → CustomizeScreen-zvF_dtiC.js} +1 -1
- package/dist/webdist/assets/{DeveloperScreen-CwW1Z3BJ.js → DeveloperScreen-DXzdDB5X.js} +1 -1
- package/dist/webdist/assets/{EnginesScreen-CiMymt2y.js → EnginesScreen-CLkBmRVD.js} +1 -1
- package/dist/webdist/assets/{ModelDetailDialog-Ch1FFdDi.js → ModelDetailDialog-CL8_8e9a.js} +1 -1
- package/dist/webdist/assets/{ModelLoadMenu-DmJ6pW_S.js → ModelLoadMenu-C6W2D_HT.js} +1 -1
- package/dist/webdist/assets/{ModelsScreen-Ch83Gnvx.js → ModelsScreen-CMEUd2XW.js} +1 -1
- package/dist/webdist/assets/RoutineEditPage-DNt9jwLQ.js +1 -0
- package/dist/webdist/assets/RoutinesPanel-APLk8zAj.js +1 -0
- package/dist/webdist/assets/{SettingsScreen-fWaRXbXZ.js → SettingsScreen-CNEhuloT.js} +1 -1
- package/dist/webdist/assets/SidebarResizeHandle-BGh24zM4.js +2 -0
- package/dist/webdist/assets/{SkillEditPage-Cpg3YkPw.js → SkillEditPage-QktxVVZP.js} +1 -1
- package/dist/webdist/assets/{TokensScreen-CYGdXUbH.js → TokensScreen-BQu-wJ5j.js} +1 -1
- package/dist/webdist/assets/{ToolApprovalBar-Ba2BoF9X.js → ToolApprovalBar-WuURxg64.js} +3 -3
- package/dist/webdist/assets/WorkspaceScreen-DR9iCevI.js +1 -0
- package/dist/webdist/assets/{abnfDiagram-N423BO3Z-CxAZuY4F.js → abnfDiagram-N423BO3Z-Xdm1Ge5Z.js} +1 -1
- package/dist/webdist/assets/{agent-api-BdnGAV-Y.js → agent-api-Dsb2M3gT.js} +1 -1
- package/dist/webdist/assets/{alert-dialog-CEf-XUyg.js → alert-dialog-CLYOFUQB.js} +1 -1
- package/dist/webdist/assets/{arc-DEU8Rl3Q.js → arc-D2e7_fvj.js} +1 -1
- package/dist/webdist/assets/{architectureDiagram-T3A2C74G-DFWEfaYa.js → architectureDiagram-T3A2C74G-NOGk06k9.js} +1 -1
- package/dist/webdist/assets/{arrow-left-BHr6DbXW.js → arrow-left-Be5kJCK_.js} +1 -1
- package/dist/webdist/assets/{badge-DiZD__AA.js → badge-ZKEdg9KY.js} +1 -1
- package/dist/webdist/assets/{blockDiagram-VBNYF7ZC-BhTU3G_O.js → blockDiagram-VBNYF7ZC-DmcFoOYB.js} +1 -1
- package/dist/webdist/assets/{bot-BR1o3u2z.js → bot-oaBq4kgz.js} +1 -1
- package/dist/webdist/assets/{c4Diagram-5PPSVZJV-CJNhzTh5.js → c4Diagram-5PPSVZJV-zzYM53Qc.js} +1 -1
- package/dist/webdist/assets/channel-BdTqTBNl.js +1 -0
- package/dist/webdist/assets/{chat-api-WdKnt_cR.js → chat-api-D7WunS4X.js} +1 -1
- package/dist/webdist/assets/{chat-queries--BKqKvZT.js → chat-queries-DfV_X46e.js} +1 -1
- package/dist/webdist/assets/{chevron-left-tBRMVkX5.js → chevron-left-DqcpOb9h.js} +1 -1
- package/dist/webdist/assets/{chevron-right-B16ur-An.js → chevron-right-D7i8peZR.js} +1 -1
- package/dist/webdist/assets/{chunk-2GRJ4B5K-Y-VLx_rW.js → chunk-2GRJ4B5K-tWNhRyV6.js} +1 -1
- package/dist/webdist/assets/{chunk-2Q5K7J3B-BUKWb833.js → chunk-2Q5K7J3B-jT4PFPv5.js} +1 -1
- package/dist/webdist/assets/{chunk-5RXB4S5H-BV3L2yyf.js → chunk-5RXB4S5H-CBgg6cvO.js} +1 -1
- package/dist/webdist/assets/{chunk-5VM5RSS4-xI1dTTCA.js → chunk-5VM5RSS4-Do3-h3Xi.js} +1 -1
- package/dist/webdist/assets/{chunk-6Q2QTUOP-C3F611uv.js → chunk-6Q2QTUOP-Y4iDp1I8.js} +1 -1
- package/dist/webdist/assets/{chunk-GF5L2VYU-xzyWIvtP.js → chunk-GF5L2VYU-Bj41431x.js} +1 -1
- package/dist/webdist/assets/{chunk-JWPE2WC7-PCn1DNAq.js → chunk-JWPE2WC7-CufywS1e.js} +1 -1
- package/dist/webdist/assets/{chunk-KBJHAD2P-B2V4Qn1_.js → chunk-KBJHAD2P-BPLIKhbV.js} +1 -1
- package/dist/webdist/assets/{chunk-RYQCIY6F-BXnb7awQ.js → chunk-RYQCIY6F-CmcnhnsQ.js} +1 -1
- package/dist/webdist/assets/{chunk-XXDRQBXY-BjzDsMy6.js → chunk-XXDRQBXY-onzm5o_Q.js} +1 -1
- package/dist/webdist/assets/{circle-check-BHQ4Ky5u.js → circle-check-BY8jczPf.js} +1 -1
- package/dist/webdist/assets/{circle-x-BbJarEnu.js → circle-x-BUlKg2cZ.js} +1 -1
- package/dist/webdist/assets/classDiagram-JCYQIIEL-R1cuP_Pr.js +1 -0
- package/dist/webdist/assets/classDiagram-v2-OCEON4UE-R1cuP_Pr.js +1 -0
- package/dist/webdist/assets/{common-B_Ugw5Ue.js → common-BsHtz98M.js} +1 -1
- package/dist/webdist/assets/{cose-bilkent-JH36ORCC-8GVWCg2F.js → cose-bilkent-JH36ORCC-8JLhJahr.js} +1 -1
- package/dist/webdist/assets/{cynefin-VYW2F7L2-D4EwNxuN.js → cynefin-VYW2F7L2-CBL5ANhm.js} +1 -1
- package/dist/webdist/assets/{cynefinDiagram-MW4NZA55-BchWtjzK.js → cynefinDiagram-MW4NZA55-Cmwj48UP.js} +1 -1
- package/dist/webdist/assets/{dagre-VZM6K2ZE-C-tKBzor.js → dagre-VZM6K2ZE-DngjyEsb.js} +1 -1
- package/dist/webdist/assets/{diagram-7IWD3JNH-CIzXwEou.js → diagram-7IWD3JNH-OQyUakUJ.js} +1 -1
- package/dist/webdist/assets/{diagram-B4RE2ZJO-B0-fu81p.js → diagram-B4RE2ZJO-uWCxDSmb.js} +1 -1
- package/dist/webdist/assets/{diagram-LBJQPF4R-DKUCmHSn.js → diagram-LBJQPF4R-BYhk_VPg.js} +1 -1
- package/dist/webdist/assets/{diagram-Q27KOJAE-BMVmaaBq.js → diagram-Q27KOJAE-CzML3pPm.js} +1 -1
- package/dist/webdist/assets/{diagram-UB23O5K3-C-GsMRNn.js → diagram-UB23O5K3-DIL750-6.js} +1 -1
- package/dist/webdist/assets/{dropdown-menu-BHlT_PrH.js → dropdown-menu-CCRmjOB8.js} +1 -1
- package/dist/webdist/assets/{ebnfDiagram-BXEA7PRR-D2cgRlFo.js → ebnfDiagram-BXEA7PRR-IOFFHUjP.js} +1 -1
- package/dist/webdist/assets/{erDiagram-JOGREHBK-MG0Qt0Dc.js → erDiagram-JOGREHBK-CsYREjU2.js} +1 -1
- package/dist/webdist/assets/{external-link-BYsIzaRn.js → external-link-CwcqLXeC.js} +1 -1
- package/dist/webdist/assets/{file-text-D5LVQPdf.js → file-text-CfktJNKt.js} +1 -1
- package/dist/webdist/assets/{flowDiagram-UKHOOZJN-wAus-SqB.js → flowDiagram-UKHOOZJN-D8V23snk.js} +1 -1
- package/dist/webdist/assets/{folder-input-CPFYQQYm.js → folder-input-CAX7jGqy.js} +1 -1
- package/dist/webdist/assets/{folder-plus-BS4MX3uc.js → folder-plus-DRH9A_nf.js} +1 -1
- package/dist/webdist/assets/{ganttDiagram-PKOTCBZU-iFTGTcny.js → ganttDiagram-PKOTCBZU-BNFLscau.js} +1 -1
- package/dist/webdist/assets/{git-branch-CH_JRbeH.js → git-branch-D3SWa_HB.js} +1 -1
- package/dist/webdist/assets/{gitGraphDiagram-DS77QQ5N-B3Lim-un.js → gitGraphDiagram-DS77QQ5N-CACE08jS.js} +1 -1
- package/dist/webdist/assets/{index-m7O9h-cc.js → index-C3DcPrhr.js} +6 -6
- package/dist/webdist/assets/{infoDiagram-6WML65LV-2U3_w76t.js → infoDiagram-6WML65LV-Bgac4fco.js} +1 -1
- package/dist/webdist/assets/{ishikawaDiagram-WSZJBQD7-8qFZeY06.js → ishikawaDiagram-WSZJBQD7-B-Gx-xHY.js} +1 -1
- package/dist/webdist/assets/{journeyDiagram-NVQOT4AX-BiQ4W5bp.js → journeyDiagram-NVQOT4AX-HkIDa9gF.js} +1 -1
- package/dist/webdist/assets/{kanban-definition-27J2QSJJ-xUKbXLDF.js → kanban-definition-27J2QSJJ-D9fCO--l.js} +1 -1
- package/dist/webdist/assets/{linear-Cfppb_KP.js → linear-BCzUyXir.js} +1 -1
- package/dist/webdist/assets/{lock-_kPFqMMF.js → lock-DU6baFK9.js} +1 -1
- package/dist/webdist/assets/{mermaid.core-Cqc4S48f.js → mermaid.core-DKhj1zV1.js} +4 -4
- package/dist/webdist/assets/{mindmap-definition-FAOFIHXS-DfAXBfEp.js → mindmap-definition-FAOFIHXS-DStYIFGn.js} +1 -1
- package/dist/webdist/assets/{pegDiagram-VL7TDLO6-BDnh7A9L.js → pegDiagram-VL7TDLO6-CMkk8HUV.js} +1 -1
- package/dist/webdist/assets/{pencil-B65o2Xsj.js → pencil-Bt-RSZUH.js} +1 -1
- package/dist/webdist/assets/{personas-B0p0BX69.js → personas-DLa9ZPoi.js} +4 -4
- package/dist/webdist/assets/{pieDiagram-7S7Q4E2Y-BoEHVtTF.js → pieDiagram-7S7Q4E2Y-BaK0uEYv.js} +1 -1
- package/dist/webdist/assets/{plus-BCCJyhZC.js → plus-CFBywfW5.js} +1 -1
- package/dist/webdist/assets/{quadrantDiagram-CIZ2JOQS-BvKFv-w0.js → quadrantDiagram-CIZ2JOQS-C34UZIt1.js} +1 -1
- package/dist/webdist/assets/{railroadDiagram-AXF67PYL-BLrX-HQS.js → railroadDiagram-AXF67PYL-Ckb23iRj.js} +1 -1
- package/dist/webdist/assets/{refresh-cw-BMe9yq9k.js → refresh-cw-BjYW1yib.js} +1 -1
- package/dist/webdist/assets/{requirementDiagram-LRYGKXZP-r4LjKdJa.js → requirementDiagram-LRYGKXZP-CG2IfNZ8.js} +1 -1
- package/dist/webdist/assets/{rotate-ccw-QlaGmqOe.js → rotate-ccw-CDrolPd6.js} +1 -1
- package/dist/webdist/assets/{sankeyDiagram-W5VNT64P-CbTbNO1R.js → sankeyDiagram-W5VNT64P-C37_2Ghs.js} +1 -1
- package/dist/webdist/assets/{sequenceDiagram-SI44F4Z6-C3cZYRGT.js → sequenceDiagram-SI44F4Z6-Cbd9Ydzi.js} +1 -1
- package/dist/webdist/assets/{sizeCapture-X5ZJPWSS-CYYQ8wtn.js → sizeCapture-X5ZJPWSS-Db4VW9lY.js} +1 -1
- package/dist/webdist/assets/{skeleton-CC9ODrk4.js → skeleton-C8GLzn0j.js} +1 -1
- package/dist/webdist/assets/{sliders-horizontal-BFSPlX5c.js → sliders-horizontal-ChT1DRyQ.js} +1 -1
- package/dist/webdist/assets/{sparkles-CmEUYmzj.js → sparkles-BgaRXk3Z.js} +1 -1
- package/dist/webdist/assets/{star-CyudL0Do.js → star-CVPXfTy4.js} +1 -1
- package/dist/webdist/assets/{stateDiagram-OKZ733FA-Bdj39Nof.js → stateDiagram-OKZ733FA-RgxA3rS_.js} +1 -1
- package/dist/webdist/assets/stateDiagram-v2-UEYNNEHI-DW4K46xR.js +1 -0
- package/dist/webdist/assets/{swimlanes-SLNWSIFB-W3xWeGOJ.js → swimlanes-SLNWSIFB-PmBwCiF3.js} +2 -2
- package/dist/webdist/assets/swimlanesDiagram-ULZ7WXOC--nEBdjnh.js +8 -0
- package/dist/webdist/assets/{switch-BDarVW8W.js → switch-CjDd4avM.js} +1 -1
- package/dist/webdist/assets/{terminal-Bvnu3OHJ.js → terminal-z7vkSxg1.js} +1 -1
- package/dist/webdist/assets/{timeline-definition-Z64GVDOM-DnavzBwI.js → timeline-definition-Z64GVDOM-vxaLdkHH.js} +1 -1
- package/dist/webdist/assets/{trash-2-C43ex79G.js → trash-2-BNqXKVdk.js} +1 -1
- package/dist/webdist/assets/{useIsDesktop-BC85xPnA.js → useIsDesktop-CRP_qB3o.js} +1 -1
- package/dist/webdist/assets/{vennDiagram-T6HMQDX7-DdJs_uLj.js → vennDiagram-T6HMQDX7-CY5Ha4JW.js} +1 -1
- package/dist/webdist/assets/{vram-BWaHTGD0.js → vram-C6uGfhzc.js} +1 -1
- package/dist/webdist/assets/{wardleyDiagram-T6FBY63Y-91gPbjKx.js → wardleyDiagram-T6FBY63Y-DQzV926_.js} +1 -1
- package/dist/webdist/assets/{wrench-Dlm2fJiY.js → wrench-Db816rX1.js} +1 -1
- package/dist/webdist/assets/{xychartDiagram-ELKLHX3M-DDKDUQ8u.js → xychartDiagram-ELKLHX3M-B7OfhEpC.js} +1 -1
- package/dist/webdist/index.html +1 -1
- package/package.json +1 -1
- package/dist/webdist/assets/CodeHomeScreen-BbrTdGuL.js +0 -1
- package/dist/webdist/assets/RoutineEditPage-DqDEDN7O.js +0 -1
- package/dist/webdist/assets/RoutinesPanel-C0CgIiju.js +0 -1
- package/dist/webdist/assets/SidebarResizeHandle-eJlzaNXV.js +0 -2
- package/dist/webdist/assets/WorkspaceScreen-CLXpSocV.js +0 -1
- package/dist/webdist/assets/channel-D7Or72nc.js +0 -1
- package/dist/webdist/assets/classDiagram-JCYQIIEL-CoD8BgVw.js +0 -1
- package/dist/webdist/assets/classDiagram-v2-OCEON4UE-CoD8BgVw.js +0 -1
- package/dist/webdist/assets/stateDiagram-v2-UEYNNEHI-B3xKgUl_.js +0 -1
- package/dist/webdist/assets/swimlanesDiagram-ULZ7WXOC-DSpzEq_Q.js +0 -8
package/dist/cli.js
CHANGED
|
@@ -5753,6 +5753,12 @@ var SUPPORTED = {
|
|
|
5753
5753
|
hermes: { bin: "hermes", label: "Hermes Agent", install: "npm install -g hermes-agent", prepareConfig: prepareHermes },
|
|
5754
5754
|
pi: { bin: "pi", label: "pi", install: "npm install -g @earendil-works/pi-coding-agent", prepareConfig: preparePi }
|
|
5755
5755
|
};
|
|
5756
|
+
var CLAUDE_AUTO_COMPACT_WINDOW_MIN = 1e5;
|
|
5757
|
+
var CLAUDE_AUTO_COMPACT_WINDOW_MAX = 1e6;
|
|
5758
|
+
function clampAutoCompactWindow(ctx) {
|
|
5759
|
+
return Math.min(CLAUDE_AUTO_COMPACT_WINDOW_MAX, Math.max(CLAUDE_AUTO_COMPACT_WINDOW_MIN, Math.round(ctx)));
|
|
5760
|
+
}
|
|
5761
|
+
var CLAUDE_AUTOCOMPACT_PCT_OVERRIDE = "80";
|
|
5756
5762
|
var realSpawn = (cmd, args, opts) => {
|
|
5757
5763
|
if (!opts?.shell) return spawn4(cmd, args, opts);
|
|
5758
5764
|
const resolved = resolveExecutable(cmd);
|
|
@@ -6180,6 +6186,15 @@ Open ${base2} \u2192 Models \u2192 Load a model, then run this again.
|
|
|
6180
6186
|
// Opt into gateway model discovery: Claude Code queries our /v1/models at startup and
|
|
6181
6187
|
// populates the /model picker with the local library (gateway synthesises the entries).
|
|
6182
6188
|
CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY: "1",
|
|
6189
|
+
// Founder-reported: the ctx-fill display "jumps between full and empty" and the CLI "never
|
|
6190
|
+
// auto-compacts at 80%, always reaches 100% and fails". The second half is Claude Code
|
|
6191
|
+
// assuming a generic ~200K context window for our unrecognized model id and only compacting
|
|
6192
|
+
// near THAT — see clampAutoCompactWindow's own doc comment for the full root cause and its
|
|
6193
|
+
// documented [100_000, 1_000_000] floor/ceiling. Only set when the daemon actually reports a
|
|
6194
|
+
// real ctx (status.model.ctx) — an absent/zero value means "don't know", and guessing a
|
|
6195
|
+
// window here would be worse than leaving the CLI's own generic default in place.
|
|
6196
|
+
...status.model?.ctx ? { CLAUDE_CODE_AUTO_COMPACT_WINDOW: String(clampAutoCompactWindow(status.model.ctx)) } : {},
|
|
6197
|
+
CLAUDE_AUTOCOMPACT_PCT_OVERRIDE,
|
|
6183
6198
|
// Cap background-agent fan-out at what the engine can actually run concurrently.
|
|
6184
6199
|
//
|
|
6185
6200
|
// Claude Code spawns subagents in parallel, and each is a full, independent request to the
|
|
@@ -7140,13 +7155,16 @@ var AUTO_MODE_ALLOWED_TOOLS = {
|
|
|
7140
7155
|
claude: ["WebSearch", "WebFetch"]
|
|
7141
7156
|
};
|
|
7142
7157
|
var MAX_SEEDED_MESSAGE_CHARS = 4e3;
|
|
7143
|
-
var UNSEEDABLE = /["\u0000-\u001f]/;
|
|
7158
|
+
var UNSEEDABLE = /["\u0000-\u0008\u000b\u000c\u000e-\u001f]/;
|
|
7159
|
+
function normalizeSeededMessage(message) {
|
|
7160
|
+
return message.replace(/[\r\n\t]+/g, " ").trim();
|
|
7161
|
+
}
|
|
7144
7162
|
function canSeedFirstMessage(message, agent) {
|
|
7145
7163
|
if (agent !== "claude") return false;
|
|
7146
|
-
const
|
|
7147
|
-
if (
|
|
7148
|
-
if (UNSEEDABLE.test(
|
|
7149
|
-
if (
|
|
7164
|
+
const normalized = normalizeSeededMessage(message);
|
|
7165
|
+
if (normalized.length === 0 || normalized.length > MAX_SEEDED_MESSAGE_CHARS) return false;
|
|
7166
|
+
if (UNSEEDABLE.test(normalized)) return false;
|
|
7167
|
+
if (normalized.endsWith("\\")) return false;
|
|
7150
7168
|
return true;
|
|
7151
7169
|
}
|
|
7152
7170
|
function buildTerminalLaunchCommand(agent, port2, token, sessionId, launchedOnce, permissionMode, mode, firstMessage) {
|
|
@@ -7155,7 +7173,7 @@ function buildTerminalLaunchCommand(agent, port2, token, sessionId, launchedOnce
|
|
|
7155
7173
|
const modeArg = permissionMode ? ` --permission-mode ${permissionMode}` : "";
|
|
7156
7174
|
const allowed = mode === "auto" ? AUTO_MODE_ALLOWED_TOOLS[agent] : void 0;
|
|
7157
7175
|
const allowedArg = allowed?.length ? ` --allowedTools ${allowed.join(",")}` : "";
|
|
7158
|
-
const promptArg = firstMessage && canSeedFirstMessage(firstMessage, agent) ? ` ${quotePtyShellArg(firstMessage
|
|
7176
|
+
const promptArg = firstMessage && canSeedFirstMessage(firstMessage, agent) ? ` ${quotePtyShellArg(normalizeSeededMessage(firstMessage))}` : "";
|
|
7159
7177
|
return `turbollm launch ${agent}${promptArg} --port ${port2} --token ${token}${sessionArg}${modeArg}${allowedArg}`;
|
|
7160
7178
|
}
|
|
7161
7179
|
function err(c, s, code, msg2) {
|
|
@@ -13488,6 +13506,10 @@ var ConversationStore = class {
|
|
|
13488
13506
|
if (!this.hasColumn("api_usage", "harness")) this.db.exec(`ALTER TABLE api_usage ADD COLUMN harness TEXT;`);
|
|
13489
13507
|
this.db.exec(`PRAGMA user_version = 43;`);
|
|
13490
13508
|
}
|
|
13509
|
+
if (v < 44) {
|
|
13510
|
+
this.db.exec(`CREATE INDEX IF NOT EXISTS idx_api_usage_code_session_tokens ON api_usage(code_session_id, prompt_tokens);`);
|
|
13511
|
+
this.db.exec(`PRAGMA user_version = 44;`);
|
|
13512
|
+
}
|
|
13491
13513
|
}
|
|
13492
13514
|
listConversations(q, kind = "all") {
|
|
13493
13515
|
if (q) {
|
|
@@ -13874,10 +13896,37 @@ var ConversationStore = class {
|
|
|
13874
13896
|
$harness: rec.harness ?? null
|
|
13875
13897
|
});
|
|
13876
13898
|
}
|
|
13877
|
-
/**
|
|
13878
|
-
*
|
|
13879
|
-
*
|
|
13880
|
-
*
|
|
13899
|
+
/** The session's own gateway-request stats (ADR-284, revised twice — founder-reported "ctx
|
|
13900
|
+
* fill jumps between full and empty in the same session", then a review pass on that same fix
|
|
13901
|
+
* catching that it had gone on to freeze the LAST-TURN stats too). Two different questions,
|
|
13902
|
+
* two different rows:
|
|
13903
|
+
*
|
|
13904
|
+
* `ctxUsed` is the MAX prompt_tokens row, not literally the most-recently-INSERTED one. Every
|
|
13905
|
+
* request a real `claude` CLI process makes — the main conversation's own turn AND every
|
|
13906
|
+
* parallel Task-tool sub-agent it spawns — shares the SAME code_session_id: `resolveCodeSession`
|
|
13907
|
+
* (gateway.ts) derives it purely from the bearer token, and the CLI mints exactly one token
|
|
13908
|
+
* (ANTHROPIC_AUTH_TOKEN, set once at launch) for its whole process, sub-agents included. A
|
|
13909
|
+
* sub-agent's own prompt is a small, isolated sub-task — nothing like the full resent
|
|
13910
|
+
* conversation — so whichever of the two finishes last used to decide what the ring showed:
|
|
13911
|
+
* the real, large, ever-growing main-turn prompt one poll, a tiny sub-agent prompt the next,
|
|
13912
|
+
* with no change to the actual conversation in between. The MAX is the honest answer to "how
|
|
13913
|
+
* full has this session's context gotten" — a real Claude Code CLI resends the whole
|
|
13914
|
+
* conversation every turn, so its size only grows within one continuous conversation, and a
|
|
13915
|
+
* sub-agent (always smaller) can never legitimately raise that high-water mark. Trade-off,
|
|
13916
|
+
* accepted: after a real `/clear` inside the CLI (invisible to the daemon — a terminal-agent
|
|
13917
|
+
* session's turns are never persisted here, see ADR-297), this keeps reporting the pre-clear
|
|
13918
|
+
* high-water mark until the new conversation grows past it. A temporarily-stale-high ring
|
|
13919
|
+
* reading after a rare, deliberate user action beats an unpredictable full/empty flicker on
|
|
13920
|
+
* every ordinary agentic turn with sub-agents.
|
|
13921
|
+
*
|
|
13922
|
+
* `promptTokens`/`genTokens`/`promptTps`/`genTps` are the temporally-LAST row instead — these
|
|
13923
|
+
* power TerminalToolbar.tsx's composer-parity stats row, which reads as "your last turn", not
|
|
13924
|
+
* "session high-water mark". Sharing the MAX row here (the original version of this fix) meant
|
|
13925
|
+
* a sub-agent call landing after the real turn hid its own genTokens/tps behind the earlier,
|
|
13926
|
+
* larger turn's numbers, and after a `/clear` these froze at the pre-clear turn's numbers
|
|
13927
|
+
* forever, same failure shape as the ctxUsed bug this method was written to fix in the first
|
|
13928
|
+
* place. null when the session has made no gateway requests yet (fresh session, or a
|
|
13929
|
+
* non-terminal-agent one). */
|
|
13881
13930
|
getLastApiUsageForSession(codeSessionId) {
|
|
13882
13931
|
const row = this.db.prepare(`
|
|
13883
13932
|
SELECT prompt_tokens, gen_tokens, duration_ms, prompt_tps, gen_tps FROM api_usage
|
|
@@ -13885,8 +13934,12 @@ var ConversationStore = class {
|
|
|
13885
13934
|
ORDER BY created_at DESC LIMIT 1
|
|
13886
13935
|
`).get({ $codeSessionId: codeSessionId });
|
|
13887
13936
|
if (!row) return null;
|
|
13937
|
+
const ctxUsed = this.db.prepare(`
|
|
13938
|
+
SELECT MAX(prompt_tokens) as maxPromptTokens FROM api_usage WHERE code_session_id = $codeSessionId
|
|
13939
|
+
`).get({ $codeSessionId: codeSessionId }).maxPromptTokens;
|
|
13888
13940
|
const seconds = row.duration_ms != null && row.duration_ms > 0 ? row.duration_ms / 1e3 : null;
|
|
13889
13941
|
return {
|
|
13942
|
+
ctxUsed,
|
|
13890
13943
|
promptTokens: row.prompt_tokens,
|
|
13891
13944
|
genTokens: row.gen_tokens,
|
|
13892
13945
|
promptTps: row.prompt_tps ?? (seconds ? row.prompt_tokens / seconds : null),
|
|
@@ -15549,6 +15602,17 @@ import { execFile as execFile10 } from "child_process";
|
|
|
15549
15602
|
import { readFileSync as readFileSync12 } from "fs";
|
|
15550
15603
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
15551
15604
|
|
|
15605
|
+
// src/bench/spill.ts
|
|
15606
|
+
var SPILL_TOLERANCE_MB = 512;
|
|
15607
|
+
function spillMb(baselineSharedMb, loadedSharedMb) {
|
|
15608
|
+
if (baselineSharedMb === null || loadedSharedMb === null) return null;
|
|
15609
|
+
return Math.max(0, loadedSharedMb - baselineSharedMb);
|
|
15610
|
+
}
|
|
15611
|
+
function isSpilling(spill) {
|
|
15612
|
+
if (spill === null) return false;
|
|
15613
|
+
return spill > SPILL_TOLERANCE_MB;
|
|
15614
|
+
}
|
|
15615
|
+
|
|
15552
15616
|
// src/api/path-utils.ts
|
|
15553
15617
|
function inferRepoFromPath(filePath, modelDirs) {
|
|
15554
15618
|
const norm = (p) => p.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
@@ -16476,7 +16540,6 @@ var PREFILL_GRACE_MS = 8e3;
|
|
|
16476
16540
|
var TOTAL_BUDGET_MS = 45 * 6e4;
|
|
16477
16541
|
var OOM_RE = /out of memory|cudaMalloc|failed to allocate|unable to allocate|device not ready|CUDA error/i;
|
|
16478
16542
|
var QUALITY_KV = ["f16", "q8_0", "turbo4"];
|
|
16479
|
-
var OUTPUT_TIE = 0.05;
|
|
16480
16543
|
var KV_BYTES = {
|
|
16481
16544
|
f32: 4,
|
|
16482
16545
|
f16: 2,
|
|
@@ -16749,16 +16812,28 @@ var BenchRunner = class {
|
|
|
16749
16812
|
const probe2 = await this.probeVram(entry, sys, { ...base2, ngl: mid }, caps);
|
|
16750
16813
|
this.pushProbe(results, base2, "ngl", mid, probe2);
|
|
16751
16814
|
await this.settleGpu(sys);
|
|
16752
|
-
|
|
16815
|
+
const verdict = probeVerdict(probe2, budgetMb, headroomMb);
|
|
16816
|
+
if (verdict.decision === "fits") {
|
|
16753
16817
|
bestNgl = mid;
|
|
16754
16818
|
lo = mid + 1;
|
|
16755
16819
|
} else {
|
|
16820
|
+
if (verdict.reason === "spill") {
|
|
16821
|
+
this.emit(`ngl=${mid} spilling ${Math.round(verdict.shortfallMb ?? 0)} MB to system RAM \u2014 using fewer GPU layers`);
|
|
16822
|
+
}
|
|
16756
16823
|
hi = mid - 1;
|
|
16757
16824
|
}
|
|
16758
16825
|
}
|
|
16759
16826
|
}
|
|
16760
16827
|
if (bestNgl === null) return null;
|
|
16761
16828
|
let found = await this.benchAt(entry, sys, { ...base2, ngl: bestNgl }, caps, results, `ngl=${bestNgl}`);
|
|
16829
|
+
if (found && isSpilling(found.cand.spillMb) && bestNgl > 0) {
|
|
16830
|
+
this.emit(`ngl=${bestNgl} spilled ${Math.round(found.cand.spillMb)} MB once actually generating (the load-only probe could not see this) \u2014 retrying at ngl=${bestNgl - 1}`);
|
|
16831
|
+
const safer = await this.benchAt(entry, sys, { ...base2, ngl: bestNgl - 1 }, caps, results, `ngl=${bestNgl - 1} (spill backoff)`);
|
|
16832
|
+
if (safer) {
|
|
16833
|
+
found = safer;
|
|
16834
|
+
bestNgl = bestNgl - 1;
|
|
16835
|
+
}
|
|
16836
|
+
}
|
|
16762
16837
|
if (found && overHeadroom(found.cand.vramAbsMb, budgetMb, headroomMb) && bestNgl > 0) {
|
|
16763
16838
|
this.emit(`ngl=${bestNgl} exceeded headroom after generation (${found.cand.vramAbsMb} MB) \u2014 retrying at ngl=${bestNgl - 1}`);
|
|
16764
16839
|
const safer = await this.benchAt(entry, sys, { ...base2, ngl: bestNgl - 1 }, caps, results, `ngl=${bestNgl - 1} (headroom backoff)`);
|
|
@@ -16792,32 +16867,61 @@ var BenchRunner = class {
|
|
|
16792
16867
|
const probe2 = await this.probeVram(entry, sys, { ...base2, nCpuMoe: mid }, caps);
|
|
16793
16868
|
this.pushProbe(results, base2, "nCpuMoe", mid, probe2);
|
|
16794
16869
|
await this.settleGpu(sys);
|
|
16795
|
-
|
|
16796
|
-
|
|
16797
|
-
} else if (probe2.outcome === "ok") {
|
|
16870
|
+
const verdict = probeVerdict(probe2, budgetMb, headroomMb);
|
|
16871
|
+
if (verdict.decision === "fits") {
|
|
16798
16872
|
bestN = mid;
|
|
16799
16873
|
hi = mid - 1;
|
|
16800
16874
|
} else {
|
|
16875
|
+
if (verdict.reason === "spill") {
|
|
16876
|
+
this.emit(`nCpuMoe=${mid} spilling ${Math.round(verdict.shortfallMb ?? 0)} MB to system RAM \u2014 offloading more`);
|
|
16877
|
+
}
|
|
16801
16878
|
lo = mid + 1;
|
|
16802
16879
|
}
|
|
16803
16880
|
}
|
|
16804
16881
|
if (bestN === null) return null;
|
|
16805
|
-
let found =
|
|
16806
|
-
|
|
16807
|
-
|
|
16808
|
-
const
|
|
16809
|
-
|
|
16882
|
+
let found = null;
|
|
16883
|
+
let foundN = bestN;
|
|
16884
|
+
for (let n = bestN; n <= maxN; n++) {
|
|
16885
|
+
const label = n === bestN ? `nCpuMoe=${n}` : `nCpuMoe=${n} (crash backoff)`;
|
|
16886
|
+
found = await this.benchAt(entry, sys, { ...base2, nCpuMoe: n }, caps, results, label);
|
|
16887
|
+
if (found) {
|
|
16888
|
+
foundN = n;
|
|
16889
|
+
break;
|
|
16890
|
+
}
|
|
16891
|
+
const stopped = this.cancelled || Date.now() > this.deadline;
|
|
16892
|
+
if (stopped) {
|
|
16893
|
+
this.emit(`nCpuMoe=${n} failed to bench (${results[results.length - 1]?.outcome ?? "crash"}) \u2014 run ${this.cancelled ? "cancelled" : "out of time budget"}, stopping`);
|
|
16894
|
+
break;
|
|
16895
|
+
}
|
|
16896
|
+
if (n < maxN) this.emit(`nCpuMoe=${n} failed to bench (${results[results.length - 1]?.outcome ?? "crash"}) \u2014 retrying at nCpuMoe=${n + 1}`);
|
|
16897
|
+
}
|
|
16898
|
+
if (found && isSpilling(found.cand.spillMb) && foundN < maxN) {
|
|
16899
|
+
this.emit(`nCpuMoe=${foundN} spilled ${Math.round(found.cand.spillMb)} MB once actually generating (the load-only probe could not see this) \u2014 retrying at nCpuMoe=${foundN + 1}`);
|
|
16900
|
+
const safer = await this.benchAt(entry, sys, { ...base2, nCpuMoe: foundN + 1 }, caps, results, `nCpuMoe=${foundN + 1} (spill backoff)`);
|
|
16901
|
+
if (safer) {
|
|
16902
|
+
found = safer;
|
|
16903
|
+
foundN = foundN + 1;
|
|
16904
|
+
}
|
|
16905
|
+
}
|
|
16906
|
+
if (found && overHeadroom(found.cand.vramAbsMb, budgetMb, headroomMb) && foundN < maxN) {
|
|
16907
|
+
this.emit(`nCpuMoe=${foundN} exceeded headroom after generation (${found.cand.vramAbsMb} MB) \u2014 retrying at nCpuMoe=${foundN + 1}`);
|
|
16908
|
+
const safer = await this.benchAt(entry, sys, { ...base2, nCpuMoe: foundN + 1 }, caps, results, `nCpuMoe=${foundN + 1} (headroom backoff)`);
|
|
16909
|
+
if (safer) {
|
|
16910
|
+
found = safer;
|
|
16911
|
+
foundN = foundN + 1;
|
|
16912
|
+
}
|
|
16810
16913
|
}
|
|
16811
16914
|
if (headroomMb === VRAM_HEADROOM_SPILL_MB && found && found.cand.tps !== null) {
|
|
16812
16915
|
let current = found;
|
|
16813
16916
|
while (current.cand.params.nCpuMoe > 0 && !this.cancelled && Date.now() <= this.deadline) {
|
|
16814
16917
|
const spillN = current.cand.params.nCpuMoe - 1;
|
|
16815
|
-
const prev = {
|
|
16816
|
-
const
|
|
16918
|
+
const prev = { ttftMs: current.cand.ttftMs, tps: current.cand.tps };
|
|
16919
|
+
const prevTtf = ttfMs(prev);
|
|
16920
|
+
const prevLabel = prevTtf !== null ? `${(prevTtf / 1e3).toFixed(1)}s to ${TTF_OUTPUT_TOKENS} tokens` : "an unmeasurable result";
|
|
16817
16921
|
this.emit(`nCpuMoe=${current.cand.params.nCpuMoe} measured ${prevLabel} \u2014 trying VRAM-spill nCpuMoe=${spillN}`);
|
|
16818
16922
|
const spillFound = await this.benchAt(entry, sys, { ...base2, nCpuMoe: spillN }, caps, results, `nCpuMoe=${spillN} (VRAM-spill hill-climb)`);
|
|
16819
16923
|
if (!spillImproves(prev, spillFound?.cand ?? null)) {
|
|
16820
|
-
this.emit(`nCpuMoe=${spillN}
|
|
16924
|
+
this.emit(`nCpuMoe=${spillN} did not beat ${prevLabel} \u2014 stopping climb, keeping nCpuMoe=${current.cand.params.nCpuMoe}`);
|
|
16821
16925
|
break;
|
|
16822
16926
|
}
|
|
16823
16927
|
current = spillFound;
|
|
@@ -16834,7 +16938,7 @@ var BenchRunner = class {
|
|
|
16834
16938
|
* measuring t/s at every search step would be wasted — we bench ONCE, at the chosen config. */
|
|
16835
16939
|
async probeVram(entry, sys, profile, caps) {
|
|
16836
16940
|
const active = this.registry.active();
|
|
16837
|
-
if (!active) return { outcome: "crash", vramAbsMb: null };
|
|
16941
|
+
if (!active) return { outcome: "crash", vramAbsMb: null, spillMb: null };
|
|
16838
16942
|
const testDeadline = Math.min(Date.now() + READY_TIMEOUT_MS + 5e3, this.deadline);
|
|
16839
16943
|
const opts = {
|
|
16840
16944
|
engine: active,
|
|
@@ -16842,19 +16946,22 @@ var BenchRunner = class {
|
|
|
16842
16946
|
modelPath: entry.path,
|
|
16843
16947
|
extraArgs: profileToArgs(profile, entry, caps, sys.cores, sys, active.binPath)
|
|
16844
16948
|
};
|
|
16949
|
+
const sharedBefore = await readGpuSharedMb(sys);
|
|
16845
16950
|
try {
|
|
16846
16951
|
await this.manager.start(opts);
|
|
16847
16952
|
} catch {
|
|
16848
|
-
return { outcome: "crash", vramAbsMb: null };
|
|
16953
|
+
return { outcome: "crash", vramAbsMb: null, spillMb: null };
|
|
16849
16954
|
}
|
|
16850
16955
|
const outcome = await this.awaitReady(testDeadline);
|
|
16851
16956
|
let vramAbsMb = null;
|
|
16957
|
+
let spill = null;
|
|
16852
16958
|
if (outcome === "ok") {
|
|
16853
16959
|
await sleep3(800);
|
|
16854
16960
|
vramAbsMb = await readGpuVramMb(sys);
|
|
16961
|
+
spill = spillMb(sharedBefore, await readGpuSharedMb(sys));
|
|
16855
16962
|
}
|
|
16856
16963
|
await this.stopEngine();
|
|
16857
|
-
return { outcome, vramAbsMb };
|
|
16964
|
+
return { outcome, vramAbsMb, spillMb: spill };
|
|
16858
16965
|
}
|
|
16859
16966
|
/** Record a VRAM-probe trial in the candidate list (tps/prefill are null — nothing was generated;
|
|
16860
16967
|
* only the load outcome and absolute VRAM are known). */
|
|
@@ -16874,7 +16981,8 @@ var BenchRunner = class {
|
|
|
16874
16981
|
prefillTps: null,
|
|
16875
16982
|
ttftMs: null,
|
|
16876
16983
|
vramMb: null,
|
|
16877
|
-
vramAbsMb: probe2.vramAbsMb
|
|
16984
|
+
vramAbsMb: probe2.vramAbsMb,
|
|
16985
|
+
spillMb: probe2.spillMb
|
|
16878
16986
|
};
|
|
16879
16987
|
results.push(probeCand);
|
|
16880
16988
|
this.emit(`${probeCand.label} \u2192 ${probe2.outcome}${probe2.vramAbsMb != null ? ` (${probe2.vramAbsMb} MB)` : ""}`, probeCand);
|
|
@@ -16905,7 +17013,7 @@ var BenchRunner = class {
|
|
|
16905
17013
|
kvTypeK: profile.kvTypeK,
|
|
16906
17014
|
flashAttn: profile.flashAttn
|
|
16907
17015
|
};
|
|
16908
|
-
const fail = (outcome2) => ({ label, params, outcome: outcome2, tps: null, prefillTps: null, ttftMs: null, vramMb: null, vramAbsMb: null });
|
|
17016
|
+
const fail = (outcome2) => ({ label, params, outcome: outcome2, tps: null, prefillTps: null, ttftMs: null, vramMb: null, vramAbsMb: null, spillMb: null });
|
|
16909
17017
|
const phase = (p) => {
|
|
16910
17018
|
this.state = { ...this.state, step: `${stepPrefix} \u2014 ${p}` };
|
|
16911
17019
|
};
|
|
@@ -16920,6 +17028,7 @@ var BenchRunner = class {
|
|
|
16920
17028
|
extraArgs: profileToArgs(profile, entry, caps, sys.cores, sys, active.binPath)
|
|
16921
17029
|
};
|
|
16922
17030
|
const vramBefore = await readGpuVramMb(sys);
|
|
17031
|
+
const sharedBefore = await readGpuSharedMb(sys);
|
|
16923
17032
|
phase("loading model\u2026");
|
|
16924
17033
|
try {
|
|
16925
17034
|
await this.manager.start(opts);
|
|
@@ -16947,10 +17056,11 @@ var BenchRunner = class {
|
|
|
16947
17056
|
phase("measuring t/s\u2026");
|
|
16948
17057
|
const measured = await this.runChatWatched(target, benchMessages, 128, remaining(), logPath);
|
|
16949
17058
|
const vramAfter = await readGpuVramMb(sys);
|
|
17059
|
+
const sharedAfter = await readGpuSharedMb(sys);
|
|
16950
17060
|
await this.stopEngine();
|
|
16951
17061
|
if ("fault" in measured) return fail(measured.fault);
|
|
16952
17062
|
const vramMb = vramBefore !== null && vramAfter !== null ? Math.max(0, vramAfter - vramBefore) : vramAfter;
|
|
16953
|
-
return { label, params, outcome: "ok", tps: measured.tps, prefillTps: measured.prefillTps, ttftMs: measured.ttftMs, vramMb, vramAbsMb: vramAfter };
|
|
17063
|
+
return { label, params, outcome: "ok", tps: measured.tps, prefillTps: measured.prefillTps, ttftMs: measured.ttftMs, vramMb, vramAbsMb: vramAfter, spillMb: spillMb(sharedBefore, sharedAfter) };
|
|
16954
17064
|
}
|
|
16955
17065
|
/** Poll the manager state until the engine is running, the readiness window
|
|
16956
17066
|
* elapses (timeout), the process exits (crash), or an OOM line appears in the
|
|
@@ -17459,20 +17569,35 @@ function pickKvQuants(baseKv, kvTypes) {
|
|
|
17459
17569
|
for (const t of [baseKv, ...QUALITY_KV]) if (supported(t) && !out.includes(t)) out.push(t);
|
|
17460
17570
|
return out;
|
|
17461
17571
|
}
|
|
17572
|
+
function probeVerdict(probe2, budgetMb, headroomMb) {
|
|
17573
|
+
if (probe2.outcome === "oom") return { decision: "offload-more", reason: "oom" };
|
|
17574
|
+
if (isSpilling(probe2.spillMb)) {
|
|
17575
|
+
return { decision: "offload-more", reason: "spill", shortfallMb: probe2.spillMb };
|
|
17576
|
+
}
|
|
17577
|
+
if (overHeadroom(probe2.vramAbsMb, budgetMb, headroomMb)) {
|
|
17578
|
+
return { decision: "offload-more", reason: "headroom" };
|
|
17579
|
+
}
|
|
17580
|
+
if (probe2.outcome === "ok") return { decision: "fits" };
|
|
17581
|
+
return { decision: "offload-more", reason: "failed" };
|
|
17582
|
+
}
|
|
17583
|
+
var TTF_OUTPUT_TOKENS = 1e3;
|
|
17584
|
+
function ttfMs(c, outputTokens = TTF_OUTPUT_TOKENS) {
|
|
17585
|
+
if (c.ttftMs === null || c.tps === null || c.tps <= 0) return null;
|
|
17586
|
+
return c.ttftMs + outputTokens / c.tps * 1e3;
|
|
17587
|
+
}
|
|
17462
17588
|
function betterBySpeed(a, b) {
|
|
17463
|
-
const at = a
|
|
17464
|
-
const bt = b
|
|
17465
|
-
if (
|
|
17466
|
-
|
|
17467
|
-
|
|
17468
|
-
if (rel < -OUTPUT_TIE) return false;
|
|
17469
|
-
return (a.prefillTps ?? 0) > (b.prefillTps ?? 0);
|
|
17589
|
+
const at = ttfMs(a);
|
|
17590
|
+
const bt = ttfMs(b);
|
|
17591
|
+
if (at === null) return false;
|
|
17592
|
+
if (bt === null) return true;
|
|
17593
|
+
return at < bt;
|
|
17470
17594
|
}
|
|
17471
17595
|
function spillImproves(previous, candidate) {
|
|
17472
|
-
if (!candidate || candidate.outcome !== "ok"
|
|
17473
|
-
|
|
17474
|
-
|
|
17475
|
-
return
|
|
17596
|
+
if (!candidate || candidate.outcome !== "ok") return false;
|
|
17597
|
+
const next = ttfMs(candidate);
|
|
17598
|
+
const prev = ttfMs(previous);
|
|
17599
|
+
if (next === null || prev === null) return false;
|
|
17600
|
+
return next < prev;
|
|
17476
17601
|
}
|
|
17477
17602
|
function kvBytes(t) {
|
|
17478
17603
|
return KV_BYTES[t] ?? 2;
|
|
@@ -17538,6 +17663,66 @@ async function readGpuVramMb(sys) {
|
|
|
17538
17663
|
if (sys.gpus.some((g) => g.vendor === "amd")) return readRocmVramMb();
|
|
17539
17664
|
return null;
|
|
17540
17665
|
}
|
|
17666
|
+
var WDDM_SHARED_PS = "$s=(Get-Counter '\\GPU Adapter Memory(*)\\Shared Usage' -ErrorAction SilentlyContinue).CounterSamples;if(-not $s){exit 1};[math]::Round((($s|Measure-Object CookedValue -Sum).Sum)/1MB,1)";
|
|
17667
|
+
var wddmSharedUnsupported = false;
|
|
17668
|
+
function readWddmSharedMb() {
|
|
17669
|
+
if (process.platform !== "win32" || wddmSharedUnsupported) return Promise.resolve(null);
|
|
17670
|
+
return new Promise((resolve5) => {
|
|
17671
|
+
try {
|
|
17672
|
+
execFile10(
|
|
17673
|
+
"powershell",
|
|
17674
|
+
["-NoProfile", "-NonInteractive", "-Command", WDDM_SHARED_PS],
|
|
17675
|
+
{ timeout: 1e4, windowsHide: true },
|
|
17676
|
+
(err8, stdout) => {
|
|
17677
|
+
if (err8) {
|
|
17678
|
+
if (!err8.killed) wddmSharedUnsupported = true;
|
|
17679
|
+
return resolve5(null);
|
|
17680
|
+
}
|
|
17681
|
+
const mb = parseFloat((stdout || "").trim());
|
|
17682
|
+
if (!Number.isFinite(mb) || mb < 0) {
|
|
17683
|
+
wddmSharedUnsupported = true;
|
|
17684
|
+
return resolve5(null);
|
|
17685
|
+
}
|
|
17686
|
+
resolve5(Math.round(mb));
|
|
17687
|
+
}
|
|
17688
|
+
);
|
|
17689
|
+
} catch {
|
|
17690
|
+
wddmSharedUnsupported = true;
|
|
17691
|
+
resolve5(null);
|
|
17692
|
+
}
|
|
17693
|
+
});
|
|
17694
|
+
}
|
|
17695
|
+
function parseRocmGttUsed(memJson) {
|
|
17696
|
+
try {
|
|
17697
|
+
const mem = JSON.parse(memJson);
|
|
17698
|
+
let total = 0;
|
|
17699
|
+
for (const fields of Object.values(mem)) {
|
|
17700
|
+
const usedKey = Object.keys(fields).find((k) => /GTT Total Used Memory/i.test(k));
|
|
17701
|
+
total += usedKey ? parseInt(String(fields[usedKey]).trim(), 10) || 0 : 0;
|
|
17702
|
+
}
|
|
17703
|
+
return Number.isFinite(total) ? Math.round(total / 1e6) : null;
|
|
17704
|
+
} catch {
|
|
17705
|
+
return null;
|
|
17706
|
+
}
|
|
17707
|
+
}
|
|
17708
|
+
function readRocmGttMb() {
|
|
17709
|
+
return new Promise((resolve5) => {
|
|
17710
|
+
try {
|
|
17711
|
+
execFile10("rocm-smi", ["--showmeminfo", "gtt", "--json"], { timeout: 8e3, windowsHide: true }, (err8, stdout) => {
|
|
17712
|
+
if (err8 || !stdout) return resolve5(null);
|
|
17713
|
+
resolve5(parseRocmGttUsed(stdout));
|
|
17714
|
+
});
|
|
17715
|
+
} catch {
|
|
17716
|
+
resolve5(null);
|
|
17717
|
+
}
|
|
17718
|
+
});
|
|
17719
|
+
}
|
|
17720
|
+
async function readGpuSharedMb(sys) {
|
|
17721
|
+
const win = await readWddmSharedMb();
|
|
17722
|
+
if (win !== null) return win;
|
|
17723
|
+
if (sys.gpus.some((g) => g.vendor === "amd")) return readRocmGttMb();
|
|
17724
|
+
return null;
|
|
17725
|
+
}
|
|
17541
17726
|
function sleep3(ms) {
|
|
17542
17727
|
return new Promise((r) => setTimeout(r, ms));
|
|
17543
17728
|
}
|
|
@@ -24252,7 +24437,59 @@ function mapFromOpenAI(oai, modelName) {
|
|
|
24252
24437
|
usage: { input_tokens: usage?.prompt_tokens ?? 0, output_tokens: usage?.completion_tokens ?? 0 }
|
|
24253
24438
|
};
|
|
24254
24439
|
}
|
|
24255
|
-
|
|
24440
|
+
var DEFAULT_PING_INTERVAL_MS = 1e4;
|
|
24441
|
+
function waitOrPing(pending2, ms) {
|
|
24442
|
+
return new Promise((resolve5) => {
|
|
24443
|
+
const timer = setTimeout(() => resolve5("ping"), ms);
|
|
24444
|
+
pending2.then(
|
|
24445
|
+
() => {
|
|
24446
|
+
clearTimeout(timer);
|
|
24447
|
+
resolve5("data");
|
|
24448
|
+
},
|
|
24449
|
+
() => {
|
|
24450
|
+
clearTimeout(timer);
|
|
24451
|
+
resolve5("data");
|
|
24452
|
+
}
|
|
24453
|
+
);
|
|
24454
|
+
});
|
|
24455
|
+
}
|
|
24456
|
+
async function pingWhilePending(pending2, emitPing, pingIntervalMs = DEFAULT_PING_INTERVAL_MS, onOrphan) {
|
|
24457
|
+
pending2.catch(() => {
|
|
24458
|
+
});
|
|
24459
|
+
let nextPingAt = Date.now() + pingIntervalMs;
|
|
24460
|
+
while (true) {
|
|
24461
|
+
const remaining = Math.max(0, nextPingAt - Date.now());
|
|
24462
|
+
const outcome = remaining === 0 ? "ping" : await waitOrPing(pending2, remaining);
|
|
24463
|
+
if (outcome === "ping") {
|
|
24464
|
+
try {
|
|
24465
|
+
await emitPing();
|
|
24466
|
+
} catch (e) {
|
|
24467
|
+
if (onOrphan) pending2.then(onOrphan).catch(() => {
|
|
24468
|
+
});
|
|
24469
|
+
throw e;
|
|
24470
|
+
}
|
|
24471
|
+
nextPingAt = Date.now() + pingIntervalMs;
|
|
24472
|
+
continue;
|
|
24473
|
+
}
|
|
24474
|
+
return pending2;
|
|
24475
|
+
}
|
|
24476
|
+
}
|
|
24477
|
+
function messageStartEvent(msgId, modelName) {
|
|
24478
|
+
return sse("message_start", {
|
|
24479
|
+
message: {
|
|
24480
|
+
id: msgId,
|
|
24481
|
+
type: "message",
|
|
24482
|
+
role: "assistant",
|
|
24483
|
+
content: [],
|
|
24484
|
+
model: modelName,
|
|
24485
|
+
stop_reason: null,
|
|
24486
|
+
stop_sequence: null,
|
|
24487
|
+
usage: { input_tokens: 0, output_tokens: 1 }
|
|
24488
|
+
}
|
|
24489
|
+
});
|
|
24490
|
+
}
|
|
24491
|
+
async function* streamToAnthropic(oaiStream, modelName, msgId, opts = {}) {
|
|
24492
|
+
const { onUsage, onLive, onToolCalls, pingIntervalMs = DEFAULT_PING_INTERVAL_MS, skipMessageStart = false } = opts;
|
|
24256
24493
|
let blockIdx = 0;
|
|
24257
24494
|
let inThinking = false;
|
|
24258
24495
|
let inText = false;
|
|
@@ -24265,25 +24502,30 @@ async function* streamToAnthropic(oaiStream, modelName, msgId, onUsage, onLive,
|
|
|
24265
24502
|
let promptTps = 0;
|
|
24266
24503
|
let genTps = 0;
|
|
24267
24504
|
let liveOut = 0;
|
|
24268
|
-
yield
|
|
24269
|
-
message: {
|
|
24270
|
-
id: msgId,
|
|
24271
|
-
type: "message",
|
|
24272
|
-
role: "assistant",
|
|
24273
|
-
content: [],
|
|
24274
|
-
model: modelName,
|
|
24275
|
-
stop_reason: null,
|
|
24276
|
-
stop_sequence: null,
|
|
24277
|
-
usage: { input_tokens: 0, output_tokens: 1 }
|
|
24278
|
-
}
|
|
24279
|
-
});
|
|
24505
|
+
if (!skipMessageStart) yield messageStartEvent(msgId, modelName);
|
|
24280
24506
|
const reader = oaiStream.getReader();
|
|
24281
24507
|
const dec = new TextDecoder();
|
|
24282
24508
|
let buf = "";
|
|
24283
24509
|
let failed = false;
|
|
24510
|
+
let nextPingAt = Date.now() + pingIntervalMs;
|
|
24284
24511
|
try {
|
|
24285
24512
|
outer: while (true) {
|
|
24286
|
-
const
|
|
24513
|
+
const readPromise = reader.read();
|
|
24514
|
+
readPromise.catch(() => {
|
|
24515
|
+
});
|
|
24516
|
+
let result;
|
|
24517
|
+
while (true) {
|
|
24518
|
+
const remaining = Math.max(0, nextPingAt - Date.now());
|
|
24519
|
+
const outcome = remaining === 0 ? "ping" : await waitOrPing(readPromise, remaining);
|
|
24520
|
+
if (outcome === "ping") {
|
|
24521
|
+
yield sse("ping", {});
|
|
24522
|
+
nextPingAt = Date.now() + pingIntervalMs;
|
|
24523
|
+
continue;
|
|
24524
|
+
}
|
|
24525
|
+
result = await readPromise;
|
|
24526
|
+
break;
|
|
24527
|
+
}
|
|
24528
|
+
const { done, value } = result;
|
|
24287
24529
|
if (done) break;
|
|
24288
24530
|
buf += dec.decode(value, { stream: true });
|
|
24289
24531
|
const lines = buf.split("\n");
|
|
@@ -24828,7 +25070,23 @@ function anthropicErrorType(status) {
|
|
|
24828
25070
|
function asClientStatus(status) {
|
|
24829
25071
|
return status >= 400 && status <= 599 ? status : 500;
|
|
24830
25072
|
}
|
|
24831
|
-
function
|
|
25073
|
+
function classifyGateError(e) {
|
|
25074
|
+
const aborted = e.message === "gate_acquire_aborted";
|
|
25075
|
+
return aborted ? { status: 400, type: "invalid_request_error", message: "Client disconnected while queued for the engine." } : { status: 503, type: "overloaded_error", message: "Timed out waiting for a free engine slot." };
|
|
25076
|
+
}
|
|
25077
|
+
function classifyFetchError(e, ac) {
|
|
25078
|
+
const err8 = e;
|
|
25079
|
+
const isAbort = err8.name === "AbortError" || ac.signal.aborted;
|
|
25080
|
+
const cause = err8.cause instanceof Error ? `: ${err8.cause.message}` : "";
|
|
25081
|
+
return {
|
|
25082
|
+
status: 500,
|
|
25083
|
+
type: "api_error",
|
|
25084
|
+
message: isAbort ? "Client disconnected before the engine responded." : `${err8.message || "Engine unreachable."}${cause}`
|
|
25085
|
+
};
|
|
25086
|
+
}
|
|
25087
|
+
function registerGateway(app2, d, opts = {}) {
|
|
25088
|
+
const pingIntervalMs = opts.pingIntervalMs ?? DEFAULT_PING_INTERVAL_MS;
|
|
25089
|
+
const gateAcquireTimeoutMs = opts.gateAcquireTimeoutMs ?? 6e5;
|
|
24832
25090
|
app2.post("/v1/messages", async (c) => {
|
|
24833
25091
|
let req;
|
|
24834
25092
|
try {
|
|
@@ -24888,23 +25146,131 @@ function registerGateway(app2, d) {
|
|
|
24888
25146
|
if (guidance?.forceTextOnly) oaiBody.tool_choice = "none";
|
|
24889
25147
|
const oaiAlias = engineModelAlias(d.registry.active()?.kind ?? "");
|
|
24890
25148
|
if (oaiAlias) oaiBody.model = oaiAlias;
|
|
25149
|
+
const ac = clientAbort(c);
|
|
25150
|
+
if (req.stream) {
|
|
25151
|
+
const msgId = `msg_${randomUUID13().replace(/-/g, "")}`;
|
|
25152
|
+
return streamSSE4(c, async (stream) => {
|
|
25153
|
+
let gateRelease2 = null;
|
|
25154
|
+
let generationStarted = false;
|
|
25155
|
+
stream.onAbort(() => ac.abort());
|
|
25156
|
+
const ping = () => stream.writeSSE({ event: "ping", data: JSON.stringify({ type: "ping" }) });
|
|
25157
|
+
try {
|
|
25158
|
+
try {
|
|
25159
|
+
await stream.writeSSE(messageStartEvent(msgId, modelName));
|
|
25160
|
+
if (d.gate) {
|
|
25161
|
+
try {
|
|
25162
|
+
gateRelease2 = await pingWhilePending(
|
|
25163
|
+
d.gate.acquire("bg", { signal: c.req.raw.signal, timeoutMs: gateAcquireTimeoutMs }),
|
|
25164
|
+
ping,
|
|
25165
|
+
pingIntervalMs,
|
|
25166
|
+
(release) => release()
|
|
25167
|
+
);
|
|
25168
|
+
} catch (e) {
|
|
25169
|
+
const { type, message } = classifyGateError(e);
|
|
25170
|
+
await stream.writeSSE({ event: "error", data: JSON.stringify({ type: "error", error: { type, message } }) });
|
|
25171
|
+
return;
|
|
25172
|
+
}
|
|
25173
|
+
}
|
|
25174
|
+
d.manager.generationStart();
|
|
25175
|
+
generationStarted = true;
|
|
25176
|
+
const requestStart2 = Date.now();
|
|
25177
|
+
let res2;
|
|
25178
|
+
try {
|
|
25179
|
+
res2 = await pingWhilePending(
|
|
25180
|
+
fetch(`${target}/v1/chat/completions`, {
|
|
25181
|
+
method: "POST",
|
|
25182
|
+
headers: { "Content-Type": "application/json" },
|
|
25183
|
+
body: JSON.stringify(oaiBody),
|
|
25184
|
+
signal: ac.signal
|
|
25185
|
+
}),
|
|
25186
|
+
ping,
|
|
25187
|
+
pingIntervalMs
|
|
25188
|
+
// No onOrphan: an orphaned Response has nothing to release — its body is simply
|
|
25189
|
+
// never read and gets garbage-collected, unlike gate.acquire()'s grant above.
|
|
25190
|
+
);
|
|
25191
|
+
} catch (e) {
|
|
25192
|
+
const { type, message } = classifyFetchError(e, ac);
|
|
25193
|
+
await stream.writeSSE({ event: "error", data: JSON.stringify({ type: "error", error: { type, message } }) });
|
|
25194
|
+
return;
|
|
25195
|
+
}
|
|
25196
|
+
if (!res2.ok || !res2.body) {
|
|
25197
|
+
const { message, type } = await describeEngineError(res2);
|
|
25198
|
+
await stream.writeSSE({
|
|
25199
|
+
event: "error",
|
|
25200
|
+
data: JSON.stringify({ type: "error", error: { type: type ?? anthropicErrorType(res2.status), message } })
|
|
25201
|
+
});
|
|
25202
|
+
return;
|
|
25203
|
+
}
|
|
25204
|
+
const gen = streamToAnthropic(res2.body, modelName, msgId, {
|
|
25205
|
+
onUsage: (u) => {
|
|
25206
|
+
try {
|
|
25207
|
+
d.manager.recordCompletion({
|
|
25208
|
+
inputTokens: u.inputTokens,
|
|
25209
|
+
outputTokens: u.outputTokens,
|
|
25210
|
+
promptTps: u.promptTps,
|
|
25211
|
+
genTps: u.genTps
|
|
25212
|
+
});
|
|
25213
|
+
d.db.recordApiUsage({
|
|
25214
|
+
source: "anthropic",
|
|
25215
|
+
modelKey: req.model ?? null,
|
|
25216
|
+
promptTokens: u.inputTokens,
|
|
25217
|
+
genTokens: u.outputTokens,
|
|
25218
|
+
codeSessionId: anthropicCodeSessionId,
|
|
25219
|
+
durationMs: Date.now() - requestStart2,
|
|
25220
|
+
promptTps: u.promptTps,
|
|
25221
|
+
genTps: u.genTps,
|
|
25222
|
+
harness: anthropicHarness
|
|
25223
|
+
});
|
|
25224
|
+
} catch {
|
|
25225
|
+
}
|
|
25226
|
+
},
|
|
25227
|
+
// Live per-request progress for the engine card (prefill % + token count),
|
|
25228
|
+
// so Claude Code traffic shows the same live row as in-app chat.
|
|
25229
|
+
onLive: (live) => {
|
|
25230
|
+
try {
|
|
25231
|
+
d.manager.setLiveGen(live);
|
|
25232
|
+
} catch {
|
|
25233
|
+
}
|
|
25234
|
+
},
|
|
25235
|
+
// Coding-activity attribution for terminal-agent sessions — see
|
|
25236
|
+
// observeCodeSessionTurn. Nothing to attribute for any other client.
|
|
25237
|
+
onToolCalls: (calls) => {
|
|
25238
|
+
if (!anthropicCodeSessionId) return;
|
|
25239
|
+
try {
|
|
25240
|
+
observeCodeSessionTurn(d, anthropicCodeSessionId, calls);
|
|
25241
|
+
} catch {
|
|
25242
|
+
}
|
|
25243
|
+
},
|
|
25244
|
+
skipMessageStart: true
|
|
25245
|
+
// already sent above, before the queue wait
|
|
25246
|
+
});
|
|
25247
|
+
for await (const evt of gen) {
|
|
25248
|
+
await stream.writeSSE({ event: evt.event, data: evt.data });
|
|
25249
|
+
}
|
|
25250
|
+
} finally {
|
|
25251
|
+
ac.abort();
|
|
25252
|
+
if (generationStarted) d.manager.generationEnd();
|
|
25253
|
+
gateRelease2?.();
|
|
25254
|
+
}
|
|
25255
|
+
} catch (e) {
|
|
25256
|
+
await stream.writeSSE({
|
|
25257
|
+
event: "error",
|
|
25258
|
+
data: JSON.stringify({ type: "error", error: { type: "api_error", message: e?.message || "Internal gateway error." } })
|
|
25259
|
+
}).catch(() => {
|
|
25260
|
+
});
|
|
25261
|
+
}
|
|
25262
|
+
});
|
|
25263
|
+
}
|
|
24891
25264
|
let gateRelease = null;
|
|
24892
25265
|
if (d.gate) {
|
|
24893
25266
|
try {
|
|
24894
|
-
gateRelease = await d.gate.acquire("bg", { signal: c.req.raw.signal, timeoutMs:
|
|
25267
|
+
gateRelease = await d.gate.acquire("bg", { signal: c.req.raw.signal, timeoutMs: gateAcquireTimeoutMs });
|
|
24895
25268
|
} catch (e) {
|
|
24896
|
-
const
|
|
24897
|
-
return c.json(
|
|
24898
|
-
{
|
|
24899
|
-
type: "error",
|
|
24900
|
-
error: aborted ? { type: "invalid_request_error", message: "Client disconnected while queued for the engine." } : { type: "overloaded_error", message: "Timed out waiting for a free engine slot." }
|
|
24901
|
-
},
|
|
24902
|
-
aborted ? 400 : 503
|
|
24903
|
-
);
|
|
25269
|
+
const { status: status2, type, message } = classifyGateError(e);
|
|
25270
|
+
return c.json({ type: "error", error: { type, message } }, status2);
|
|
24904
25271
|
}
|
|
24905
25272
|
}
|
|
24906
25273
|
d.manager.generationStart();
|
|
24907
|
-
const ac = clientAbort(c);
|
|
24908
25274
|
const requestStart = Date.now();
|
|
24909
25275
|
let res;
|
|
24910
25276
|
try {
|
|
@@ -24917,19 +25283,8 @@ function registerGateway(app2, d) {
|
|
|
24917
25283
|
} catch (e) {
|
|
24918
25284
|
d.manager.generationEnd();
|
|
24919
25285
|
gateRelease?.();
|
|
24920
|
-
const
|
|
24921
|
-
|
|
24922
|
-
const cause = err8.cause instanceof Error ? `: ${err8.cause.message}` : "";
|
|
24923
|
-
return c.json(
|
|
24924
|
-
{
|
|
24925
|
-
type: "error",
|
|
24926
|
-
error: {
|
|
24927
|
-
type: "api_error",
|
|
24928
|
-
message: isAbort ? "Client disconnected before the engine responded." : `${err8.message || "Engine unreachable."}${cause}`
|
|
24929
|
-
}
|
|
24930
|
-
},
|
|
24931
|
-
500
|
|
24932
|
-
);
|
|
25286
|
+
const { status: status2, type, message } = classifyFetchError(e, ac);
|
|
25287
|
+
return c.json({ type: "error", error: { type, message } }, status2);
|
|
24933
25288
|
}
|
|
24934
25289
|
if (!res.ok || !res.body) {
|
|
24935
25290
|
d.manager.generationEnd();
|
|
@@ -24940,65 +25295,6 @@ function registerGateway(app2, d) {
|
|
|
24940
25295
|
asClientStatus(res.status)
|
|
24941
25296
|
);
|
|
24942
25297
|
}
|
|
24943
|
-
if (req.stream) {
|
|
24944
|
-
const msgId = `msg_${randomUUID13().replace(/-/g, "")}`;
|
|
24945
|
-
const gen = streamToAnthropic(
|
|
24946
|
-
res.body,
|
|
24947
|
-
modelName,
|
|
24948
|
-
msgId,
|
|
24949
|
-
(u) => {
|
|
24950
|
-
try {
|
|
24951
|
-
d.manager.recordCompletion({
|
|
24952
|
-
inputTokens: u.inputTokens,
|
|
24953
|
-
outputTokens: u.outputTokens,
|
|
24954
|
-
promptTps: u.promptTps,
|
|
24955
|
-
genTps: u.genTps
|
|
24956
|
-
});
|
|
24957
|
-
d.db.recordApiUsage({
|
|
24958
|
-
source: "anthropic",
|
|
24959
|
-
modelKey: req.model ?? null,
|
|
24960
|
-
promptTokens: u.inputTokens,
|
|
24961
|
-
genTokens: u.outputTokens,
|
|
24962
|
-
codeSessionId: anthropicCodeSessionId,
|
|
24963
|
-
durationMs: Date.now() - requestStart,
|
|
24964
|
-
promptTps: u.promptTps,
|
|
24965
|
-
genTps: u.genTps,
|
|
24966
|
-
harness: anthropicHarness
|
|
24967
|
-
});
|
|
24968
|
-
} catch {
|
|
24969
|
-
}
|
|
24970
|
-
},
|
|
24971
|
-
// Live per-request progress for the engine card (prefill % + token count),
|
|
24972
|
-
// so Claude Code traffic shows the same live row as in-app chat.
|
|
24973
|
-
(live) => {
|
|
24974
|
-
try {
|
|
24975
|
-
d.manager.setLiveGen(live);
|
|
24976
|
-
} catch {
|
|
24977
|
-
}
|
|
24978
|
-
},
|
|
24979
|
-
// Coding-activity attribution for terminal-agent sessions — see
|
|
24980
|
-
// observeCodeSessionTurn. Nothing to attribute for any other client.
|
|
24981
|
-
(calls) => {
|
|
24982
|
-
if (!anthropicCodeSessionId) return;
|
|
24983
|
-
try {
|
|
24984
|
-
observeCodeSessionTurn(d, anthropicCodeSessionId, calls);
|
|
24985
|
-
} catch {
|
|
24986
|
-
}
|
|
24987
|
-
}
|
|
24988
|
-
);
|
|
24989
|
-
return streamSSE4(c, async (stream) => {
|
|
24990
|
-
stream.onAbort(() => ac.abort());
|
|
24991
|
-
try {
|
|
24992
|
-
for await (const evt of gen) {
|
|
24993
|
-
await stream.writeSSE({ event: evt.event, data: evt.data });
|
|
24994
|
-
}
|
|
24995
|
-
} finally {
|
|
24996
|
-
ac.abort();
|
|
24997
|
-
d.manager.generationEnd();
|
|
24998
|
-
gateRelease?.();
|
|
24999
|
-
}
|
|
25000
|
-
});
|
|
25001
|
-
}
|
|
25002
25298
|
try {
|
|
25003
25299
|
const oaiRes = await res.json();
|
|
25004
25300
|
recordOpenAiUsage(d, oaiRes, "anthropic", req.model ?? null, anthropicCodeSessionId, Date.now() - requestStart, anthropicHarness);
|