studiograph 1.3.48-next.241 → 1.3.48-next.243
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/agent/agent-pool.d.ts +24 -10
- package/dist/agent/agent-pool.js +31 -10
- package/dist/agent/agent-pool.js.map +1 -1
- package/dist/agent/context-window.d.ts +113 -0
- package/dist/agent/context-window.js +459 -0
- package/dist/agent/context-window.js.map +1 -0
- package/dist/agent/orchestrator.d.ts +7 -0
- package/dist/agent/orchestrator.js +71 -0
- package/dist/agent/orchestrator.js.map +1 -1
- package/dist/agent/skills/entity-schema.md +4 -1
- package/dist/agent/tools/graph-tools.js +6 -1
- package/dist/agent/tools/graph-tools.js.map +1 -1
- package/dist/cli/commands/admin/migrate-assets.d.ts +9 -0
- package/dist/cli/commands/admin/migrate-assets.js +49 -1
- package/dist/cli/commands/admin/migrate-assets.js.map +1 -1
- package/dist/core/artifact-frame-model.d.ts +139 -0
- package/dist/core/artifact-frame-model.js +433 -0
- package/dist/core/artifact-frame-model.js.map +1 -0
- package/dist/core/entity-body-templates.js +1 -1
- package/dist/core/entity-body-templates.js.map +1 -1
- package/dist/core/font-family.d.ts +62 -0
- package/dist/core/font-family.js +84 -0
- package/dist/core/font-family.js.map +1 -0
- package/dist/core/graph.d.ts +0 -4
- package/dist/core/graph.js +7 -13
- package/dist/core/graph.js.map +1 -1
- package/dist/core/secrets/index.d.ts +2 -2
- package/dist/core/secrets/index.js +1 -1
- package/dist/core/secrets/index.js.map +1 -1
- package/dist/core/secrets/registry.d.ts +65 -16
- package/dist/core/secrets/registry.js +96 -28
- package/dist/core/secrets/registry.js.map +1 -1
- package/dist/core/types.js +5 -3
- package/dist/core/types.js.map +1 -1
- package/dist/core/validation.js +7 -3
- package/dist/core/validation.js.map +1 -1
- package/dist/core/workspace-manager.d.ts +12 -0
- package/dist/core/workspace-manager.js +19 -0
- package/dist/core/workspace-manager.js.map +1 -1
- package/dist/mcp/tools.js +37 -0
- package/dist/mcp/tools.js.map +1 -1
- package/dist/server/index.js +5 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/routes/asset-api.js +65 -179
- package/dist/server/routes/asset-api.js.map +1 -1
- package/dist/server/routes/chat.js +39 -0
- package/dist/server/routes/chat.js.map +1 -1
- package/dist/server/routes/graph-api.js +113 -7
- package/dist/server/routes/graph-api.js.map +1 -1
- package/dist/services/asset-listing.d.ts +24 -35
- package/dist/services/asset-listing.js +37 -235
- package/dist/services/asset-listing.js.map +1 -1
- package/dist/services/asset-move-impact.d.ts +49 -0
- package/dist/services/asset-move-impact.js +62 -0
- package/dist/services/asset-move-impact.js.map +1 -0
- package/dist/services/assets/index.d.ts +2 -22
- package/dist/services/assets/index.js +2 -20
- package/dist/services/assets/index.js.map +1 -1
- package/dist/services/fonts/font-metadata.d.ts +42 -0
- package/dist/services/fonts/font-metadata.js +126 -0
- package/dist/services/fonts/font-metadata.js.map +1 -0
- package/dist/services/fonts/font-skill-service.d.ts +66 -0
- package/dist/services/fonts/font-skill-service.js +137 -0
- package/dist/services/fonts/font-skill-service.js.map +1 -0
- package/dist/services/utility-model-resolver.d.ts +6 -10
- package/dist/services/utility-model-resolver.js +11 -17
- package/dist/services/utility-model-resolver.js.map +1 -1
- package/dist/web/_app/immutable/assets/0.4q9K6_aj.css +2 -0
- package/dist/web/_app/immutable/assets/{4.CdxzP04G.css → 4.rAQde9C1.css} +1 -1
- package/dist/web/_app/immutable/assets/{6.Bd_F20BM.css → 6.DONJs2Ua.css} +1 -1
- package/dist/web/_app/immutable/assets/ArtifactEntityView.DUGIdmZD.css +1 -0
- package/dist/web/_app/immutable/assets/{BulkActionsBar.D_-CBnA5.css → BulkActionsBar.CXSM7tO7.css} +1 -1
- package/dist/web/_app/immutable/assets/{FolderPicker.Cp46GHe2.css → FolderPicker.BIvdgOqd.css} +1 -1
- package/dist/web/_app/immutable/chunks/-P-_45Lq.js +1 -0
- package/dist/web/_app/immutable/chunks/{YLpl_oIu.js → 2dmmroBJ.js} +2 -2
- package/dist/web/_app/immutable/chunks/39GYFK-92.js +2 -0
- package/dist/web/_app/immutable/chunks/{CQl7rD-U2.js → 3NWakot5.js} +1 -1
- package/dist/web/_app/immutable/chunks/{Cb8T8f2g2.js → 41TuxWjY2.js} +1 -1
- package/dist/web/_app/immutable/chunks/{40u3Fyat.js → 7hwha0Sh.js} +1 -1
- package/dist/web/_app/immutable/chunks/{LCa8e6zu.js → 8fpcZDYw.js} +1 -1
- package/dist/web/_app/immutable/chunks/94Ryxh_u.js +5 -0
- package/dist/web/_app/immutable/chunks/9eAJQZrp.js +2 -0
- package/dist/web/_app/immutable/chunks/B5t0eFJ92.js +1 -0
- package/dist/web/_app/immutable/chunks/B8Yk5rbB.js +1 -0
- package/dist/web/_app/immutable/chunks/{BzaxZ6j12.js → BDAt3t0d.js} +1 -1
- package/dist/web/_app/immutable/chunks/{DtWm59NA.js → BJDXKR9Z.js} +1 -1
- package/dist/web/_app/immutable/chunks/BKFu0lwN.js +1 -0
- package/dist/web/_app/immutable/chunks/BKoDfR5M.js +1 -0
- package/dist/web/_app/immutable/chunks/{3mkRcVr2.js → BMH0TYNu.js} +1 -1
- package/dist/web/_app/immutable/chunks/BYwcH5OJ.js +8 -0
- package/dist/web/_app/immutable/chunks/BZaiSHRW.js +2 -0
- package/dist/web/_app/immutable/chunks/B_ZKmxdt.js +2 -0
- package/dist/web/_app/immutable/chunks/{BaCR2693.js → BdpPyI9Q.js} +1 -1
- package/dist/web/_app/immutable/chunks/BeLBbSLQ.js +1 -0
- package/dist/web/_app/immutable/chunks/{2eVtOz9o.js → BetCjBPr.js} +1 -1
- package/dist/web/_app/immutable/chunks/{CG2hnd1h.js → Bgvs_OLG.js} +2 -2
- package/dist/web/_app/immutable/chunks/{CykwAMMd.js → Blkb3o9A.js} +1 -1
- package/dist/web/_app/immutable/chunks/{DV2oc0o_2.js → Blnr7o0u2.js} +1 -1
- package/dist/web/_app/immutable/chunks/BqXMLvhL.js +1 -0
- package/dist/web/_app/immutable/chunks/{rW2lf3lL.js → BsGGYtBY.js} +3 -3
- package/dist/web/_app/immutable/chunks/{okRkqEuc2.js → Bu_5WOFt2.js} +2 -2
- package/dist/web/_app/immutable/chunks/{Szr0Rn1A2.js → BudZbwja2.js} +1 -1
- package/dist/web/_app/immutable/chunks/BuiYznRn2.js +1 -0
- package/dist/web/_app/immutable/chunks/{CUFQMEBn2.js → BxFrEcwX.js} +1 -1
- package/dist/web/_app/immutable/chunks/{D1yycc8k.js → By5fEhAV.js} +1 -1
- package/dist/web/_app/immutable/chunks/ByDn7_Qm.js +1 -0
- package/dist/web/_app/immutable/chunks/C-mgrnTp2.js +1 -0
- package/dist/web/_app/immutable/chunks/C17GgqdR.js +2 -0
- package/dist/web/_app/immutable/chunks/{B0psjWmv2.js → CAGPIH5O2.js} +1 -1
- package/dist/web/_app/immutable/chunks/CIgtLr8b.js +1 -0
- package/dist/web/_app/immutable/chunks/{CvLjUdY6.js → CMKOh3Gu.js} +1 -1
- package/dist/web/_app/immutable/chunks/{ClfNZrQj2.js → CQ7ssHiz2.js} +1 -1
- package/dist/web/_app/immutable/chunks/{B2vfWNjV.js → CQz_RPfB.js} +1 -1
- package/dist/web/_app/immutable/chunks/{BFa4tT_F.js → CSYiGoxd.js} +1 -1
- package/dist/web/_app/immutable/chunks/CVGnbOuS2.js +2 -0
- package/dist/web/_app/immutable/chunks/CbjSpBcK2.js +83 -0
- package/dist/web/_app/immutable/chunks/{BsWU-saX.js → Ciqwqq772.js} +1 -1
- package/dist/web/_app/immutable/chunks/Cj6oCHku.js +6 -0
- package/dist/web/_app/immutable/chunks/CkvGsRCA.js +1 -0
- package/dist/web/_app/immutable/chunks/CpUmT5hX2.js +1 -0
- package/dist/web/_app/immutable/chunks/Cr7JY6F4.js +1 -0
- package/dist/web/_app/immutable/chunks/D-p0AOc9.js +1 -0
- package/dist/web/_app/immutable/chunks/{DA1iSzCa.js → D1Jt3dpR.js} +3 -3
- package/dist/web/_app/immutable/chunks/D5PyYNZN2.js +1 -0
- package/dist/web/_app/immutable/chunks/D62Ewp98.js +1 -0
- package/dist/web/_app/immutable/chunks/{CkRPByFB.js → D6T1mkH32.js} +1 -1
- package/dist/web/_app/immutable/chunks/D7pxZWUe2.js +1 -0
- package/dist/web/_app/immutable/chunks/{DzMudXy8.js → DFRK-opu.js} +1 -1
- package/dist/web/_app/immutable/chunks/{C5LRkli5.js → DJ-tF4mo.js} +1 -1
- package/dist/web/_app/immutable/chunks/{T0b_sc_32.js → DMpy3mnJ2.js} +3 -3
- package/dist/web/_app/immutable/chunks/DRkkJdkV.js +1 -0
- package/dist/web/_app/immutable/chunks/DTRR5v8f.js +1 -0
- package/dist/web/_app/immutable/chunks/DYdH-va_.js +2 -0
- package/dist/web/_app/immutable/chunks/{DNSTIFsE2.js → D_1fVNTS2.js} +1 -1
- package/dist/web/_app/immutable/chunks/Db11KpbT.js +1 -0
- package/dist/web/_app/immutable/chunks/DcPrzUMJ2.js +1 -0
- package/dist/web/_app/immutable/chunks/{CcdOGNoe.js → DeNJFJmW.js} +1 -1
- package/dist/web/_app/immutable/chunks/DioG9gEY.js +1 -0
- package/dist/web/_app/immutable/chunks/{Jvqy_K4S.js → DjWIE8kE.js} +1 -1
- package/dist/web/_app/immutable/chunks/{CxSiMudP.js → Dm3FeQiy.js} +2 -2
- package/dist/web/_app/immutable/chunks/{D2hYhhaj.js → DoBrc_MD.js} +1 -1
- package/dist/web/_app/immutable/chunks/{BHt0yMqi.js → Dqt3ZQbf.js} +1 -1
- package/dist/web/_app/immutable/chunks/{DZyEqtHZ.js → DvDv3PXS.js} +1 -1
- package/dist/web/_app/immutable/chunks/DwcahpVZ2.js +2 -0
- package/dist/web/_app/immutable/chunks/{o5oBpH0v.js → Dz0NKq7k.js} +1 -1
- package/dist/web/_app/immutable/chunks/JPkInEiy2.js +2 -0
- package/dist/web/_app/immutable/chunks/OlaBOMTk.js +1 -0
- package/dist/web/_app/immutable/chunks/VYOuBRrp2.js +1 -0
- package/dist/web/_app/immutable/chunks/WnsexHKM.js +2 -0
- package/dist/web/_app/immutable/chunks/_4BKTeRf.js +1 -0
- package/dist/web/_app/immutable/chunks/_nxBVnQa.js +1 -0
- package/dist/web/_app/immutable/chunks/deOZsxWU.js +1 -0
- package/dist/web/_app/immutable/chunks/kkDjm5WC.js +1 -0
- package/dist/web/_app/immutable/chunks/{NQ4cw_fx.js → oYsB08kS.js} +1 -1
- package/dist/web/_app/immutable/chunks/{Cm2Oo0dQ.js → u78dShfg.js} +1 -1
- package/dist/web/_app/immutable/chunks/{DsgQXkPI.js → x2r4Enci.js} +1 -1
- package/dist/web/_app/immutable/chunks/{DcpCVN9q.js → yhLA_1G0.js} +1 -1
- package/dist/web/_app/immutable/entry/app.CyaMSTD-.js +2 -0
- package/dist/web/_app/immutable/entry/start.w51yOSqv.js +1 -0
- package/dist/web/_app/immutable/nodes/{0.B6Y_LBk7.js → 0.De1hV4CK.js} +2 -2
- package/dist/web/_app/immutable/nodes/{1.D4YkYR_K.js → 1.Byc9lVX0.js} +1 -1
- package/dist/web/_app/immutable/nodes/{10.GeQH4voX.js → 10.5O2PtJDQ.js} +1 -1
- package/dist/web/_app/immutable/nodes/{11.Dh82pQqJ.js → 11.B5We-hs7.js} +1 -1
- package/dist/web/_app/immutable/nodes/{12.B_3NIDKw.js → 12.BJY0oraq.js} +1 -1
- package/dist/web/_app/immutable/nodes/{13.CENuKScK.js → 13.3tv4r7-W.js} +1 -1
- package/dist/web/_app/immutable/nodes/15.CXxJLwsA.js +1 -0
- package/dist/web/_app/immutable/nodes/{16.CQ1g-7fy.js → 16.DAZbcz9r.js} +1 -1
- package/dist/web/_app/immutable/nodes/17.DNfdzJKO.js +1 -0
- package/dist/web/_app/immutable/nodes/{18.BGK1DT5w.js → 18.CRWiO34_.js} +1 -1
- package/dist/web/_app/immutable/nodes/2.D2zEcEUa.js +125 -0
- package/dist/web/_app/immutable/nodes/3.DtSKWUk0.js +6 -0
- package/dist/web/_app/immutable/nodes/4.C1C-ZltD.js +16 -0
- package/dist/web/_app/immutable/nodes/{5.Dg7MyS_D.js → 5.B7l9_B2T.js} +1 -1
- package/dist/web/_app/immutable/nodes/6.DT5EDRvz.js +4 -0
- package/dist/web/_app/immutable/nodes/{7.DsTgdKJY.js → 7.CQ8iYk-b.js} +1 -1
- package/dist/web/_app/immutable/nodes/{8.C2cgEqFX.js → 8.B-B5mqzx.js} +1 -1
- package/dist/web/_app/immutable/nodes/{9.DzTYjHLu.js → 9.A8hgLZWF.js} +1 -1
- package/dist/web/_app/version.json +1 -1
- package/dist/web/index.html +17 -17
- package/package.json +3 -1
- package/dist/web/_app/immutable/assets/0.CbwnbgGB.css +0 -2
- package/dist/web/_app/immutable/assets/ArtifactEntityView.pIdeOQQE.css +0 -1
- package/dist/web/_app/immutable/chunks/4nXmhCEf2.js +0 -2
- package/dist/web/_app/immutable/chunks/B1jPUcUY.js +0 -2
- package/dist/web/_app/immutable/chunks/B8CjE3Zb.js +0 -1
- package/dist/web/_app/immutable/chunks/BCC39_oZ.js +0 -1
- package/dist/web/_app/immutable/chunks/BL0OfnJm.js +0 -8
- package/dist/web/_app/immutable/chunks/BNpak4NV.js +0 -6
- package/dist/web/_app/immutable/chunks/BSBfdnem.js +0 -1
- package/dist/web/_app/immutable/chunks/BTNc9Y1I.js +0 -1
- package/dist/web/_app/immutable/chunks/BbdJq5V-.js +0 -2
- package/dist/web/_app/immutable/chunks/BiL0KXYv2.js +0 -1
- package/dist/web/_app/immutable/chunks/BielyHqY.js +0 -1
- package/dist/web/_app/immutable/chunks/Bl41UZSS.js +0 -1
- package/dist/web/_app/immutable/chunks/BljLR4Fb.js +0 -1
- package/dist/web/_app/immutable/chunks/BoCumB752.js +0 -1
- package/dist/web/_app/immutable/chunks/BqGYAiLE.js +0 -2
- package/dist/web/_app/immutable/chunks/CE3Vl5fC.js +0 -2
- package/dist/web/_app/immutable/chunks/CIfkrRcg2.js +0 -1
- package/dist/web/_app/immutable/chunks/CW-Ov0pM2.js +0 -7
- package/dist/web/_app/immutable/chunks/CWReqXbo.js +0 -1
- package/dist/web/_app/immutable/chunks/CZgFaimi2.js +0 -83
- package/dist/web/_app/immutable/chunks/CmD0Pw4p.js +0 -1
- package/dist/web/_app/immutable/chunks/CrwD95qi.js +0 -1
- package/dist/web/_app/immutable/chunks/D-3tCwGW2.js +0 -1
- package/dist/web/_app/immutable/chunks/D0rBhnqB2.js +0 -1
- package/dist/web/_app/immutable/chunks/D7DUV8m62.js +0 -1
- package/dist/web/_app/immutable/chunks/D7LxIGlr2.js +0 -1
- package/dist/web/_app/immutable/chunks/DAiEwMLj.js +0 -1
- package/dist/web/_app/immutable/chunks/DBsoRks82.js +0 -2
- package/dist/web/_app/immutable/chunks/DLrTb3Ng2.js +0 -2
- package/dist/web/_app/immutable/chunks/DOPRSg8K.js +0 -1
- package/dist/web/_app/immutable/chunks/Dk_kSxt2.js +0 -2
- package/dist/web/_app/immutable/chunks/Dv03G3ns.js +0 -1
- package/dist/web/_app/immutable/chunks/DxiNzbUg2.js +0 -1
- package/dist/web/_app/immutable/chunks/PeIamBdH.js +0 -3
- package/dist/web/_app/immutable/chunks/Re1mVUuh.js +0 -1
- package/dist/web/_app/immutable/chunks/anMAD2SC.js +0 -1
- package/dist/web/_app/immutable/chunks/g9Li6c7C.js +0 -2
- package/dist/web/_app/immutable/chunks/poXSsDDO.js +0 -1
- package/dist/web/_app/immutable/entry/app.DMx34n6u.js +0 -2
- package/dist/web/_app/immutable/entry/start.CyYr2Wch.js +0 -1
- package/dist/web/_app/immutable/nodes/15.CsSKUe8p.js +0 -1
- package/dist/web/_app/immutable/nodes/17.BOHirkyf.js +0 -1
- package/dist/web/_app/immutable/nodes/2.z37DNGUZ.js +0 -123
- package/dist/web/_app/immutable/nodes/3.CRRrDZcD.js +0 -6
- package/dist/web/_app/immutable/nodes/4.BvQ4JVou.js +0 -11
- package/dist/web/_app/immutable/nodes/6.BsBJH-kP.js +0 -6
- /package/dist/web/_app/immutable/chunks/{CYEYDNwO2.js → CYEYDNwO.js} +0 -0
- /package/dist/web/_app/immutable/chunks/{D4uUZ_aS2.js → D4uUZ_aS.js} +0 -0
- /package/dist/web/_app/immutable/chunks/{HrvI1J6b.js → HrvI1J6b2.js} +0 -0
- /package/dist/web/_app/immutable/chunks/{UZu_W1XF.js → UZu_W1XF2.js} +0 -0
- /package/dist/web/_app/immutable/chunks/{hG-QCarB.js → hG-QCarB2.js} +0 -0
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context-window management for the in-app chat agent.
|
|
3
|
+
*
|
|
4
|
+
* The agent accumulates conversation history in `agent.state.messages`. Large
|
|
5
|
+
* tool results (asset/image payloads from list_assets / read_asset /
|
|
6
|
+
* inspect_artifact, big document or dataset bodies) plus the running turn count
|
|
7
|
+
* make that array grow without bound. Once it exceeds the model's token ceiling
|
|
8
|
+
* the provider rejects the WHOLE request with a hard 400 ("prompt is too long:
|
|
9
|
+
* N tokens > M maximum"), killing the chat.
|
|
10
|
+
*
|
|
11
|
+
* This module is the PROACTIVE half of the fix: a pure, provider-agnostic
|
|
12
|
+
* pass that estimates tokens and, when a request would overflow, trims the
|
|
13
|
+
* transcript down to fit BEFORE it is sent. It is wired into the runtime via
|
|
14
|
+
* pi-agent-core's `transformContext` hook (orchestrator.ts), which runs once
|
|
15
|
+
* before every LLM call and non-destructively produces the message array sent
|
|
16
|
+
* to the provider — the persistent transcript (saved to disk / shown in the UI)
|
|
17
|
+
* is untouched.
|
|
18
|
+
*
|
|
19
|
+
* The DESTRUCTIVE half (agent-pool.ts `compactArtifactHistory`) is the privacy/
|
|
20
|
+
* baseline complement: after each turn it strips images and elides artifact
|
|
21
|
+
* bundles from the PERSISTED transcript, but deliberately keeps large text (so
|
|
22
|
+
* saved chats stay readable). It does not share this module's helpers — the two
|
|
23
|
+
* passes have different jobs (must-not-persist vs live token budget); see that
|
|
24
|
+
* method's own docstring for the split of labor.
|
|
25
|
+
*
|
|
26
|
+
* Nothing here does I/O or model calls — it is deterministic and unit-tested.
|
|
27
|
+
*/
|
|
28
|
+
// ── Tunables ────────────────────────────────────────────────────────────────
|
|
29
|
+
/**
|
|
30
|
+
* Characters per token — deliberately the LOW end of the realistic range, which
|
|
31
|
+
* makes the whole module's safety invariant hold:
|
|
32
|
+
*
|
|
33
|
+
* as long as real-chars-per-token ≥ CHARS_PER_TOKEN, our estimate is ≥ the
|
|
34
|
+
* real token count, so clipping/trimming to a token budget guarantees the
|
|
35
|
+
* real prompt stays under that budget (and thus under the model window).
|
|
36
|
+
*
|
|
37
|
+
* The two error directions are asymmetric: under-counting risks a hard provider
|
|
38
|
+
* 400 that kills the whole conversation (the exact failure this module prevents);
|
|
39
|
+
* over-counting only trims slightly earlier than strictly necessary. Measured
|
|
40
|
+
* density by content: English prose ~4, code ~3, and the token-dense JSON that
|
|
41
|
+
* actually overflows — `list_assets` rows full of UUIDs/URLs/filenames — ~2.4
|
|
42
|
+
* (observed live: 1.10M real tokens vs a 0.88M estimate at the old value of 3,
|
|
43
|
+
* a 25% under-count that 400'd a 1M-window request). 2.2 sits just below that
|
|
44
|
+
* worst realistic case, so dense tool results no longer slip the budget; only
|
|
45
|
+
* sub-2.2 content (heavy base64 text, which is rare since images are stripped as
|
|
46
|
+
* blocks) can, and the reactive `isContextOverflow` net (chat.ts) backstops it.
|
|
47
|
+
*/
|
|
48
|
+
const CHARS_PER_TOKEN = 2.2;
|
|
49
|
+
/** Per-message structural overhead (role framing, delimiters) in tokens. */
|
|
50
|
+
const PER_MESSAGE_OVERHEAD = 4;
|
|
51
|
+
/** Rough token cost of an image content block. Image tokens are a function of
|
|
52
|
+
* pixel dimensions, not byte size, but we only have the base64 string here, so
|
|
53
|
+
* this is a deliberately rough heuristic with an 800-token floor (downscaled
|
|
54
|
+
* inspect images ≤1024px land ~1.6k; large composer uploads more). Exactness
|
|
55
|
+
* isn't required — images ride only in recent/pinned turns, and the budget
|
|
56
|
+
* margin plus the reactive overflow net absorb the slack. */
|
|
57
|
+
function estimateImageTokens(base64Len) {
|
|
58
|
+
return Math.max(800, Math.ceil(base64Len / 700));
|
|
59
|
+
}
|
|
60
|
+
/** A tool result is "large" once its content estimate crosses this. Tier-1
|
|
61
|
+
* stubbing and the post-turn compaction both use it. ~2k tokens ≈ a screenful
|
|
62
|
+
* of JSON — small results stay inline so the agent doesn't have to re-fetch. */
|
|
63
|
+
export const LARGE_TOOL_RESULT_TOKENS = 2000;
|
|
64
|
+
/** Turns at the tail that are never evicted or stubbed — the live working set
|
|
65
|
+
* the agent needs to answer the current question. A "turn" is a user-message-
|
|
66
|
+
* delimited group (see `groupIntoTurns`). */
|
|
67
|
+
const PINNED_RECENT_TURNS = 3;
|
|
68
|
+
/** Estimate tokens for one message's content (text + images + tool-call args). */
|
|
69
|
+
export function estimateMessageTokens(msg) {
|
|
70
|
+
const m = msg;
|
|
71
|
+
if (!m || typeof m !== 'object')
|
|
72
|
+
return 0;
|
|
73
|
+
let chars = 0;
|
|
74
|
+
let tokens = PER_MESSAGE_OVERHEAD;
|
|
75
|
+
const addContent = (content) => {
|
|
76
|
+
if (typeof content === 'string') {
|
|
77
|
+
chars += content.length;
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (!Array.isArray(content))
|
|
81
|
+
return;
|
|
82
|
+
for (const block of content) {
|
|
83
|
+
if (!block || typeof block !== 'object')
|
|
84
|
+
continue;
|
|
85
|
+
if (block.type === 'text' && typeof block.text === 'string')
|
|
86
|
+
chars += block.text.length;
|
|
87
|
+
else if (block.type === 'thinking' && typeof block.thinking === 'string')
|
|
88
|
+
chars += block.thinking.length;
|
|
89
|
+
else if (block.type === 'image' && typeof block.data === 'string')
|
|
90
|
+
tokens += estimateImageTokens(block.data.length);
|
|
91
|
+
else if (block.type === 'toolCall') {
|
|
92
|
+
// Assistant tool-call block: name + serialized arguments.
|
|
93
|
+
if (typeof block.name === 'string')
|
|
94
|
+
chars += block.name.length;
|
|
95
|
+
try {
|
|
96
|
+
chars += JSON.stringify(block.arguments ?? {}).length;
|
|
97
|
+
}
|
|
98
|
+
catch { /* unserializable — skip */ }
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
addContent(m.content);
|
|
103
|
+
return tokens + Math.ceil(chars / CHARS_PER_TOKEN);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Estimate the total prompt tokens for a transcript, optionally including the
|
|
107
|
+
* system prompt and tool schemas (both ride in every request).
|
|
108
|
+
*/
|
|
109
|
+
export function estimateTokens(messages, systemPrompt, tools) {
|
|
110
|
+
let total = 0;
|
|
111
|
+
for (const msg of messages)
|
|
112
|
+
total += estimateMessageTokens(msg);
|
|
113
|
+
if (systemPrompt)
|
|
114
|
+
total += Math.ceil(systemPrompt.length / CHARS_PER_TOKEN);
|
|
115
|
+
if (tools && tools.length) {
|
|
116
|
+
let schemaChars = 0;
|
|
117
|
+
for (const t of tools) {
|
|
118
|
+
schemaChars += (t.name?.length ?? 0) + (t.description?.length ?? 0);
|
|
119
|
+
try {
|
|
120
|
+
schemaChars += JSON.stringify(t.parameters ?? {}).length;
|
|
121
|
+
}
|
|
122
|
+
catch { /* skip */ }
|
|
123
|
+
}
|
|
124
|
+
total += Math.ceil(schemaChars / CHARS_PER_TOKEN);
|
|
125
|
+
}
|
|
126
|
+
return total;
|
|
127
|
+
}
|
|
128
|
+
// Note on accuracy: an earlier design self-calibrated the estimator against each
|
|
129
|
+
// turn's real `usage.input`. That was removed deliberately — once the manager
|
|
130
|
+
// starts trimming, the persisted transcript and the prompt actually sent diverge,
|
|
131
|
+
// so the learned ratio biases downward exactly when trimming is active (less
|
|
132
|
+
// trimming → overflow), and folding the fixed tool-schema cost into a
|
|
133
|
+
// multiplicative factor double-counted it. The conservative fixed estimator
|
|
134
|
+
// (`CHARS_PER_TOKEN`, see above) plus the `computeBudget` margin and the reactive
|
|
135
|
+
// `isContextOverflow` backstop are simpler and fail safe.
|
|
136
|
+
// ── Large-result detection + stubbing (used by manageContext's Tier 1) ─────
|
|
137
|
+
/** True if a toolResult message's content is large enough to be worth eliding. */
|
|
138
|
+
export function isLargeToolResult(msg) {
|
|
139
|
+
const m = msg;
|
|
140
|
+
if (!m || m.role !== 'toolResult' || !Array.isArray(m.content))
|
|
141
|
+
return false;
|
|
142
|
+
// Any image block makes it large (vision must never persist across turns).
|
|
143
|
+
if (m.content.some((b) => b?.type === 'image'))
|
|
144
|
+
return true;
|
|
145
|
+
return estimateMessageTokens(msg) >= LARGE_TOOL_RESULT_TOKENS;
|
|
146
|
+
}
|
|
147
|
+
/** The stub text that replaces an elided tool result. Names the tool so the
|
|
148
|
+
* agent knows it can call it again to retrieve the content on demand. */
|
|
149
|
+
export function toolResultStub(toolName) {
|
|
150
|
+
const name = toolName || 'a tool';
|
|
151
|
+
return `[A large ${name} result from an earlier step was removed to save context. ` +
|
|
152
|
+
`Call ${name} again if you need its current contents.]`;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Return a pairing-safe stubbed COPY of a toolResult message: content replaced
|
|
156
|
+
* with a one-line stub, `toolCallId`/`toolName`/`role` preserved (so the paired
|
|
157
|
+
* assistant tool-call still resolves and the provider doesn't 400), `details`
|
|
158
|
+
* dropped. Returns the original reference if it is not a large tool result.
|
|
159
|
+
*/
|
|
160
|
+
export function stubToolResult(msg) {
|
|
161
|
+
const m = msg;
|
|
162
|
+
if (!isLargeToolResult(msg))
|
|
163
|
+
return msg;
|
|
164
|
+
const copy = { ...m, content: [{ type: 'text', text: toolResultStub(m.toolName) }] };
|
|
165
|
+
delete copy.details;
|
|
166
|
+
return copy;
|
|
167
|
+
}
|
|
168
|
+
/** Compute the usable input-token budget for a model. */
|
|
169
|
+
export function computeBudget(model) {
|
|
170
|
+
// Defensive: a freeform/custom model id resolves to `undefined` via pi-ai's
|
|
171
|
+
// getModel(); without this guard `model.contextWindow` throws, transformContext
|
|
172
|
+
// swallows it, and proactive trimming is silently disabled for the whole
|
|
173
|
+
// session. Treat an unknown model as a conservative 200k window.
|
|
174
|
+
const ctx = model?.contextWindow && model.contextWindow > 0 ? model.contextWindow : 200_000;
|
|
175
|
+
const reserveForOutput = Math.min(model?.maxTokens && model.maxTokens > 0 ? model.maxTokens : 8000, 64_000);
|
|
176
|
+
// 10% cushion (was 6%): absorbs estimator variance for content slightly denser
|
|
177
|
+
// than CHARS_PER_TOKEN, on top of the output reservation.
|
|
178
|
+
const safetyMargin = Math.max(Math.floor(ctx * 0.10), 8000);
|
|
179
|
+
return Math.max(ctx - reserveForOutput - safetyMargin, Math.floor(ctx * 0.25));
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Group messages into user-delimited turns. A turn starts at a `user` message
|
|
183
|
+
* and runs up to (but not including) the next `user` message — so an assistant's
|
|
184
|
+
* tool-call blocks and the `toolResult` messages that answer them always live in
|
|
185
|
+
* the SAME turn. That invariant is what makes whole-turn eviction pairing-safe:
|
|
186
|
+
* dropping a turn never orphans a tool_use from its tool_result (the Anthropic
|
|
187
|
+
* 400). Any leading non-user messages (e.g. a loaded session) form turn 0.
|
|
188
|
+
*/
|
|
189
|
+
function groupIntoTurns(messages) {
|
|
190
|
+
const turns = [];
|
|
191
|
+
let current = [];
|
|
192
|
+
for (const msg of messages) {
|
|
193
|
+
if (msg?.role === 'user' && current.length > 0) {
|
|
194
|
+
turns.push(current);
|
|
195
|
+
current = [];
|
|
196
|
+
}
|
|
197
|
+
current.push(msg);
|
|
198
|
+
}
|
|
199
|
+
if (current.length > 0)
|
|
200
|
+
turns.push(current);
|
|
201
|
+
return turns;
|
|
202
|
+
}
|
|
203
|
+
/** Short topic snippet from a user message, for the rolling-summary stub. */
|
|
204
|
+
function userTopic(msg) {
|
|
205
|
+
const m = msg;
|
|
206
|
+
if (m?.role !== 'user')
|
|
207
|
+
return null;
|
|
208
|
+
let text = '';
|
|
209
|
+
if (typeof m.content === 'string')
|
|
210
|
+
text = m.content;
|
|
211
|
+
else if (Array.isArray(m.content)) {
|
|
212
|
+
text = m.content.filter((b) => b?.type === 'text').map((b) => b.text).join(' ');
|
|
213
|
+
}
|
|
214
|
+
text = text.replace(/\s+/g, ' ').trim();
|
|
215
|
+
if (!text)
|
|
216
|
+
return null;
|
|
217
|
+
return text.length > 80 ? text.slice(0, 80) + '…' : text;
|
|
218
|
+
}
|
|
219
|
+
/** Rolling-summary text describing the evicted turns. Prepended to the first
|
|
220
|
+
* KEPT user message (not emitted as its own message) so we never produce two
|
|
221
|
+
* consecutive user messages — some providers enforce role alternation. */
|
|
222
|
+
function buildSummaryText(evicted) {
|
|
223
|
+
const msgCount = evicted.reduce((n, t) => n + t.length, 0);
|
|
224
|
+
const topics = evicted
|
|
225
|
+
.flat()
|
|
226
|
+
.map(userTopic)
|
|
227
|
+
.filter((t) => !!t)
|
|
228
|
+
.slice(0, 8);
|
|
229
|
+
const topicLine = topics.length ? ` Earlier topics: ${topics.join('; ')}.` : '';
|
|
230
|
+
return `[${msgCount} earlier message(s) were removed to fit the model's context window.${topicLine} ` +
|
|
231
|
+
`Ask the user or re-run a tool if you need details from before this point.]`;
|
|
232
|
+
}
|
|
233
|
+
/** Return a COPY of `turns` with the summary text prepended to the first user
|
|
234
|
+
* message of the first kept turn (so it reads as scene-setting on the oldest
|
|
235
|
+
* surviving question, with no extra message and no role-alternation break). */
|
|
236
|
+
function prependSummaryToTurns(turns, summary) {
|
|
237
|
+
const flat = turns.flat();
|
|
238
|
+
const idx = flat.findIndex((m) => m?.role === 'user');
|
|
239
|
+
const block = { type: 'text', text: summary };
|
|
240
|
+
if (idx < 0) {
|
|
241
|
+
// No user message survived (unusual) — fall back to a standalone leading note.
|
|
242
|
+
return [{ role: 'user', content: [block], timestamp: 0 }, ...flat];
|
|
243
|
+
}
|
|
244
|
+
const m = flat[idx];
|
|
245
|
+
const content = Array.isArray(m.content)
|
|
246
|
+
? [block, ...m.content]
|
|
247
|
+
: [block, { type: 'text', text: typeof m.content === 'string' ? m.content : '' }];
|
|
248
|
+
flat[idx] = { ...m, content };
|
|
249
|
+
return flat;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Trim the latest turn's own oversized blocks in place (on copies): drop image
|
|
253
|
+
* blocks and clip very long text blocks, prepending a note. Used only when a
|
|
254
|
+
* single turn alone exceeds budget (Tier 3) so the turn still runs instead of
|
|
255
|
+
* poisoning the session.
|
|
256
|
+
*/
|
|
257
|
+
function truncateTurn(turn, targetTokens) {
|
|
258
|
+
const note = '[Some attachments or large contents in this message were truncated because it exceeded the model context window. ' +
|
|
259
|
+
'Send less at once or split it up if you need the full content processed.]';
|
|
260
|
+
// Cumulative char budget across the WHOLE turn — not per-block. A turn with
|
|
261
|
+
// several individually-small-but-collectively-large text results (e.g. one
|
|
262
|
+
// assistant step returning many tool results) must still come in under the
|
|
263
|
+
// target; a per-block cap would let each pass and leave the turn over budget.
|
|
264
|
+
let remainingChars = Math.max(2000, Math.floor(targetTokens * CHARS_PER_TOKEN));
|
|
265
|
+
const clipText = (text) => {
|
|
266
|
+
if (remainingChars <= 0)
|
|
267
|
+
return '…[truncated]';
|
|
268
|
+
if (text.length <= remainingChars) {
|
|
269
|
+
remainingChars -= text.length;
|
|
270
|
+
return text;
|
|
271
|
+
}
|
|
272
|
+
const slice = text.slice(0, remainingChars) + '\n…[truncated]';
|
|
273
|
+
remainingChars = 0;
|
|
274
|
+
return slice;
|
|
275
|
+
};
|
|
276
|
+
// Strip images and clip text cumulatively on a copy of each message.
|
|
277
|
+
const out = turn.map((msg) => {
|
|
278
|
+
const m = msg;
|
|
279
|
+
if (Array.isArray(m.content)) {
|
|
280
|
+
const content = m.content
|
|
281
|
+
.filter((b) => b?.type !== 'image')
|
|
282
|
+
.map((b) => {
|
|
283
|
+
if (b?.type === 'text' && typeof b.text === 'string')
|
|
284
|
+
return { ...b, text: clipText(b.text) };
|
|
285
|
+
// Elide an oversized assistant tool-call argument payload (e.g. a
|
|
286
|
+
// create_artifact bundle made THIS turn, before the post-turn
|
|
287
|
+
// compaction runs). estimateMessageTokens counts these, so leaving
|
|
288
|
+
// them inline lets Tier 3 finish "truncated" yet still over budget →
|
|
289
|
+
// a provider 400. Replace the args with a marker; keep id/name so the
|
|
290
|
+
// tool_use ↔ tool_result pairing survives. Mirrors agent-pool's
|
|
291
|
+
// `{ _elided: true }` convention for create_artifact bundles.
|
|
292
|
+
if (b?.type === 'toolCall' && b.arguments && b.arguments._elided !== true) {
|
|
293
|
+
let argLen = 0;
|
|
294
|
+
try {
|
|
295
|
+
argLen = JSON.stringify(b.arguments).length;
|
|
296
|
+
}
|
|
297
|
+
catch {
|
|
298
|
+
argLen = 0;
|
|
299
|
+
}
|
|
300
|
+
if (argLen > 1000) {
|
|
301
|
+
remainingChars = Math.max(0, remainingChars - 40);
|
|
302
|
+
return { ...b, arguments: { _elided: true } };
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return b;
|
|
306
|
+
});
|
|
307
|
+
// Never emit an empty content array — the providers 400 on a tool_result
|
|
308
|
+
// (or user message) with no content. An image-only result filtered down to
|
|
309
|
+
// nothing gets a placeholder so the message — and its toolCallId pairing —
|
|
310
|
+
// survives.
|
|
311
|
+
if (content.length === 0)
|
|
312
|
+
content.push({ type: 'text', text: '[content removed to fit context]' });
|
|
313
|
+
return { ...m, content };
|
|
314
|
+
}
|
|
315
|
+
if (typeof m.content === 'string') {
|
|
316
|
+
return { ...m, content: clipText(m.content) };
|
|
317
|
+
}
|
|
318
|
+
return msg;
|
|
319
|
+
});
|
|
320
|
+
// Prepend the note to the first user message of the turn, if any.
|
|
321
|
+
const firstUserIdx = out.findIndex((m) => m?.role === 'user');
|
|
322
|
+
if (firstUserIdx >= 0) {
|
|
323
|
+
const m = out[firstUserIdx];
|
|
324
|
+
const noteBlock = { type: 'text', text: note };
|
|
325
|
+
const content = Array.isArray(m.content)
|
|
326
|
+
? [noteBlock, ...m.content]
|
|
327
|
+
: [noteBlock, { type: 'text', text: typeof m.content === 'string' ? m.content : '' }];
|
|
328
|
+
out[firstUserIdx] = { ...m, content };
|
|
329
|
+
}
|
|
330
|
+
return out;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Bring a transcript under the model's input-token budget. Non-destructive:
|
|
334
|
+
* returns a NEW array of (possibly stubbed) message copies; the caller's message
|
|
335
|
+
* objects are never mutated. Tiered:
|
|
336
|
+
* 1. stub oversized tool results (oldest first, recent turns pinned)
|
|
337
|
+
* 2. evict oldest whole turns, folding a rolling summary into the oldest kept
|
|
338
|
+
* user message
|
|
339
|
+
* 3. if the latest turn alone still overflows, truncate its oversized blocks
|
|
340
|
+
*
|
|
341
|
+
* Always pairing-safe: tool_use ↔ tool_result groupings are preserved so the
|
|
342
|
+
* provider never 400s on an orphaned tool result. Best-effort under extreme
|
|
343
|
+
* inputs: if even the truncated latest turn can't fit (e.g. enormous tool-call
|
|
344
|
+
* arguments the manager won't drop), it returns the smallest result it can and
|
|
345
|
+
* `stats.estimateAfter` reports the residual — the reactive `isContextOverflow`
|
|
346
|
+
* net (chat.ts) is the final backstop.
|
|
347
|
+
*/
|
|
348
|
+
export function manageContext(input) {
|
|
349
|
+
const { messages, systemPrompt, model, tools } = input;
|
|
350
|
+
const budget = computeBudget(model);
|
|
351
|
+
// Fixed per-request overhead (system prompt + tool schemas) — constant across
|
|
352
|
+
// all tiers, so compute once. Per-message costs are cached and kept summed so
|
|
353
|
+
// the whole pass stays O(n) instead of re-estimating the array in every loop.
|
|
354
|
+
const fixedOverhead = estimateTokens([], systemPrompt, tools);
|
|
355
|
+
const msgEst = (msg) => estimateMessageTokens(msg);
|
|
356
|
+
const perMsg = messages.map(msgEst);
|
|
357
|
+
const total = () => fixedOverhead + perMsg.reduce((a, b) => a + b, 0);
|
|
358
|
+
const estimateBefore = total();
|
|
359
|
+
const stats = {
|
|
360
|
+
budget,
|
|
361
|
+
estimateBefore,
|
|
362
|
+
estimateAfter: estimateBefore,
|
|
363
|
+
stubbedResults: 0,
|
|
364
|
+
evictedTurns: 0,
|
|
365
|
+
truncatedLatestTurn: false,
|
|
366
|
+
overBudget: false,
|
|
367
|
+
};
|
|
368
|
+
// Fast path — the overwhelming common case.
|
|
369
|
+
if (estimateBefore <= budget)
|
|
370
|
+
return { messages, stats };
|
|
371
|
+
// Parallel arrays: `working[i]` ↔ `costs[i]`. `running` tracks the live total.
|
|
372
|
+
let working = messages.slice();
|
|
373
|
+
let costs = perMsg.slice();
|
|
374
|
+
let running = estimateBefore;
|
|
375
|
+
// ── Tier 1: stub oversized tool results, oldest first, pinning recent turns ──
|
|
376
|
+
// Recent turns are normally pinned so the agent keeps its live working set
|
|
377
|
+
// inline. EXCEPTION: a single tool result that alone eats a large fraction of
|
|
378
|
+
// the budget (e.g. a 3M-token `list_assets` dump) is stubbed even when pinned —
|
|
379
|
+
// keeping one giant result inline would otherwise force Tier 2 to evict whole
|
|
380
|
+
// turns of surrounding conversation to make room, gutting the agent's task
|
|
381
|
+
// context (observed live: a 7,278-asset list collapsed context to ~48k and the
|
|
382
|
+
// agent lost the thread). The agent re-fetches from the stub on demand.
|
|
383
|
+
const GIANT_RESULT_TOKENS = Math.floor(budget * 0.33);
|
|
384
|
+
const turnsForPin = groupIntoTurns(working);
|
|
385
|
+
const pinnedFrom = Math.max(0, turnsForPin.length - PINNED_RECENT_TURNS);
|
|
386
|
+
const pinnedMsgs = new Set(turnsForPin.slice(pinnedFrom).flat());
|
|
387
|
+
for (let i = 0; i < working.length && running > budget; i++) {
|
|
388
|
+
const msg = working[i];
|
|
389
|
+
// Skip pinned recent messages UNLESS this one is a single giant result.
|
|
390
|
+
if (pinnedMsgs.has(msg) && costs[i] <= GIANT_RESULT_TOKENS)
|
|
391
|
+
continue;
|
|
392
|
+
if (isLargeToolResult(msg)) {
|
|
393
|
+
working[i] = stubToolResult(msg);
|
|
394
|
+
const newCost = msgEst(working[i]);
|
|
395
|
+
running += newCost - costs[i];
|
|
396
|
+
costs[i] = newCost;
|
|
397
|
+
stats.stubbedResults++;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
// ── Tier 2: evict oldest whole turns, keeping the pinned tail ──────────────
|
|
401
|
+
if (running > budget) {
|
|
402
|
+
const turns = groupIntoTurns(working);
|
|
403
|
+
// Cost per turn, aligned with `turns` order (turns partition `working`).
|
|
404
|
+
let offset = 0;
|
|
405
|
+
const turnCosts = turns.map((t) => {
|
|
406
|
+
let c = 0;
|
|
407
|
+
for (let k = 0; k < t.length; k++)
|
|
408
|
+
c += costs[offset + k];
|
|
409
|
+
offset += t.length;
|
|
410
|
+
return c;
|
|
411
|
+
});
|
|
412
|
+
const evicted = [];
|
|
413
|
+
let firstKept = 0; // index of the first turn still kept
|
|
414
|
+
// Evict oldest first, keeping at least the single latest turn (Tier 3 then
|
|
415
|
+
// truncates that turn if it alone still overflows). The PINNED_RECENT_TURNS
|
|
416
|
+
// preference only matters when already under budget — and there we never
|
|
417
|
+
// enter this loop — so the hard floor here is 1, not PINNED_RECENT_TURNS;
|
|
418
|
+
// otherwise a few large recent turns could exceed a small budget with no way
|
|
419
|
+
// to recover.
|
|
420
|
+
while (turns.length - firstKept > 1 && running > budget) {
|
|
421
|
+
evicted.push(turns[firstKept]);
|
|
422
|
+
running -= turnCosts[firstKept];
|
|
423
|
+
firstKept++;
|
|
424
|
+
stats.evictedTurns++;
|
|
425
|
+
}
|
|
426
|
+
if (evicted.length > 0) {
|
|
427
|
+
const keptTurns = turns.slice(firstKept);
|
|
428
|
+
working = prependSummaryToTurns(keptTurns, buildSummaryText(evicted));
|
|
429
|
+
costs = working.map(msgEst);
|
|
430
|
+
running = fixedOverhead + costs.reduce((a, b) => a + b, 0);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
// ── Tier 3: latest turn alone still overflows — truncate its own bulk ──────
|
|
434
|
+
if (running > budget) {
|
|
435
|
+
const turns = groupIntoTurns(working);
|
|
436
|
+
if (turns.length > 0) {
|
|
437
|
+
const lastIdx = turns.length - 1;
|
|
438
|
+
const before = turns.slice(0, lastIdx).flat();
|
|
439
|
+
const beforeTokens = fixedOverhead + before.map(msgEst).reduce((a, b) => a + b, 0);
|
|
440
|
+
// Room left for the latest turn after everything kept before it. Floor at
|
|
441
|
+
// 256 so a large `before` can't push the target so high that truncateTurn's
|
|
442
|
+
// own minimum-keep defeats the truncation entirely.
|
|
443
|
+
const target = Math.max(budget - beforeTokens, 256);
|
|
444
|
+
turns[lastIdx] = truncateTurn(turns[lastIdx], target);
|
|
445
|
+
working = turns.flat();
|
|
446
|
+
costs = working.map(msgEst);
|
|
447
|
+
running = fixedOverhead + costs.reduce((a, b) => a + b, 0);
|
|
448
|
+
stats.truncatedLatestTurn = true;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
stats.estimateAfter = running;
|
|
452
|
+
// Terminal guard: all tiers ran and we're STILL over budget — the irreducible
|
|
453
|
+
// cost (systemPrompt + tool schemas, or one unshrinkable message) exceeds the
|
|
454
|
+
// budget. Nothing more to trim here; flag it so the caller can log it (not a
|
|
455
|
+
// silent failure) and the reactive net handles the likely 400.
|
|
456
|
+
stats.overBudget = running > budget;
|
|
457
|
+
return { messages: working, stats };
|
|
458
|
+
}
|
|
459
|
+
//# sourceMappingURL=context-window.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-window.js","sourceRoot":"","sources":["../../src/agent/context-window.ts"],"names":[],"mappings":"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"}
|
|
@@ -171,6 +171,13 @@ export declare class AgentOrchestrator {
|
|
|
171
171
|
provider: string;
|
|
172
172
|
modelId: string;
|
|
173
173
|
};
|
|
174
|
+
/**
|
|
175
|
+
* The active chat model's context window (max prompt tokens), or null if it
|
|
176
|
+
* can't be resolved. Used by the chat route's reactive overflow detection
|
|
177
|
+
* (isContextOverflow needs the window to catch silent overflow). Mirrors the
|
|
178
|
+
* model resolution in createAgentInstance, including the Credits gateway.
|
|
179
|
+
*/
|
|
180
|
+
getContextWindow(): number | null;
|
|
174
181
|
/** Can the active chat model accept image input? Drives the composer's
|
|
175
182
|
* vision-attach affordance (disable + tooltip when false). The Studiograph
|
|
176
183
|
* Credits gateway is vision-capable (A6); otherwise resolve from the model. */
|
|
@@ -38,6 +38,7 @@ import { loadUserConfig, resolveApiKey, resolveProvider, resolveModelId } from '
|
|
|
38
38
|
import { supportsVision } from '../services/model-capabilities.js';
|
|
39
39
|
import { onSecretMutation } from '../core/secrets/index.js';
|
|
40
40
|
import { AgentPool } from './agent-pool.js';
|
|
41
|
+
import { manageContext } from './context-window.js';
|
|
41
42
|
import { addInferenceUsage, createCloudInferenceStreamFn, createInferenceUsageSummary, isStudiographCreditsMode, studiographCreditsModel, } from '../services/cloud-inference-billing.js';
|
|
42
43
|
import { recordAiUsage } from '../services/ai-usage-ledger.js';
|
|
43
44
|
export class AgentOrchestrator {
|
|
@@ -253,8 +254,59 @@ export class AgentOrchestrator {
|
|
|
253
254
|
bundledSeen.add(name);
|
|
254
255
|
dedupedTools.push(t);
|
|
255
256
|
}
|
|
257
|
+
// Proactive context-window manager. pi-agent-core calls `transformContext`
|
|
258
|
+
// once before every LLM request, non-destructively producing the message
|
|
259
|
+
// array sent to the provider (the persistent transcript in
|
|
260
|
+
// agent.state.messages — saved to disk / shown in the UI — is untouched).
|
|
261
|
+
// We estimate tokens and, when a request would overflow the model's window,
|
|
262
|
+
// stub oversized tool results / evict oldest turns / truncate an over-large
|
|
263
|
+
// latest turn so the request fits — instead of letting the provider 400 the
|
|
264
|
+
// whole conversation. The estimator is deliberately conservative (see
|
|
265
|
+
// context-window.ts); the hook MUST NOT throw (runtime contract), so on any
|
|
266
|
+
// error we fall back to the untrimmed messages.
|
|
267
|
+
const transformContext = async (messages) => {
|
|
268
|
+
try {
|
|
269
|
+
const { messages: out, stats } = manageContext({
|
|
270
|
+
messages,
|
|
271
|
+
systemPrompt,
|
|
272
|
+
model: model,
|
|
273
|
+
// Use the LIVE tool list, not the captured `dedupedTools`: per-user
|
|
274
|
+
// connector + custom tools are appended to `agent.state.tools` by
|
|
275
|
+
// loadCustomToolsAsync AFTER construction and ride in every request,
|
|
276
|
+
// so estimating with the initial set would undercount the schema
|
|
277
|
+
// overhead and let a near-limit prompt skip trimming. `agent` is
|
|
278
|
+
// assigned just below; this closure only runs at request time, by
|
|
279
|
+
// which point it's initialized.
|
|
280
|
+
tools: agent.state.tools,
|
|
281
|
+
});
|
|
282
|
+
// Log whenever we're at/over budget or actually trimmed — rare path,
|
|
283
|
+
// high signal for ops/QA. (Quiet on the common under-budget fast path.)
|
|
284
|
+
if (stats.estimateBefore > stats.budget || stats.stubbedResults || stats.evictedTurns || stats.truncatedLatestTurn) {
|
|
285
|
+
// eslint-disable-next-line no-console
|
|
286
|
+
console.warn(`[ctx-window] before=${stats.estimateBefore} after=${stats.estimateAfter} budget=${stats.budget} ` +
|
|
287
|
+
`msgs=${messages.length} stubbed=${stats.stubbedResults} evicted=${stats.evictedTurns} truncated=${stats.truncatedLatestTurn}` +
|
|
288
|
+
(stats.overBudget ? ' OVER-BUDGET-AFTER-TRIM' : ''));
|
|
289
|
+
}
|
|
290
|
+
// Distinct, loud signal for the irreducible case (systemPrompt + tool
|
|
291
|
+
// schemas alone exceed budget): everything trimmed yet still over. The
|
|
292
|
+
// reactive isContextOverflow net will surface the 400 to the user; this
|
|
293
|
+
// tells ops WHY (too many/large tools, not a runaway transcript).
|
|
294
|
+
if (stats.overBudget) {
|
|
295
|
+
// eslint-disable-next-line no-console
|
|
296
|
+
console.error(`[ctx-window] STILL OVER BUDGET after full trim (after=${stats.estimateAfter} > budget=${stats.budget}). ` +
|
|
297
|
+
`Likely systemPrompt + tool schemas exceed the window (tools=${agent.state.tools.length}). Request may 400.`);
|
|
298
|
+
}
|
|
299
|
+
return out;
|
|
300
|
+
}
|
|
301
|
+
catch (err) {
|
|
302
|
+
// eslint-disable-next-line no-console
|
|
303
|
+
console.error('[ctx-window] manageContext threw — sending UNTRIMMED messages:', err instanceof Error ? err.stack || err.message : err);
|
|
304
|
+
return messages;
|
|
305
|
+
}
|
|
306
|
+
};
|
|
256
307
|
const agent = new Agent({
|
|
257
308
|
getApiKey: () => apiKey,
|
|
309
|
+
transformContext,
|
|
258
310
|
// `thinkingLevel: 'medium'` turns on extended thinking so the runtime
|
|
259
311
|
// emits thinking_delta events. The chat stream relays those as a
|
|
260
312
|
// single-line "what the agent is thinking" status (replacing the pulsing
|
|
@@ -773,6 +825,25 @@ ${repoList}`;
|
|
|
773
825
|
modelId: resolveModelId(userConfig, this.config),
|
|
774
826
|
};
|
|
775
827
|
}
|
|
828
|
+
/**
|
|
829
|
+
* The active chat model's context window (max prompt tokens), or null if it
|
|
830
|
+
* can't be resolved. Used by the chat route's reactive overflow detection
|
|
831
|
+
* (isContextOverflow needs the window to catch silent overflow). Mirrors the
|
|
832
|
+
* model resolution in createAgentInstance, including the Credits gateway.
|
|
833
|
+
*/
|
|
834
|
+
getContextWindow() {
|
|
835
|
+
try {
|
|
836
|
+
if (isStudiographCreditsMode()) {
|
|
837
|
+
return studiographCreditsModel().contextWindow ?? null;
|
|
838
|
+
}
|
|
839
|
+
const { provider, modelId } = this.getModelSelection();
|
|
840
|
+
const model = getModel(provider, modelId);
|
|
841
|
+
return model?.contextWindow ?? null;
|
|
842
|
+
}
|
|
843
|
+
catch {
|
|
844
|
+
return null;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
776
847
|
/** Can the active chat model accept image input? Drives the composer's
|
|
777
848
|
* vision-attach affordance (disable + tooltip when false). The Studiograph
|
|
778
849
|
* Credits gateway is vision-capable (A6); otherwise resolve from the model. */
|