@astralyn/pi 1.4.6 → 1.4.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (253) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/bundle/chunks/{chunk-EYKC6ESU.js → chunk-5VIOGR76.js} +1 -1
  3. package/dist/bundle/chunks/{chunk-KDO6L76C.js → chunk-FODCTZXW.js} +4 -4
  4. package/dist/bundle/chunks/{chunk-RBTJBQWX.js → chunk-I2COK45I.js} +136 -137
  5. package/dist/bundle/chunks/{chunk-AXFWZIHH.js → chunk-MG5XDTXV.js} +1 -1
  6. package/dist/bundle/chunks/{chunk-ALHAFUHP.js → chunk-PU2IWWXV.js} +1 -1
  7. package/dist/bundle/chunks/{chunk-5M3YJ54Z.js → chunk-RO2VH6UZ.js} +1 -1
  8. package/dist/bundle/chunks/{cli-FH77R7JR.js → cli-MEL7SGJD.js} +1 -1
  9. package/dist/bundle/chunks/{execute-5ZQUS52M.js → execute-4CWRNHPO.js} +1 -1
  10. package/dist/bundle/chunks/{runtime-R5CMHJ5P.js → runtime-R3X67XMR.js} +1 -1
  11. package/dist/bundle/chunks/{virtual-modules-KCQU7LOC.js → virtual-modules-BBLD7YLS.js} +1 -1
  12. package/dist/bundle/cli.js +1 -1
  13. package/dist/bundle/index.js +1 -1
  14. package/dist/bundle/rpc-entry.js +1 -1
  15. package/dist/core/agent-session.d.ts +0 -3
  16. package/dist/core/agent-session.d.ts.map +1 -1
  17. package/dist/core/agent-session.js +0 -4
  18. package/dist/core/agent-session.js.map +1 -1
  19. package/dist/core/extensions/index.d.ts +1 -1
  20. package/dist/core/extensions/index.d.ts.map +1 -1
  21. package/dist/core/extensions/index.js.map +1 -1
  22. package/dist/core/package-manager.d.ts +3 -1
  23. package/dist/core/package-manager.d.ts.map +1 -1
  24. package/dist/core/package-manager.js +10 -0
  25. package/dist/core/package-manager.js.map +1 -1
  26. package/dist/core/resource-loader.d.ts +2 -0
  27. package/dist/core/resource-loader.d.ts.map +1 -1
  28. package/dist/core/resource-loader.js +17 -4
  29. package/dist/core/resource-loader.js.map +1 -1
  30. package/dist/core/settings-manager.d.ts +4 -2
  31. package/dist/core/settings-manager.d.ts.map +1 -1
  32. package/dist/core/settings-manager.js +11 -0
  33. package/dist/core/settings-manager.js.map +1 -1
  34. package/dist/core/settings-schema.d.ts +7 -0
  35. package/dist/core/settings-schema.d.ts.map +1 -1
  36. package/dist/core/settings-schema.js +6 -0
  37. package/dist/core/settings-schema.js.map +1 -1
  38. package/dist/core/skill-invocation.d.ts +46 -0
  39. package/dist/core/skill-invocation.d.ts.map +1 -0
  40. package/dist/core/skill-invocation.js +128 -0
  41. package/dist/core/skill-invocation.js.map +1 -0
  42. package/dist/core/skills.d.ts +10 -0
  43. package/dist/core/skills.d.ts.map +1 -1
  44. package/dist/core/skills.js +23 -0
  45. package/dist/core/skills.js.map +1 -1
  46. package/dist/core/tasks/delivery.d.ts +11 -4
  47. package/dist/core/tasks/delivery.d.ts.map +1 -1
  48. package/dist/core/tasks/delivery.js +9 -9
  49. package/dist/core/tasks/delivery.js.map +1 -1
  50. package/dist/core/tasks/execution.d.ts +10 -3
  51. package/dist/core/tasks/execution.d.ts.map +1 -1
  52. package/dist/core/tasks/execution.js +72 -52
  53. package/dist/core/tasks/execution.js.map +1 -1
  54. package/dist/core/tasks/format.d.ts +0 -2
  55. package/dist/core/tasks/format.d.ts.map +1 -1
  56. package/dist/core/tasks/format.js +1 -16
  57. package/dist/core/tasks/format.js.map +1 -1
  58. package/dist/core/tasks/limits.d.ts +12 -1
  59. package/dist/core/tasks/limits.d.ts.map +1 -1
  60. package/dist/core/tasks/limits.js +20 -1
  61. package/dist/core/tasks/limits.js.map +1 -1
  62. package/dist/core/tasks/output.d.ts +6 -12
  63. package/dist/core/tasks/output.d.ts.map +1 -1
  64. package/dist/core/tasks/output.js +35 -32
  65. package/dist/core/tasks/output.js.map +1 -1
  66. package/dist/core/tasks/record.d.ts +45 -0
  67. package/dist/core/tasks/record.d.ts.map +1 -0
  68. package/dist/core/tasks/record.js +80 -0
  69. package/dist/core/tasks/record.js.map +1 -0
  70. package/dist/core/tasks/runtime.d.ts +14 -14
  71. package/dist/core/tasks/runtime.d.ts.map +1 -1
  72. package/dist/core/tasks/runtime.js +111 -93
  73. package/dist/core/tasks/runtime.js.map +1 -1
  74. package/dist/core/tasks/session.d.ts +0 -1
  75. package/dist/core/tasks/session.d.ts.map +1 -1
  76. package/dist/core/tasks/session.js +7 -8
  77. package/dist/core/tasks/session.js.map +1 -1
  78. package/dist/core/tasks/store.d.ts +18 -25
  79. package/dist/core/tasks/store.d.ts.map +1 -1
  80. package/dist/core/tasks/store.js +42 -40
  81. package/dist/core/tasks/store.js.map +1 -1
  82. package/dist/core/tasks/types.d.ts +96 -17
  83. package/dist/core/tasks/types.d.ts.map +1 -1
  84. package/dist/core/tasks/types.js.map +1 -1
  85. package/dist/core/tools/shell-execution.d.ts +8 -7
  86. package/dist/core/tools/shell-execution.d.ts.map +1 -1
  87. package/dist/core/tools/shell-execution.js +18 -26
  88. package/dist/core/tools/shell-execution.js.map +1 -1
  89. package/dist/core/tools/shell-tool.d.ts.map +1 -1
  90. package/dist/core/tools/shell-tool.js +31 -29
  91. package/dist/core/tools/shell-tool.js.map +1 -1
  92. package/dist/core/usage-totals.d.ts +3 -0
  93. package/dist/core/usage-totals.d.ts.map +1 -1
  94. package/dist/core/usage-totals.js +15 -0
  95. package/dist/core/usage-totals.js.map +1 -1
  96. package/dist/extensions/agent/progress.d.ts.map +1 -1
  97. package/dist/extensions/agent/progress.js +1 -11
  98. package/dist/extensions/agent/progress.js.map +1 -1
  99. package/dist/extensions/agent/settings-host.d.ts +33 -0
  100. package/dist/extensions/agent/settings-host.d.ts.map +1 -0
  101. package/dist/extensions/agent/settings-host.js +75 -0
  102. package/dist/extensions/agent/settings-host.js.map +1 -0
  103. package/dist/extensions/agent/settings.d.ts.map +1 -1
  104. package/dist/extensions/agent/settings.js +18 -7
  105. package/dist/extensions/agent/settings.js.map +1 -1
  106. package/dist/extensions/agent/tool.d.ts.map +1 -1
  107. package/dist/extensions/agent/tool.js +9 -5
  108. package/dist/extensions/agent/tool.js.map +1 -1
  109. package/dist/extensions/agent/worker.d.ts.map +1 -1
  110. package/dist/extensions/agent/worker.js +3 -5
  111. package/dist/extensions/agent/worker.js.map +1 -1
  112. package/dist/extensions/btw/agent.d.ts +1 -1
  113. package/dist/extensions/btw/agent.d.ts.map +1 -1
  114. package/dist/extensions/btw/agent.js +3 -20
  115. package/dist/extensions/btw/agent.js.map +1 -1
  116. package/dist/extensions/cached-compaction/index.d.ts.map +1 -1
  117. package/dist/extensions/cached-compaction/index.js +3 -22
  118. package/dist/extensions/cached-compaction/index.js.map +1 -1
  119. package/dist/extensions/cached-compaction/render.d.ts +1 -0
  120. package/dist/extensions/cached-compaction/render.d.ts.map +1 -1
  121. package/dist/extensions/cached-compaction/render.js +42 -14
  122. package/dist/extensions/cached-compaction/render.js.map +1 -1
  123. package/dist/extensions/deepwiki/client.d.ts.map +1 -1
  124. package/dist/extensions/deepwiki/client.js +6 -78
  125. package/dist/extensions/deepwiki/client.js.map +1 -1
  126. package/dist/extensions/deepwiki/transport.d.ts +3 -0
  127. package/dist/extensions/deepwiki/transport.d.ts.map +1 -0
  128. package/dist/extensions/deepwiki/transport.js +96 -0
  129. package/dist/extensions/deepwiki/transport.js.map +1 -0
  130. package/dist/extensions/provider/ui/builtin-data.d.ts +3 -3
  131. package/dist/extensions/provider/ui/builtin-data.d.ts.map +1 -1
  132. package/dist/extensions/provider/ui/builtin-data.js.map +1 -1
  133. package/dist/extensions/provider/ui/compat.d.ts +3 -3
  134. package/dist/extensions/provider/ui/compat.d.ts.map +1 -1
  135. package/dist/extensions/provider/ui/compat.js.map +1 -1
  136. package/dist/extensions/provider/ui/controls.d.ts +2 -2
  137. package/dist/extensions/provider/ui/controls.d.ts.map +1 -1
  138. package/dist/extensions/provider/ui/controls.js.map +1 -1
  139. package/dist/extensions/provider/ui/dialogs.d.ts +3 -3
  140. package/dist/extensions/provider/ui/dialogs.d.ts.map +1 -1
  141. package/dist/extensions/provider/ui/dialogs.js.map +1 -1
  142. package/dist/extensions/provider/ui/dictionary.d.ts +2 -2
  143. package/dist/extensions/provider/ui/dictionary.d.ts.map +1 -1
  144. package/dist/extensions/provider/ui/dictionary.js.map +1 -1
  145. package/dist/extensions/provider/ui/editor.d.ts.map +1 -1
  146. package/dist/extensions/provider/ui/editor.js +1 -3
  147. package/dist/extensions/provider/ui/editor.js.map +1 -1
  148. package/dist/extensions/provider/ui/fetch-models.d.ts +2 -2
  149. package/dist/extensions/provider/ui/fetch-models.d.ts.map +1 -1
  150. package/dist/extensions/provider/ui/fetch-models.js.map +1 -1
  151. package/dist/extensions/provider/ui/model-fields.d.ts +2 -2
  152. package/dist/extensions/provider/ui/model-fields.d.ts.map +1 -1
  153. package/dist/extensions/provider/ui/model-fields.js.map +1 -1
  154. package/dist/extensions/provider/ui/model-options.d.ts +6 -6
  155. package/dist/extensions/provider/ui/model-options.d.ts.map +1 -1
  156. package/dist/extensions/provider/ui/model-options.js.map +1 -1
  157. package/dist/extensions/provider/ui/pane.d.ts +21 -12
  158. package/dist/extensions/provider/ui/pane.d.ts.map +1 -1
  159. package/dist/extensions/provider/ui/pane.js.map +1 -1
  160. package/dist/extensions/provider/ui/provider-fields.d.ts +3 -3
  161. package/dist/extensions/provider/ui/provider-fields.d.ts.map +1 -1
  162. package/dist/extensions/provider/ui/provider-fields.js.map +1 -1
  163. package/dist/extensions/provider/ui/thinking-map.d.ts +2 -2
  164. package/dist/extensions/provider/ui/thinking-map.d.ts.map +1 -1
  165. package/dist/extensions/provider/ui/thinking-map.js.map +1 -1
  166. package/dist/extensions/run-summary/format.js +1 -1
  167. package/dist/extensions/run-summary/format.js.map +1 -1
  168. package/dist/extensions/statusline/usage.d.ts.map +1 -1
  169. package/dist/extensions/statusline/usage.js +2 -6
  170. package/dist/extensions/statusline/usage.js.map +1 -1
  171. package/dist/extensions/tasks/tool/actions.d.ts +1 -1
  172. package/dist/extensions/tasks/tool/actions.d.ts.map +1 -1
  173. package/dist/extensions/tasks/tool/actions.js +49 -106
  174. package/dist/extensions/tasks/tool/actions.js.map +1 -1
  175. package/dist/extensions/tasks/tool/constants.d.ts +1 -6
  176. package/dist/extensions/tasks/tool/constants.d.ts.map +1 -1
  177. package/dist/extensions/tasks/tool/constants.js +1 -6
  178. package/dist/extensions/tasks/tool/constants.js.map +1 -1
  179. package/dist/extensions/tasks/tool/schema.d.ts +0 -3
  180. package/dist/extensions/tasks/tool/schema.d.ts.map +1 -1
  181. package/dist/extensions/tasks/tool/schema.js +4 -13
  182. package/dist/extensions/tasks/tool/schema.js.map +1 -1
  183. package/dist/extensions/tasks/tool.d.ts.map +1 -1
  184. package/dist/extensions/tasks/tool.js +2 -2
  185. package/dist/extensions/tasks/tool.js.map +1 -1
  186. package/dist/extensions/tasks/ui/completion-render.d.ts.map +1 -1
  187. package/dist/extensions/tasks/ui/completion-render.js +31 -33
  188. package/dist/extensions/tasks/ui/completion-render.js.map +1 -1
  189. package/dist/extensions/tasks/ui/layout.d.ts +7 -7
  190. package/dist/extensions/tasks/ui/layout.d.ts.map +1 -1
  191. package/dist/extensions/tasks/ui/layout.js +17 -19
  192. package/dist/extensions/tasks/ui/layout.js.map +1 -1
  193. package/dist/extensions/tasks/ui/list.d.ts.map +1 -1
  194. package/dist/extensions/tasks/ui/list.js +2 -3
  195. package/dist/extensions/tasks/ui/list.js.map +1 -1
  196. package/dist/extensions/tasks/ui/manager.d.ts +16 -7
  197. package/dist/extensions/tasks/ui/manager.d.ts.map +1 -1
  198. package/dist/extensions/tasks/ui/manager.js +163 -131
  199. package/dist/extensions/tasks/ui/manager.js.map +1 -1
  200. package/dist/extensions/tasks/ui/model.d.ts +25 -0
  201. package/dist/extensions/tasks/ui/model.d.ts.map +1 -1
  202. package/dist/extensions/tasks/ui/model.js +4 -0
  203. package/dist/extensions/tasks/ui/model.js.map +1 -1
  204. package/dist/extensions/tasks/ui/render.d.ts +1 -1
  205. package/dist/extensions/tasks/ui/render.d.ts.map +1 -1
  206. package/dist/extensions/tasks/ui/render.js +11 -7
  207. package/dist/extensions/tasks/ui/render.js.map +1 -1
  208. package/dist/extensions/tasks/ui/status.d.ts +15 -0
  209. package/dist/extensions/tasks/ui/status.d.ts.map +1 -0
  210. package/dist/extensions/tasks/ui/status.js +16 -0
  211. package/dist/extensions/tasks/ui/status.js.map +1 -0
  212. package/dist/index.d.ts +1 -1
  213. package/dist/index.d.ts.map +1 -1
  214. package/dist/index.js.map +1 -1
  215. package/dist/modes/interactive/components/config-selector.d.ts +17 -2
  216. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  217. package/dist/modes/interactive/components/config-selector.js +199 -18
  218. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  219. package/dist/modes/interactive/components/status-marker.d.ts +2 -1
  220. package/dist/modes/interactive/components/status-marker.d.ts.map +1 -1
  221. package/dist/modes/interactive/components/status-marker.js +0 -1
  222. package/dist/modes/interactive/components/status-marker.js.map +1 -1
  223. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  224. package/dist/modes/interactive/interactive-mode.js +7 -1
  225. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  226. package/dist/utils/duration.d.ts +3 -0
  227. package/dist/utils/duration.d.ts.map +1 -0
  228. package/dist/utils/duration.js +17 -0
  229. package/dist/utils/duration.js.map +1 -0
  230. package/dist/utils/number.d.ts +3 -0
  231. package/dist/utils/number.d.ts.map +1 -0
  232. package/dist/utils/number.js +5 -0
  233. package/dist/utils/number.js.map +1 -0
  234. package/docs/bundled/extensions/agent.md +3 -3
  235. package/docs/bundled/extensions/cached-compaction.md +13 -4
  236. package/docs/bundled/extensions/deepwiki.md +3 -1
  237. package/docs/extensions.md +5 -5
  238. package/docs/packages.md +5 -3
  239. package/docs/sdk.md +1 -1
  240. package/docs/session-format.md +1 -1
  241. package/docs/settings.md +11 -0
  242. package/docs/skills.md +23 -0
  243. package/npm-shrinkwrap.json +2 -2
  244. package/package.json +1 -1
  245. package/schemas/settings.schema.json +36 -0
  246. package/dist/extensions/deepwiki/http.d.ts +0 -13
  247. package/dist/extensions/deepwiki/http.d.ts.map +0 -1
  248. package/dist/extensions/deepwiki/http.js +0 -88
  249. package/dist/extensions/deepwiki/http.js.map +0 -1
  250. package/dist/extensions/tasks/ui/task-view.d.ts +0 -8
  251. package/dist/extensions/tasks/ui/task-view.d.ts.map +0 -1
  252. package/dist/extensions/tasks/ui/task-view.js +0 -13
  253. package/dist/extensions/tasks/ui/task-view.js.map +0 -1
@@ -1,18 +1,9 @@
1
1
  import { Agent, } from "@earendil-works/pi-agent-core";
2
2
  import { normalizeContext } from "@earendil-works/pi-ai";
3
3
  import { estimateContextTokens } from "@earendil-works/pi-ai/utils/estimate";
4
+ import { cacheHitPercent, combineUsage, emptyUsage } from "../../core/usage-totals.js";
4
5
  import { MAX_DISPLAY_CHARS, MAX_MODEL_STEPS, MAX_OUTPUT_RESERVE_TOKENS, MAX_QUESTION_CHARS, MAX_QUESTIONS, MAX_RESPONSE_CHARS, TOOL_DENIAL, } from "./constants.js";
5
6
  import { createBtwMessages } from "./snapshot.js";
6
- function emptyUsage() {
7
- return {
8
- input: 0,
9
- output: 0,
10
- cacheRead: 0,
11
- cacheWrite: 0,
12
- totalTokens: 0,
13
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
14
- };
15
- }
16
7
  function displayText(text) {
17
8
  return text.length <= MAX_DISPLAY_CHARS ? text : `${text.slice(0, MAX_DISPLAY_CHARS)}\n\n[Display truncated]`;
18
9
  }
@@ -186,16 +177,8 @@ export class BtwAgent {
186
177
  ? part.thinking.length
187
178
  : JSON.stringify(part.arguments).length), 0);
188
179
  this.blockedTools += message.content.filter((part) => part.type === "toolCall").length;
189
- const usage = message.usage;
190
- const promptTokens = usage.input + usage.cacheRead + usage.cacheWrite;
191
- this.latestCacheHitPercent =
192
- (usage.cacheRead > 0 || usage.cacheWrite > 0) && promptTokens > 0
193
- ? (usage.cacheRead / promptTokens) * 100
194
- : undefined;
195
- for (const key of ["input", "output", "cacheRead", "cacheWrite", "totalTokens"])
196
- this.usage[key] += usage[key];
197
- for (const key of ["input", "output", "cacheRead", "cacheWrite", "total"])
198
- this.usage.cost[key] += usage.cost[key];
180
+ this.latestCacheHitPercent = cacheHitPercent(message.usage);
181
+ this.usage = combineUsage(this.usage, message.usage);
199
182
  if (message.stopReason === "error" || message.stopReason === "aborted") {
200
183
  turn.status = message.stopReason === "aborted" ? "cancelled" : "error";
201
184
  turn.notice ??=
@@ -1 +1 @@
1
- {"version":3,"file":"agent.js","sourceRoot":"","sources":["../../../src/extensions/btw/agent.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\tAgent,\n\ttype AgentEvent,\n\ttype AgentMessage,\n\ttype AgentTool,\n\ttype AgentTurnDecision,\n} from \"@earendil-works/pi-agent-core\";\nimport type { AssistantMessage, Message, Models, Usage } from \"@earendil-works/pi-ai\";\nimport { normalizeContext } from \"@earendil-works/pi-ai\";\nimport { estimateContextTokens } from \"@earendil-works/pi-ai/utils/estimate\";\nimport type { ContextSnapshot } from \"../../core/extensions/index.ts\";\nimport {\n\tMAX_DISPLAY_CHARS,\n\tMAX_MODEL_STEPS,\n\tMAX_OUTPUT_RESERVE_TOKENS,\n\tMAX_QUESTION_CHARS,\n\tMAX_QUESTIONS,\n\tMAX_RESPONSE_CHARS,\n\tTOOL_DENIAL,\n} from \"./constants.ts\";\nimport { createBtwMessages } from \"./snapshot.ts\";\n\nexport interface BtwTurn {\n\tquestion: string;\n\tanswer: string;\n\tthinking: string;\n\tstatus: \"streaming\" | \"cancelling\" | \"done\" | \"cancelled\" | \"error\";\n\tnotice?: string;\n\tstartedAt: number;\n\telapsedMs: number;\n\tfirstAnswerMs?: number;\n}\n\nexport interface BtwConversationState {\n\tbusy: boolean;\n\tturns: readonly BtwTurn[];\n\tusage: Usage;\n\tlatestCacheHitPercent: number | undefined;\n\tblockedTools: number;\n}\n\ninterface ActiveQuestion {\n\tturn: BtwTurn;\n\tsteps: number;\n\tcompletedAnswer: string;\n\tresponseChars: number;\n}\n\nfunction emptyUsage(): Usage {\n\treturn {\n\t\tinput: 0,\n\t\toutput: 0,\n\t\tcacheRead: 0,\n\t\tcacheWrite: 0,\n\t\ttotalTokens: 0,\n\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t};\n}\n\nfunction displayText(text: string): string {\n\treturn text.length <= MAX_DISPLAY_CHARS ? text : `${text.slice(0, MAX_DISPLAY_CHARS)}\\n\\n[Display truncated]`;\n}\n\n/** An in-memory native Agent. No SessionManager, real tool executors, or main-agent signal. */\nexport class BtwAgent implements BtwConversationState {\n\treadonly turns: BtwTurn[] = [];\n\treadonly usage = emptyUsage();\n\tlatestCacheHitPercent: number | undefined;\n\tblockedTools = 0;\n\tprivate readonly agent: Agent;\n\tprivate readonly unsubscribe: () => void;\n\tprivate readonly onChange: () => void;\n\tprivate readonly inputBudget: number;\n\tprivate active?: ActiveQuestion;\n\tprivate disposed = false;\n\n\tconstructor(snapshot: ContextSnapshot, runtime: Pick<Models, \"streamSimple\">, onChange: () => void) {\n\t\tthis.onChange = onChange;\n\t\tconst outputReserve = Math.min(\n\t\t\tMAX_OUTPUT_RESERVE_TOKENS,\n\t\t\tsnapshot.model.maxTokens,\n\t\t\tMath.max(1024, Math.floor(snapshot.model.contextWindow / 8)),\n\t\t);\n\t\t// Leave room for the answer and estimation error; never trim the inherited prefix.\n\t\tthis.inputBudget = snapshot.model.contextWindow - outputReserve - 4096;\n\t\tconst tools: AgentTool[] = snapshot.tools.map((tool) => ({\n\t\t\t...structuredClone(tool),\n\t\t\tlabel: tool.name,\n\t\t\texecute: async () => {\n\t\t\t\t// Defense in depth: these objects never receive an original execute function.\n\t\t\t\tthrow new Error(TOOL_DENIAL);\n\t\t\t},\n\t\t}));\n\t\tthis.agent = new Agent({\n\t\t\tinitialState: {\n\t\t\t\tmodel: snapshot.model,\n\t\t\t\tthinkingLevel: snapshot.thinkingLevel,\n\t\t\t\tsystemPrompt: snapshot.systemPrompt,\n\t\t\t\ttools,\n\t\t\t\tmessages: createBtwMessages(snapshot),\n\t\t\t},\n\t\t\tsessionId: snapshot.streamOptions.sessionId ?? snapshot.sessionId,\n\t\t\ttransport: snapshot.streamOptions.transport,\n\t\t\tthinkingBudgets: snapshot.streamOptions.thinkingBudgets,\n\t\t\tmaxRetryDelayMs: snapshot.streamOptions.maxRetryDelayMs,\n\t\t\tonPayload: snapshot.streamOptions.onPayload,\n\t\t\tonResponse: snapshot.streamOptions.onResponse,\n\t\t\tstreamFn: (model, context, options) =>\n\t\t\t\t// Keep the parent's generation settings as well as its message prefix.\n\t\t\t\t// The model's native output ceiling and our response-size guard still bound output.\n\t\t\t\truntime.streamSimple(model, context, { ...snapshot.streamOptions, ...options }),\n\t\t\tbeforeToolCall: async () => ({ block: true, reason: TOOL_DENIAL }),\n\t\t\tfinishTurn: ({ message, toolResults, context }): AgentTurnDecision | undefined => {\n\t\t\t\tconst question = this.active;\n\t\t\t\t// Error and aborted responses are hard exits; the step budget only counts answered turns.\n\t\t\t\tif (!question || this.disposed || message.stopReason === \"error\" || message.stopReason === \"aborted\")\n\t\t\t\t\treturn undefined;\n\t\t\t\tquestion.steps++;\n\t\t\t\tif (!toolResults.length) return undefined;\n\t\t\t\tif (question.steps >= MAX_MODEL_STEPS || !this.fits(context.messages)) {\n\t\t\t\t\tquestion.turn.status = \"error\";\n\t\t\t\t\tquestion.turn.notice =\n\t\t\t\t\t\tquestion.steps >= MAX_MODEL_STEPS\n\t\t\t\t\t\t\t? \"Stopped after repeated tool requests. BTW can only answer from its snapshot.\"\n\t\t\t\t\t\t\t: \"Context limit reached. Reopen /btw for a new conversation.\";\n\t\t\t\t\treturn { action: \"end\" };\n\t\t\t\t}\n\t\t\t\treturn undefined;\n\t\t\t},\n\t\t});\n\t\tthis.unsubscribe = this.agent.subscribe((event) => this.handleEvent(event));\n\t}\n\n\tget busy(): boolean {\n\t\treturn this.active !== undefined;\n\t}\n\n\tprivate validate(text: string): string | undefined {\n\t\tif (this.disposed) return \"This BTW conversation is closed.\";\n\t\tif (this.busy) return \"BTW is still answering. Your question is kept in the editor.\";\n\t\tif (this.turns.length >= MAX_QUESTIONS) return \"BTW conversation limit reached. Reopen /btw to start a new one.\";\n\t\tif (text.length > MAX_QUESTION_CHARS)\n\t\t\treturn `BTW questions are limited to ${MAX_QUESTION_CHARS.toLocaleString()} characters.`;\n\t\tconst question: AgentMessage = { role: \"user\", content: text, timestamp: Date.now() };\n\t\tif (!this.fits([...this.agent.state.messages, question])) {\n\t\t\treturn \"Not enough context space for this BTW question. Compact the main conversation, then reopen /btw.\";\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t/** Accept synchronously so a rejected question stays in the editor, outside both agents' queues. */\n\tstartQuestion(text: string): { error: string } | { completion: Promise<void> } {\n\t\tconst error = this.validate(text);\n\t\tif (error) return { error };\n\t\tconst question: ActiveQuestion = {\n\t\t\tturn: {\n\t\t\t\tquestion: text,\n\t\t\t\tanswer: \"\",\n\t\t\t\tthinking: \"\",\n\t\t\t\tstatus: \"streaming\",\n\t\t\t\tstartedAt: Date.now(),\n\t\t\t\telapsedMs: 0,\n\t\t\t},\n\t\t\tsteps: 0,\n\t\t\tcompletedAnswer: \"\",\n\t\t\tresponseChars: 0,\n\t\t};\n\t\tthis.active = question;\n\t\tthis.turns.push(question.turn);\n\t\treturn { completion: this.runQuestion(question) };\n\t}\n\n\tprivate async runQuestion({ turn }: ActiveQuestion): Promise<void> {\n\t\ttry {\n\t\t\tthis.onChange();\n\t\t\tawait this.agent.prompt(turn.question);\n\t\t\tif (turn.status === \"streaming\") {\n\t\t\t\tturn.status = \"done\";\n\t\t\t\tif (!turn.answer) turn.notice = \"The model returned no answer text.\";\n\t\t\t} else if (turn.status === \"cancelling\") {\n\t\t\t\tturn.status = \"cancelled\";\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tturn.status = \"error\";\n\t\t\tturn.notice = displayText(error instanceof Error ? error.message : String(error));\n\t\t} finally {\n\t\t\t// Cancellation remains busy until the native Agent has fully settled.\n\t\t\tthis.active = undefined;\n\t\t\tturn.elapsedMs = Date.now() - turn.startedAt;\n\t\t\tif (this.disposed) this.agent.reset();\n\t\t\telse this.onChange();\n\t\t}\n\t}\n\n\tcancel(): void {\n\t\tif (!this.active || this.disposed) return;\n\t\tthis.active.turn.status = \"cancelling\";\n\t\tthis.agent.abort();\n\t\tthis.onChange();\n\t}\n\n\tdispose(): void {\n\t\tif (this.disposed) return;\n\t\tthis.disposed = true;\n\t\tthis.unsubscribe();\n\t\tthis.agent.abort();\n\t\tthis.turns.length = 0;\n\t\tif (!this.busy) this.agent.reset();\n\t\t// sessionId is cache affinity shared with main. Never clean up its provider resources here.\n\t}\n\n\tprivate fits(messages: AgentMessage[]): boolean {\n\t\treturn (\n\t\t\testimateContextTokens(\n\t\t\t\tnormalizeContext({\n\t\t\t\t\tsystemPrompt: this.agent.state.systemPrompt,\n\t\t\t\t\ttools: this.agent.state.tools,\n\t\t\t\t\tmessages: messages.filter(\n\t\t\t\t\t\t(message): message is Message =>\n\t\t\t\t\t\t\tmessage.role === \"user\" || message.role === \"assistant\" || message.role === \"toolResult\",\n\t\t\t\t\t),\n\t\t\t\t}),\n\t\t\t).tokens < this.inputBudget\n\t\t);\n\t}\n\n\tprivate handleEvent(event: AgentEvent): void {\n\t\tconst question = this.active;\n\t\tif (\n\t\t\tthis.disposed ||\n\t\t\t!question ||\n\t\t\t(event.type !== \"message_update\" && event.type !== \"message_end\") ||\n\t\t\tevent.message.role !== \"assistant\"\n\t\t)\n\t\t\treturn;\n\t\tconst { turn } = question;\n\t\tconst message = event.message;\n\t\tthis.updateAnswer(question, message);\n\t\tif (event.type === \"message_end\") {\n\t\t\tquestion.completedAnswer = turn.answer;\n\t\t\tquestion.responseChars += message.content.reduce(\n\t\t\t\t(sum, part) =>\n\t\t\t\t\tsum +\n\t\t\t\t\t(part.type === \"text\"\n\t\t\t\t\t\t? part.text.length\n\t\t\t\t\t\t: part.type === \"thinking\"\n\t\t\t\t\t\t\t? part.thinking.length\n\t\t\t\t\t\t\t: JSON.stringify(part.arguments).length),\n\t\t\t\t0,\n\t\t\t);\n\t\t\tthis.blockedTools += message.content.filter((part) => part.type === \"toolCall\").length;\n\t\t\tconst usage = message.usage;\n\t\t\tconst promptTokens = usage.input + usage.cacheRead + usage.cacheWrite;\n\t\t\tthis.latestCacheHitPercent =\n\t\t\t\t(usage.cacheRead > 0 || usage.cacheWrite > 0) && promptTokens > 0\n\t\t\t\t\t? (usage.cacheRead / promptTokens) * 100\n\t\t\t\t\t: undefined;\n\t\t\tfor (const key of [\"input\", \"output\", \"cacheRead\", \"cacheWrite\", \"totalTokens\"] as const)\n\t\t\t\tthis.usage[key] += usage[key];\n\t\t\tfor (const key of [\"input\", \"output\", \"cacheRead\", \"cacheWrite\", \"total\"] as const)\n\t\t\t\tthis.usage.cost[key] += usage.cost[key];\n\t\t\tif (message.stopReason === \"error\" || message.stopReason === \"aborted\") {\n\t\t\t\tturn.status = message.stopReason === \"aborted\" ? \"cancelled\" : \"error\";\n\t\t\t\tturn.notice ??=\n\t\t\t\t\tmessage.stopReason === \"aborted\"\n\t\t\t\t\t\t? \"Stopped. You can ask another question.\"\n\t\t\t\t\t\t: displayText(message.errorMessage ?? \"The request failed.\");\n\t\t\t}\n\t\t}\n\t\tthis.onChange();\n\t}\n\n\tprivate updateAnswer(question: ActiveQuestion, message: AssistantMessage): void {\n\t\tconst { turn } = question;\n\t\tconst text = message.content\n\t\t\t.filter((part) => part.type === \"text\")\n\t\t\t.map((part) => part.text)\n\t\t\t.join(\"\");\n\t\tconst thinking = message.content\n\t\t\t.filter((part) => part.type === \"thinking\")\n\t\t\t.map((part) => part.thinking)\n\t\t\t.join(\"\");\n\t\tif (text && turn.firstAnswerMs === undefined) turn.firstAnswerMs = Date.now() - turn.startedAt;\n\t\tturn.answer = displayText([question.completedAnswer, text].filter(Boolean).join(\"\\n\\n\"));\n\t\tturn.thinking = thinking.slice(-2000);\n\t\tif (question.responseChars + JSON.stringify(message.content).length > MAX_RESPONSE_CHARS) {\n\t\t\tturn.notice = \"Stopped at the BTW response size limit. You can ask a shorter follow-up.\";\n\t\t\tthis.agent.abort();\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"agent.js","sourceRoot":"","sources":["../../../src/extensions/btw/agent.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\tAgent,\n\ttype AgentEvent,\n\ttype AgentMessage,\n\ttype AgentTool,\n\ttype AgentTurnDecision,\n} from \"@earendil-works/pi-agent-core\";\nimport type { AssistantMessage, Message, Models, Usage } from \"@earendil-works/pi-ai\";\nimport { normalizeContext } from \"@earendil-works/pi-ai\";\nimport { estimateContextTokens } from \"@earendil-works/pi-ai/utils/estimate\";\nimport type { ContextSnapshot } from \"../../core/extensions/index.ts\";\nimport { cacheHitPercent, combineUsage, emptyUsage } from \"../../core/usage-totals.ts\";\nimport {\n\tMAX_DISPLAY_CHARS,\n\tMAX_MODEL_STEPS,\n\tMAX_OUTPUT_RESERVE_TOKENS,\n\tMAX_QUESTION_CHARS,\n\tMAX_QUESTIONS,\n\tMAX_RESPONSE_CHARS,\n\tTOOL_DENIAL,\n} from \"./constants.ts\";\nimport { createBtwMessages } from \"./snapshot.ts\";\n\nexport interface BtwTurn {\n\tquestion: string;\n\tanswer: string;\n\tthinking: string;\n\tstatus: \"streaming\" | \"cancelling\" | \"done\" | \"cancelled\" | \"error\";\n\tnotice?: string;\n\tstartedAt: number;\n\telapsedMs: number;\n\tfirstAnswerMs?: number;\n}\n\nexport interface BtwConversationState {\n\tbusy: boolean;\n\tturns: readonly BtwTurn[];\n\tusage: Usage;\n\tlatestCacheHitPercent: number | undefined;\n\tblockedTools: number;\n}\n\ninterface ActiveQuestion {\n\tturn: BtwTurn;\n\tsteps: number;\n\tcompletedAnswer: string;\n\tresponseChars: number;\n}\n\nfunction displayText(text: string): string {\n\treturn text.length <= MAX_DISPLAY_CHARS ? text : `${text.slice(0, MAX_DISPLAY_CHARS)}\\n\\n[Display truncated]`;\n}\n\n/** An in-memory native Agent. No SessionManager, real tool executors, or main-agent signal. */\nexport class BtwAgent implements BtwConversationState {\n\treadonly turns: BtwTurn[] = [];\n\tusage = emptyUsage();\n\tlatestCacheHitPercent: number | undefined;\n\tblockedTools = 0;\n\tprivate readonly agent: Agent;\n\tprivate readonly unsubscribe: () => void;\n\tprivate readonly onChange: () => void;\n\tprivate readonly inputBudget: number;\n\tprivate active?: ActiveQuestion;\n\tprivate disposed = false;\n\n\tconstructor(snapshot: ContextSnapshot, runtime: Pick<Models, \"streamSimple\">, onChange: () => void) {\n\t\tthis.onChange = onChange;\n\t\tconst outputReserve = Math.min(\n\t\t\tMAX_OUTPUT_RESERVE_TOKENS,\n\t\t\tsnapshot.model.maxTokens,\n\t\t\tMath.max(1024, Math.floor(snapshot.model.contextWindow / 8)),\n\t\t);\n\t\t// Leave room for the answer and estimation error; never trim the inherited prefix.\n\t\tthis.inputBudget = snapshot.model.contextWindow - outputReserve - 4096;\n\t\tconst tools: AgentTool[] = snapshot.tools.map((tool) => ({\n\t\t\t...structuredClone(tool),\n\t\t\tlabel: tool.name,\n\t\t\texecute: async () => {\n\t\t\t\t// Defense in depth: these objects never receive an original execute function.\n\t\t\t\tthrow new Error(TOOL_DENIAL);\n\t\t\t},\n\t\t}));\n\t\tthis.agent = new Agent({\n\t\t\tinitialState: {\n\t\t\t\tmodel: snapshot.model,\n\t\t\t\tthinkingLevel: snapshot.thinkingLevel,\n\t\t\t\tsystemPrompt: snapshot.systemPrompt,\n\t\t\t\ttools,\n\t\t\t\tmessages: createBtwMessages(snapshot),\n\t\t\t},\n\t\t\tsessionId: snapshot.streamOptions.sessionId ?? snapshot.sessionId,\n\t\t\ttransport: snapshot.streamOptions.transport,\n\t\t\tthinkingBudgets: snapshot.streamOptions.thinkingBudgets,\n\t\t\tmaxRetryDelayMs: snapshot.streamOptions.maxRetryDelayMs,\n\t\t\tonPayload: snapshot.streamOptions.onPayload,\n\t\t\tonResponse: snapshot.streamOptions.onResponse,\n\t\t\tstreamFn: (model, context, options) =>\n\t\t\t\t// Keep the parent's generation settings as well as its message prefix.\n\t\t\t\t// The model's native output ceiling and our response-size guard still bound output.\n\t\t\t\truntime.streamSimple(model, context, { ...snapshot.streamOptions, ...options }),\n\t\t\tbeforeToolCall: async () => ({ block: true, reason: TOOL_DENIAL }),\n\t\t\tfinishTurn: ({ message, toolResults, context }): AgentTurnDecision | undefined => {\n\t\t\t\tconst question = this.active;\n\t\t\t\t// Error and aborted responses are hard exits; the step budget only counts answered turns.\n\t\t\t\tif (!question || this.disposed || message.stopReason === \"error\" || message.stopReason === \"aborted\")\n\t\t\t\t\treturn undefined;\n\t\t\t\tquestion.steps++;\n\t\t\t\tif (!toolResults.length) return undefined;\n\t\t\t\tif (question.steps >= MAX_MODEL_STEPS || !this.fits(context.messages)) {\n\t\t\t\t\tquestion.turn.status = \"error\";\n\t\t\t\t\tquestion.turn.notice =\n\t\t\t\t\t\tquestion.steps >= MAX_MODEL_STEPS\n\t\t\t\t\t\t\t? \"Stopped after repeated tool requests. BTW can only answer from its snapshot.\"\n\t\t\t\t\t\t\t: \"Context limit reached. Reopen /btw for a new conversation.\";\n\t\t\t\t\treturn { action: \"end\" };\n\t\t\t\t}\n\t\t\t\treturn undefined;\n\t\t\t},\n\t\t});\n\t\tthis.unsubscribe = this.agent.subscribe((event) => this.handleEvent(event));\n\t}\n\n\tget busy(): boolean {\n\t\treturn this.active !== undefined;\n\t}\n\n\tprivate validate(text: string): string | undefined {\n\t\tif (this.disposed) return \"This BTW conversation is closed.\";\n\t\tif (this.busy) return \"BTW is still answering. Your question is kept in the editor.\";\n\t\tif (this.turns.length >= MAX_QUESTIONS) return \"BTW conversation limit reached. Reopen /btw to start a new one.\";\n\t\tif (text.length > MAX_QUESTION_CHARS)\n\t\t\treturn `BTW questions are limited to ${MAX_QUESTION_CHARS.toLocaleString()} characters.`;\n\t\tconst question: AgentMessage = { role: \"user\", content: text, timestamp: Date.now() };\n\t\tif (!this.fits([...this.agent.state.messages, question])) {\n\t\t\treturn \"Not enough context space for this BTW question. Compact the main conversation, then reopen /btw.\";\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t/** Accept synchronously so a rejected question stays in the editor, outside both agents' queues. */\n\tstartQuestion(text: string): { error: string } | { completion: Promise<void> } {\n\t\tconst error = this.validate(text);\n\t\tif (error) return { error };\n\t\tconst question: ActiveQuestion = {\n\t\t\tturn: {\n\t\t\t\tquestion: text,\n\t\t\t\tanswer: \"\",\n\t\t\t\tthinking: \"\",\n\t\t\t\tstatus: \"streaming\",\n\t\t\t\tstartedAt: Date.now(),\n\t\t\t\telapsedMs: 0,\n\t\t\t},\n\t\t\tsteps: 0,\n\t\t\tcompletedAnswer: \"\",\n\t\t\tresponseChars: 0,\n\t\t};\n\t\tthis.active = question;\n\t\tthis.turns.push(question.turn);\n\t\treturn { completion: this.runQuestion(question) };\n\t}\n\n\tprivate async runQuestion({ turn }: ActiveQuestion): Promise<void> {\n\t\ttry {\n\t\t\tthis.onChange();\n\t\t\tawait this.agent.prompt(turn.question);\n\t\t\tif (turn.status === \"streaming\") {\n\t\t\t\tturn.status = \"done\";\n\t\t\t\tif (!turn.answer) turn.notice = \"The model returned no answer text.\";\n\t\t\t} else if (turn.status === \"cancelling\") {\n\t\t\t\tturn.status = \"cancelled\";\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tturn.status = \"error\";\n\t\t\tturn.notice = displayText(error instanceof Error ? error.message : String(error));\n\t\t} finally {\n\t\t\t// Cancellation remains busy until the native Agent has fully settled.\n\t\t\tthis.active = undefined;\n\t\t\tturn.elapsedMs = Date.now() - turn.startedAt;\n\t\t\tif (this.disposed) this.agent.reset();\n\t\t\telse this.onChange();\n\t\t}\n\t}\n\n\tcancel(): void {\n\t\tif (!this.active || this.disposed) return;\n\t\tthis.active.turn.status = \"cancelling\";\n\t\tthis.agent.abort();\n\t\tthis.onChange();\n\t}\n\n\tdispose(): void {\n\t\tif (this.disposed) return;\n\t\tthis.disposed = true;\n\t\tthis.unsubscribe();\n\t\tthis.agent.abort();\n\t\tthis.turns.length = 0;\n\t\tif (!this.busy) this.agent.reset();\n\t\t// sessionId is cache affinity shared with main. Never clean up its provider resources here.\n\t}\n\n\tprivate fits(messages: AgentMessage[]): boolean {\n\t\treturn (\n\t\t\testimateContextTokens(\n\t\t\t\tnormalizeContext({\n\t\t\t\t\tsystemPrompt: this.agent.state.systemPrompt,\n\t\t\t\t\ttools: this.agent.state.tools,\n\t\t\t\t\tmessages: messages.filter(\n\t\t\t\t\t\t(message): message is Message =>\n\t\t\t\t\t\t\tmessage.role === \"user\" || message.role === \"assistant\" || message.role === \"toolResult\",\n\t\t\t\t\t),\n\t\t\t\t}),\n\t\t\t).tokens < this.inputBudget\n\t\t);\n\t}\n\n\tprivate handleEvent(event: AgentEvent): void {\n\t\tconst question = this.active;\n\t\tif (\n\t\t\tthis.disposed ||\n\t\t\t!question ||\n\t\t\t(event.type !== \"message_update\" && event.type !== \"message_end\") ||\n\t\t\tevent.message.role !== \"assistant\"\n\t\t)\n\t\t\treturn;\n\t\tconst { turn } = question;\n\t\tconst message = event.message;\n\t\tthis.updateAnswer(question, message);\n\t\tif (event.type === \"message_end\") {\n\t\t\tquestion.completedAnswer = turn.answer;\n\t\t\tquestion.responseChars += message.content.reduce(\n\t\t\t\t(sum, part) =>\n\t\t\t\t\tsum +\n\t\t\t\t\t(part.type === \"text\"\n\t\t\t\t\t\t? part.text.length\n\t\t\t\t\t\t: part.type === \"thinking\"\n\t\t\t\t\t\t\t? part.thinking.length\n\t\t\t\t\t\t\t: JSON.stringify(part.arguments).length),\n\t\t\t\t0,\n\t\t\t);\n\t\t\tthis.blockedTools += message.content.filter((part) => part.type === \"toolCall\").length;\n\t\t\tthis.latestCacheHitPercent = cacheHitPercent(message.usage);\n\t\t\tthis.usage = combineUsage(this.usage, message.usage);\n\t\t\tif (message.stopReason === \"error\" || message.stopReason === \"aborted\") {\n\t\t\t\tturn.status = message.stopReason === \"aborted\" ? \"cancelled\" : \"error\";\n\t\t\t\tturn.notice ??=\n\t\t\t\t\tmessage.stopReason === \"aborted\"\n\t\t\t\t\t\t? \"Stopped. You can ask another question.\"\n\t\t\t\t\t\t: displayText(message.errorMessage ?? \"The request failed.\");\n\t\t\t}\n\t\t}\n\t\tthis.onChange();\n\t}\n\n\tprivate updateAnswer(question: ActiveQuestion, message: AssistantMessage): void {\n\t\tconst { turn } = question;\n\t\tconst text = message.content\n\t\t\t.filter((part) => part.type === \"text\")\n\t\t\t.map((part) => part.text)\n\t\t\t.join(\"\");\n\t\tconst thinking = message.content\n\t\t\t.filter((part) => part.type === \"thinking\")\n\t\t\t.map((part) => part.thinking)\n\t\t\t.join(\"\");\n\t\tif (text && turn.firstAnswerMs === undefined) turn.firstAnswerMs = Date.now() - turn.startedAt;\n\t\tturn.answer = displayText([question.completedAnswer, text].filter(Boolean).join(\"\\n\\n\"));\n\t\tturn.thinking = thinking.slice(-2000);\n\t\tif (question.responseChars + JSON.stringify(message.content).length > MAX_RESPONSE_CHARS) {\n\t\t\tturn.notice = \"Stopped at the BTW response size limit. You can ask a shorter follow-up.\";\n\t\t\tthis.agent.abort();\n\t\t}\n\t}\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/extensions/cached-compaction/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAMnE,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAoF/D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/extensions/cached-compaction/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAMnE,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAkE/D"}
@@ -3,8 +3,8 @@ import { renderCachedCompactionEntry } from "./render.js";
3
3
  import { checkpointSummary, keptStart, summaryInstruction } from "./summary.js";
4
4
  const CARD_TYPE = "cached-compaction";
5
5
  export default function cachedCompaction(pi) {
6
+ /** Card for the summary returned to the current compaction; every compaction starts by clearing it. */
6
7
  let completed;
7
- let pendingCard;
8
8
  pi.registerEntryRenderer(CARD_TYPE, renderCachedCompactionEntry);
9
9
  pi.on("session_before_compact", async (event, ctx) => {
10
10
  completed = undefined;
@@ -63,29 +63,10 @@ export default function cachedCompaction(pi) {
63
63
  }
64
64
  });
65
65
  pi.on("session_compact", (event) => {
66
- if (!event.fromExtension || !completed)
67
- return;
68
66
  const card = completed;
69
67
  completed = undefined;
70
- pendingCard = card;
71
- setTimeout(() => {
72
- if (pendingCard !== card)
73
- return;
74
- pendingCard = undefined;
75
- try {
76
- pi.appendEntry(CARD_TYPE, card);
77
- }
78
- catch {
79
- // The session may be disposed before the deferred card is appended.
80
- }
81
- }, 0);
82
- });
83
- pi.on("session_compact_failed", () => {
84
- completed = undefined;
85
- });
86
- pi.on("session_shutdown", () => {
87
- completed = undefined;
88
- pendingCard = undefined;
68
+ if (event.fromExtension && card)
69
+ pi.appendEntry(CARD_TYPE, card);
89
70
  });
90
71
  }
91
72
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/extensions/cached-compaction/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { contentText } from \"@earendil-works/pi-ai\";\nimport type { ExtensionAPI } from \"../../core/extensions/types.ts\";\nimport { type CachedCompactionCard, renderCachedCompactionEntry } from \"./render.ts\";\nimport { checkpointSummary, keptStart, summaryInstruction } from \"./summary.ts\";\n\nconst CARD_TYPE = \"cached-compaction\";\n\nexport default function cachedCompaction(pi: ExtensionAPI): void {\n\tlet completed: CachedCompactionCard | undefined;\n\tlet pendingCard: CachedCompactionCard | undefined;\n\n\tpi.registerEntryRenderer(CARD_TYPE, renderCachedCompactionEntry);\n\n\tpi.on(\"session_before_compact\", async (event, ctx) => {\n\t\tcompleted = undefined;\n\t\tif (event.reason === \"overflow\") return;\n\t\tconst startedAt = Date.now();\n\t\ttry {\n\t\t\tconst snapshot = await ctx.getContextSnapshot();\n\t\t\tconst kept = keptStart(event.branchEntries, event.preparation.firstKeptEntryId, snapshot.messages);\n\t\t\tif (!kept) return;\n\t\t\tconst response = await ctx.modelRegistry\n\t\t\t\t.streamSimple(\n\t\t\t\t\tsnapshot.model,\n\t\t\t\t\t{\n\t\t\t\t\t\tmessages: [\n\t\t\t\t\t\t\t...snapshot.messages,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\trole: \"user\",\n\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: summaryInstruction(kept, event.customInstructions) }],\n\t\t\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\t...snapshot.streamOptions,\n\t\t\t\t\t\treasoning: snapshot.thinkingLevel === \"off\" ? undefined : snapshot.thinkingLevel,\n\t\t\t\t\t\tsignal: event.signal,\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t\t.result();\n\t\t\tif (response.stopReason === \"aborted\") return { cancel: true };\n\t\t\tif (response.stopReason !== \"stop\" || response.content.some((block) => block.type === \"toolCall\")) return;\n\t\t\tconst text = contentText(response.content).trim();\n\t\t\tif (!text) return;\n\t\t\tconst checkpoint = checkpointSummary(text, event.preparation.fileOps, event.branchEntries);\n\t\t\tcompleted = {\n\t\t\t\treason: event.reason,\n\t\t\t\tcacheRead: response.usage.cacheRead,\n\t\t\t\tinput: response.usage.input,\n\t\t\t\tcacheWrite: response.usage.cacheWrite,\n\t\t\t\toutput: response.usage.output,\n\t\t\t\tdurationMs: Date.now() - startedAt,\n\t\t\t};\n\t\t\treturn {\n\t\t\t\tcompaction: {\n\t\t\t\t\tsummary: checkpoint.summary,\n\t\t\t\t\tfirstKeptEntryId: event.preparation.firstKeptEntryId,\n\t\t\t\t\ttokensBefore: event.preparation.tokensBefore,\n\t\t\t\t\tusage: response.usage,\n\t\t\t\t\tdetails: checkpoint.details,\n\t\t\t\t},\n\t\t\t};\n\t\t} catch {\n\t\t\treturn event.signal.aborted ? { cancel: true } : undefined;\n\t\t}\n\t});\n\n\tpi.on(\"session_compact\", (event) => {\n\t\tif (!event.fromExtension || !completed) return;\n\t\tconst card = completed;\n\t\tcompleted = undefined;\n\t\tpendingCard = card;\n\t\tsetTimeout(() => {\n\t\t\tif (pendingCard !== card) return;\n\t\t\tpendingCard = undefined;\n\t\t\ttry {\n\t\t\t\tpi.appendEntry(CARD_TYPE, card);\n\t\t\t} catch {\n\t\t\t\t// The session may be disposed before the deferred card is appended.\n\t\t\t}\n\t\t}, 0);\n\t});\n\n\tpi.on(\"session_compact_failed\", () => {\n\t\tcompleted = undefined;\n\t});\n\tpi.on(\"session_shutdown\", () => {\n\t\tcompleted = undefined;\n\t\tpendingCard = undefined;\n\t});\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/extensions/cached-compaction/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEpD,OAAO,EAA6B,2BAA2B,EAAE,MAAM,aAAa,CAAC;AACrF,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEhF,MAAM,SAAS,GAAG,mBAAmB,CAAC;AAEtC,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,EAAgB;IACxD,uGAAuG;IACvG,IAAI,SAA2C,CAAC;IAEhD,EAAE,CAAC,qBAAqB,CAAC,SAAS,EAAE,2BAA2B,CAAC,CAAC;IAEjE,EAAE,CAAC,EAAE,CAAC,wBAAwB,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;QACpD,SAAS,GAAG,SAAS,CAAC;QACtB,IAAI,KAAK,CAAC,MAAM,KAAK,UAAU;YAAE,OAAO;QACxC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC7B,IAAI,CAAC;YACJ,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,kBAAkB,EAAE,CAAC;YAChD,MAAM,IAAI,GAAG,SAAS,CAAC,KAAK,CAAC,aAAa,EAAE,KAAK,CAAC,WAAW,CAAC,gBAAgB,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;YACnG,IAAI,CAAC,IAAI;gBAAE,OAAO;YAClB,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,aAAa;iBACtC,YAAY,CACZ,QAAQ,CAAC,KAAK,EACd;gBACC,QAAQ,EAAE;oBACT,GAAG,QAAQ,CAAC,QAAQ;oBACpB;wBACC,IAAI,EAAE,MAAM;wBACZ,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,kBAAkB,CAAC,IAAI,EAAE,KAAK,CAAC,kBAAkB,CAAC,EAAE,CAAC;wBACrF,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;qBACrB;iBACD;aACD,EACD;gBACC,GAAG,QAAQ,CAAC,aAAa;gBACzB,SAAS,EAAE,QAAQ,CAAC,aAAa,KAAK,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,aAAa;gBAChF,MAAM,EAAE,KAAK,CAAC,MAAM;aACpB,CACD;iBACA,MAAM,EAAE,CAAC;YACX,IAAI,QAAQ,CAAC,UAAU,KAAK,SAAS;gBAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;YAC/D,IAAI,QAAQ,CAAC,UAAU,KAAK,MAAM,IAAI,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,UAAU,CAAC;gBAAE,OAAO;YAC1G,MAAM,IAAI,GAAG,WAAW,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;YAClD,IAAI,CAAC,IAAI;gBAAE,OAAO;YAClB,MAAM,UAAU,GAAG,iBAAiB,CAAC,IAAI,EAAE,KAAK,CAAC,WAAW,CAAC,OAAO,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;YAC3F,SAAS,GAAG;gBACX,MAAM,EAAE,KAAK,CAAC,MAAM;gBACpB,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC,SAAS;gBACnC,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,KAAK;gBAC3B,UAAU,EAAE,QAAQ,CAAC,KAAK,CAAC,UAAU;gBACrC,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC,MAAM;gBAC7B,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;aAClC,CAAC;YACF,OAAO;gBACN,UAAU,EAAE;oBACX,OAAO,EAAE,UAAU,CAAC,OAAO;oBAC3B,gBAAgB,EAAE,KAAK,CAAC,WAAW,CAAC,gBAAgB;oBACpD,YAAY,EAAE,KAAK,CAAC,WAAW,CAAC,YAAY;oBAC5C,KAAK,EAAE,QAAQ,CAAC,KAAK;oBACrB,OAAO,EAAE,UAAU,CAAC,OAAO;iBAC3B;aACD,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACR,OAAO,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;QAC5D,CAAC;IACF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,EAAE,CAAC,iBAAiB,EAAE,CAAC,KAAK,EAAE,EAAE;QAClC,MAAM,IAAI,GAAG,SAAS,CAAC;QACvB,SAAS,GAAG,SAAS,CAAC;QACtB,IAAI,KAAK,CAAC,aAAa,IAAI,IAAI;YAAE,EAAE,CAAC,WAAW,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAClE,CAAC,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import { contentText } from \"@earendil-works/pi-ai\";\nimport type { ExtensionAPI } from \"../../core/extensions/types.ts\";\nimport { type CachedCompactionCard, renderCachedCompactionEntry } from \"./render.ts\";\nimport { checkpointSummary, keptStart, summaryInstruction } from \"./summary.ts\";\n\nconst CARD_TYPE = \"cached-compaction\";\n\nexport default function cachedCompaction(pi: ExtensionAPI): void {\n\t/** Card for the summary returned to the current compaction; every compaction starts by clearing it. */\n\tlet completed: CachedCompactionCard | undefined;\n\n\tpi.registerEntryRenderer(CARD_TYPE, renderCachedCompactionEntry);\n\n\tpi.on(\"session_before_compact\", async (event, ctx) => {\n\t\tcompleted = undefined;\n\t\tif (event.reason === \"overflow\") return;\n\t\tconst startedAt = Date.now();\n\t\ttry {\n\t\t\tconst snapshot = await ctx.getContextSnapshot();\n\t\t\tconst kept = keptStart(event.branchEntries, event.preparation.firstKeptEntryId, snapshot.messages);\n\t\t\tif (!kept) return;\n\t\t\tconst response = await ctx.modelRegistry\n\t\t\t\t.streamSimple(\n\t\t\t\t\tsnapshot.model,\n\t\t\t\t\t{\n\t\t\t\t\t\tmessages: [\n\t\t\t\t\t\t\t...snapshot.messages,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\trole: \"user\",\n\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: summaryInstruction(kept, event.customInstructions) }],\n\t\t\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\t...snapshot.streamOptions,\n\t\t\t\t\t\treasoning: snapshot.thinkingLevel === \"off\" ? undefined : snapshot.thinkingLevel,\n\t\t\t\t\t\tsignal: event.signal,\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t\t.result();\n\t\t\tif (response.stopReason === \"aborted\") return { cancel: true };\n\t\t\tif (response.stopReason !== \"stop\" || response.content.some((block) => block.type === \"toolCall\")) return;\n\t\t\tconst text = contentText(response.content).trim();\n\t\t\tif (!text) return;\n\t\t\tconst checkpoint = checkpointSummary(text, event.preparation.fileOps, event.branchEntries);\n\t\t\tcompleted = {\n\t\t\t\treason: event.reason,\n\t\t\t\tcacheRead: response.usage.cacheRead,\n\t\t\t\tinput: response.usage.input,\n\t\t\t\tcacheWrite: response.usage.cacheWrite,\n\t\t\t\toutput: response.usage.output,\n\t\t\t\tdurationMs: Date.now() - startedAt,\n\t\t\t};\n\t\t\treturn {\n\t\t\t\tcompaction: {\n\t\t\t\t\tsummary: checkpoint.summary,\n\t\t\t\t\tfirstKeptEntryId: event.preparation.firstKeptEntryId,\n\t\t\t\t\ttokensBefore: event.preparation.tokensBefore,\n\t\t\t\t\tusage: response.usage,\n\t\t\t\t\tdetails: checkpoint.details,\n\t\t\t\t},\n\t\t\t};\n\t\t} catch {\n\t\t\treturn event.signal.aborted ? { cancel: true } : undefined;\n\t\t}\n\t});\n\n\tpi.on(\"session_compact\", (event) => {\n\t\tconst card = completed;\n\t\tcompleted = undefined;\n\t\tif (event.fromExtension && card) pi.appendEntry(CARD_TYPE, card);\n\t});\n}\n"]}
@@ -7,5 +7,6 @@ export interface CachedCompactionCard {
7
7
  output: number;
8
8
  durationMs: number;
9
9
  }
10
+ /** A footnote to the native compaction summary above it, expanding with it. */
10
11
  export declare const renderCachedCompactionEntry: EntryRenderer<CachedCompactionCard>;
11
12
  //# sourceMappingURL=render.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../src/extensions/cached-compaction/render.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAEpE,MAAM,WAAW,oBAAoB;IACpC,MAAM,EAAE,QAAQ,GAAG,WAAW,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACnB;AAQD,eAAO,MAAM,2BAA2B,EAAE,aAAa,CAAC,oBAAoB,CAQ3E,CAAC"}
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../src/extensions/cached-compaction/render.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAMpE,MAAM,WAAW,oBAAoB;IACpC,MAAM,EAAE,QAAQ,GAAG,WAAW,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACnB;AAqBD,+EAA+E;AAC/E,eAAO,MAAM,2BAA2B,EAAE,aAAa,CAAC,oBAAoB,CAmB3E,CAAC"}
@@ -1,19 +1,47 @@
1
1
  import { Text } from "@earendil-works/pi-tui";
2
- function formatTokens(count) {
3
- if (count >= 1_000_000)
4
- return `${(count / 1_000_000).toFixed(1)}m`;
5
- if (count >= 1000)
6
- return `${(count / 1000).toFixed(1)}k`;
7
- return String(count);
2
+ import { cacheHitPercent } from "../../core/usage-totals.js";
3
+ import { formatTokens } from "../../modes/interactive/components/footer.js";
4
+ import { formatDuration } from "../../utils/duration.js";
5
+ /** Cache reuse is why this path exists, so its rate is the one colored segment. */
6
+ function hitRate(card, theme) {
7
+ const percent = cacheHitPercent(card);
8
+ if (percent === undefined)
9
+ return undefined;
10
+ return theme.fg(percent >= 80 ? "success" : percent < 50 ? "warning" : "dim", `CH ${percent.toFixed(1)}%`);
11
+ }
12
+ /** The prompt split by how the provider billed it; parts it did not use are left out. */
13
+ function promptBreakdown(card) {
14
+ const total = card.input + card.cacheRead + card.cacheWrite;
15
+ if (total === 0)
16
+ return undefined;
17
+ const parts = [
18
+ card.cacheRead > 0 ? `R${formatTokens(card.cacheRead)} cached` : undefined,
19
+ card.cacheWrite > 0 ? `W${formatTokens(card.cacheWrite)} written` : undefined,
20
+ card.input > 0 ? `↑${formatTokens(card.input)} uncached` : undefined,
21
+ ];
22
+ return `Prompt ${formatTokens(total)} = ${parts.filter(Boolean).join(" + ")}`;
8
23
  }
9
- export const renderCachedCompactionEntry = (entry, _options, theme) => {
10
- const data = entry.data;
11
- if (!data)
24
+ /** A footnote to the native compaction summary above it, expanding with it. */
25
+ export const renderCachedCompactionEntry = (entry, { expanded }, theme) => {
26
+ const card = entry.data;
27
+ if (!card)
12
28
  return undefined;
13
- const promptTokens = data.input + data.cacheRead + data.cacheWrite;
14
- const percentage = promptTokens > 0 ? ((100 * data.cacheRead) / promptTokens).toFixed(1) : "0.0";
15
- const title = `Cached compaction · ${data.reason === "manual" ? "manual" : "automatic"}`;
16
- const stats = `Cache ${formatTokens(data.cacheRead)}/${formatTokens(promptTokens)} (${percentage}%) · output ${formatTokens(data.output)} · ${(data.durationMs / 1000).toFixed(1)}s`;
17
- return new Text(`${theme.fg("accent", title)}\n${theme.fg("dim", stats)}`, 1, 0);
29
+ const dim = (text) => theme.fg("dim", text);
30
+ const reason = card.reason === "manual" ? "manual" : "auto";
31
+ const head = [
32
+ dim("From cache"),
33
+ expanded || reason === "auto" ? dim(reason) : undefined,
34
+ hitRate(card, theme),
35
+ expanded ? undefined : dim(`↓${formatTokens(card.output)}`),
36
+ dim(formatDuration(card.durationMs)),
37
+ ];
38
+ const lines = [head.filter(Boolean).join(dim(" · "))];
39
+ if (expanded) {
40
+ const breakdown = promptBreakdown(card);
41
+ if (breakdown)
42
+ lines.push(dim(breakdown));
43
+ lines.push(dim(`Output ↓${formatTokens(card.output)}`));
44
+ }
45
+ return new Text(lines.join("\n"), 1, 0);
18
46
  };
19
47
  //# sourceMappingURL=render.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"render.js","sourceRoot":"","sources":["../../../src/extensions/cached-compaction/render.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAY9C,SAAS,YAAY,CAAC,KAAa;IAClC,IAAI,KAAK,IAAI,SAAS;QAAE,OAAO,GAAG,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;IACpE,IAAI,KAAK,IAAI,IAAI;QAAE,OAAO,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;IAC1D,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;AACtB,CAAC;AAED,MAAM,CAAC,MAAM,2BAA2B,GAAwC,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE;IAC1G,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;IACxB,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAC;IAC5B,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC;IACnE,MAAM,UAAU,GAAG,YAAY,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,YAAY,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IACjG,MAAM,KAAK,GAAG,uBAAuB,IAAI,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IACzF,MAAM,KAAK,GAAG,SAAS,YAAY,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,YAAY,CAAC,YAAY,CAAC,KAAK,UAAU,eAAe,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;IACrL,OAAO,IAAI,IAAI,CAAC,GAAG,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,KAAK,CAAC,KAAK,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAClF,CAAC,CAAC","sourcesContent":["import { Text } from \"@earendil-works/pi-tui\";\nimport type { EntryRenderer } from \"../../core/extensions/types.ts\";\n\nexport interface CachedCompactionCard {\n\treason: \"manual\" | \"threshold\";\n\tcacheRead: number;\n\tinput: number;\n\tcacheWrite: number;\n\toutput: number;\n\tdurationMs: number;\n}\n\nfunction formatTokens(count: number): string {\n\tif (count >= 1_000_000) return `${(count / 1_000_000).toFixed(1)}m`;\n\tif (count >= 1000) return `${(count / 1000).toFixed(1)}k`;\n\treturn String(count);\n}\n\nexport const renderCachedCompactionEntry: EntryRenderer<CachedCompactionCard> = (entry, _options, theme) => {\n\tconst data = entry.data;\n\tif (!data) return undefined;\n\tconst promptTokens = data.input + data.cacheRead + data.cacheWrite;\n\tconst percentage = promptTokens > 0 ? ((100 * data.cacheRead) / promptTokens).toFixed(1) : \"0.0\";\n\tconst title = `Cached compaction · ${data.reason === \"manual\" ? \"manual\" : \"automatic\"}`;\n\tconst stats = `Cache ${formatTokens(data.cacheRead)}/${formatTokens(promptTokens)} (${percentage}%) · output ${formatTokens(data.output)} · ${(data.durationMs / 1000).toFixed(1)}s`;\n\treturn new Text(`${theme.fg(\"accent\", title)}\\n${theme.fg(\"dim\", stats)}`, 1, 0);\n};\n"]}
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../../../src/extensions/cached-compaction/render.ts"],"names":[],"mappings":"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","sourcesContent":["import { Text } from \"@earendil-works/pi-tui\";\nimport type { EntryRenderer } from \"../../core/extensions/types.ts\";\nimport { cacheHitPercent } from \"../../core/usage-totals.ts\";\nimport { formatTokens } from \"../../modes/interactive/components/footer.ts\";\nimport type { Theme } from \"../../modes/interactive/theme/theme.ts\";\nimport { formatDuration } from \"../../utils/duration.ts\";\n\nexport interface CachedCompactionCard {\n\treason: \"manual\" | \"threshold\";\n\tcacheRead: number;\n\tinput: number;\n\tcacheWrite: number;\n\toutput: number;\n\tdurationMs: number;\n}\n\n/** Cache reuse is why this path exists, so its rate is the one colored segment. */\nfunction hitRate(card: CachedCompactionCard, theme: Theme): string | undefined {\n\tconst percent = cacheHitPercent(card);\n\tif (percent === undefined) return undefined;\n\treturn theme.fg(percent >= 80 ? \"success\" : percent < 50 ? \"warning\" : \"dim\", `CH ${percent.toFixed(1)}%`);\n}\n\n/** The prompt split by how the provider billed it; parts it did not use are left out. */\nfunction promptBreakdown(card: CachedCompactionCard): string | undefined {\n\tconst total = card.input + card.cacheRead + card.cacheWrite;\n\tif (total === 0) return undefined;\n\tconst parts = [\n\t\tcard.cacheRead > 0 ? `R${formatTokens(card.cacheRead)} cached` : undefined,\n\t\tcard.cacheWrite > 0 ? `W${formatTokens(card.cacheWrite)} written` : undefined,\n\t\tcard.input > 0 ? `↑${formatTokens(card.input)} uncached` : undefined,\n\t];\n\treturn `Prompt ${formatTokens(total)} = ${parts.filter(Boolean).join(\" + \")}`;\n}\n\n/** A footnote to the native compaction summary above it, expanding with it. */\nexport const renderCachedCompactionEntry: EntryRenderer<CachedCompactionCard> = (entry, { expanded }, theme) => {\n\tconst card = entry.data;\n\tif (!card) return undefined;\n\tconst dim = (text: string) => theme.fg(\"dim\", text);\n\tconst reason = card.reason === \"manual\" ? \"manual\" : \"auto\";\n\tconst head = [\n\t\tdim(\"From cache\"),\n\t\texpanded || reason === \"auto\" ? dim(reason) : undefined,\n\t\thitRate(card, theme),\n\t\texpanded ? undefined : dim(`↓${formatTokens(card.output)}`),\n\t\tdim(formatDuration(card.durationMs)),\n\t];\n\tconst lines = [head.filter(Boolean).join(dim(\" · \"))];\n\tif (expanded) {\n\t\tconst breakdown = promptBreakdown(card);\n\t\tif (breakdown) lines.push(dim(breakdown));\n\t\tlines.push(dim(`Output ↓${formatTokens(card.output)}`));\n\t}\n\treturn new Text(lines.join(\"\\n\"), 1, 0);\n};\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/extensions/deepwiki/client.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,cAAc,EAA2B,MAAM,aAAa,CAAC;AAW3E,KAAK,gBAAgB,GAAG,qBAAqB,GAAG,oBAAoB,GAAG,cAAc,CAAC;AA0BtF,MAAM,WAAW,gBAAgB;IAChC,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACnB;AAkHD,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAE/D;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAE1D;AAmBD,wBAAsB,YAAY,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA+DrH"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/extensions/deepwiki/client.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,cAAc,EAA2B,MAAM,aAAa,CAAC;AAO3E,KAAK,gBAAgB,GAAG,qBAAqB,GAAG,oBAAoB,GAAG,cAAc,CAAC;AAEtF,MAAM,WAAW,gBAAgB;IAChC,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACnB;AA2ED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAE/D;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAE1D;AAmBD,wBAAsB,YAAY,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA8BrH"}
@@ -1,14 +1,9 @@
1
1
  import { PAGE_HEADER_RE } from "./contents.js";
2
- import { fetchWithRetry } from "./http.js";
3
2
  import { normalizeDeepWikiParams } from "./schema.js";
4
- const DEEPWIKI_MCP_URL = "https://mcp.deepwiki.com/mcp";
5
- const MCP_PROTOCOL_VERSION = "2024-11-05";
6
- const REQUEST_TIMEOUT_MS = 45_000;
3
+ import { callDeepWikiTool } from "./transport.js";
7
4
  const CACHE_TTL_MS = 10 * 60_000;
8
5
  /** Long-lived process guard: entries are only otherwise dropped on re-read after expiry. */
9
6
  const MAX_CACHE_ENTRIES = 50;
10
- /** Transient-failure retries (network blips, 5xx, 429). Aborts/timeouts are not retried. */
11
- const MAX_RETRIES = 2;
12
7
  const responseCache = new Map();
13
8
  function toolNameForAction(action) {
14
9
  if (action === "structure")
@@ -38,47 +33,6 @@ function cloneResponse(response, cacheHit) {
38
33
  cacheHit,
39
34
  };
40
35
  }
41
- function truncate(text, maxLength = 500) {
42
- if (text.length <= maxLength)
43
- return text;
44
- return `${text.slice(0, maxLength - 3)}...`;
45
- }
46
- function parseJson(text) {
47
- try {
48
- return JSON.parse(text);
49
- }
50
- catch (error) {
51
- const message = error instanceof Error ? error.message : String(error);
52
- throw new Error(`DeepWiki returned invalid JSON: ${message}`);
53
- }
54
- }
55
- function parseSse(text) {
56
- const blocks = text.split(/\r?\n\r?\n/);
57
- let fallback;
58
- for (const block of blocks) {
59
- const dataLines = block
60
- .split(/\r?\n/)
61
- .filter((line) => line.startsWith("data:"))
62
- .map((line) => line.slice(5).replace(/^ /, ""));
63
- if (dataLines.length === 0)
64
- continue;
65
- const data = dataLines.join("\n").trim();
66
- if (!data || data === "[DONE]")
67
- continue;
68
- const envelope = parseJson(data);
69
- if (envelope.error || envelope.result !== undefined)
70
- return envelope;
71
- fallback = envelope;
72
- }
73
- if (fallback)
74
- return fallback;
75
- throw new Error("DeepWiki returned an empty event stream");
76
- }
77
- function parseEnvelope(text, contentType) {
78
- if (contentType?.includes("text/event-stream"))
79
- return parseSse(text);
80
- return parseJson(text);
81
- }
82
36
  function extractStructuredResult(result) {
83
37
  if (!result.structuredContent || typeof result.structuredContent !== "object")
84
38
  return undefined;
@@ -150,50 +104,24 @@ export async function callDeepWiki(params, signal) {
150
104
  if (cached)
151
105
  responseCache.delete(key);
152
106
  const toolName = toolNameForAction(normalizedParams.action);
153
- const bodyJson = JSON.stringify({
154
- jsonrpc: "2.0",
155
- id: 1,
156
- method: "tools/call",
157
- params: {
158
- name: toolName,
159
- arguments: argumentsForParams(normalizedParams),
160
- },
161
- });
162
- // Timeout + bounded transient retry live in this extension's http.ts; non-retryable
163
- // statuses come back here for the domain-specific HTTP error below.
164
- const { response, text } = await fetchWithRetry(DEEPWIKI_MCP_URL, {
165
- method: "POST",
166
- headers: {
167
- Accept: "application/json, text/event-stream",
168
- "Content-Type": "application/json",
169
- "MCP-Protocol-Version": MCP_PROTOCOL_VERSION,
170
- },
171
- body: bodyJson,
172
- }, { timeoutMs: REQUEST_TIMEOUT_MS, retries: MAX_RETRIES, signal, label: "DeepWiki" });
107
+ const result = await callDeepWikiTool(toolName, argumentsForParams(normalizedParams), signal);
173
108
  signal?.throwIfAborted();
174
- if (!response.ok) {
175
- throw new Error(`DeepWiki HTTP ${response.status}: ${truncate(text)}`);
176
- }
177
- const envelope = parseEnvelope(text, response.headers.get("content-type"));
178
- if (envelope.error) {
179
- throw new Error(envelope.error.message ?? `DeepWiki JSON-RPC error ${envelope.error.code ?? "unknown"}`);
180
- }
181
- const resultText = extractToolText(envelope.result);
109
+ const resultText = extractToolText(result);
182
110
  const pageTitles = extractPageTitles(toolName, resultText);
183
- const result = {
111
+ const response = {
184
112
  toolName,
185
113
  text: resultText,
186
114
  outputLength: resultText.length,
187
115
  ...(pageTitles.length ? { pageTitles } : {}),
188
116
  };
189
117
  signal?.throwIfAborted();
190
- responseCache.set(key, { expiresAt: Date.now() + CACHE_TTL_MS, response: cloneResponse(result, false) });
118
+ responseCache.set(key, { expiresAt: Date.now() + CACHE_TTL_MS, response: cloneResponse(response, false) });
191
119
  while (responseCache.size > MAX_CACHE_ENTRIES) {
192
120
  const oldest = responseCache.keys().next().value;
193
121
  if (oldest === undefined)
194
122
  break;
195
123
  responseCache.delete(oldest);
196
124
  }
197
- return result;
125
+ return response;
198
126
  }
199
127
  //# sourceMappingURL=client.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/extensions/deepwiki/client.ts"],"names":[],"mappings":"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","sourcesContent":["import { PAGE_HEADER_RE } from \"./contents.ts\";\nimport { fetchWithRetry } from \"./http.ts\";\nimport { type DeepWikiParams, normalizeDeepWikiParams } from \"./schema.ts\";\n\nconst DEEPWIKI_MCP_URL = \"https://mcp.deepwiki.com/mcp\";\nconst MCP_PROTOCOL_VERSION = \"2024-11-05\";\nconst REQUEST_TIMEOUT_MS = 45_000;\nconst CACHE_TTL_MS = 10 * 60_000;\n/** Long-lived process guard: entries are only otherwise dropped on re-read after expiry. */\nconst MAX_CACHE_ENTRIES = 50;\n/** Transient-failure retries (network blips, 5xx, 429). Aborts/timeouts are not retried. */\nconst MAX_RETRIES = 2;\n\ntype DeepWikiToolName = \"read_wiki_structure\" | \"read_wiki_contents\" | \"ask_question\";\n\ninterface JsonRpcErrorPayload {\n\tcode?: number;\n\tmessage?: string;\n\tdata?: unknown;\n}\n\ninterface JsonRpcEnvelope {\n\tjsonrpc?: string;\n\tid?: string | number | null;\n\tresult?: unknown;\n\terror?: JsonRpcErrorPayload;\n}\n\ninterface McpTextContent {\n\ttype: \"text\";\n\ttext: string;\n}\n\ninterface McpToolCallResult {\n\tcontent?: unknown;\n\tstructuredContent?: unknown;\n\tisError?: boolean;\n}\n\nexport interface DeepWikiResponse {\n\ttoolName: DeepWikiToolName;\n\ttext: string;\n\toutputLength: number;\n\tpageTitles?: string[];\n\tcacheHit?: boolean;\n}\n\ninterface CacheEntry {\n\texpiresAt: number;\n\tresponse: DeepWikiResponse;\n}\n\nconst responseCache = new Map<string, CacheEntry>();\n\nfunction toolNameForAction(action: DeepWikiParams[\"action\"]): DeepWikiToolName {\n\tif (action === \"structure\") return \"read_wiki_structure\";\n\tif (action === \"contents\") return \"read_wiki_contents\";\n\treturn \"ask_question\";\n}\n\n// Callers pass params already normalized by callDeepWiki's entry gate; no re-normalization.\nfunction argumentsForParams(params: DeepWikiParams): Record<string, unknown> {\n\tif (params.action === \"question\") {\n\t\tconst question = params.question?.trim();\n\t\tif (!question) throw new Error(\"question is required when action is question\");\n\t\treturn { repoName: params.repoName, question };\n\t}\n\treturn { repoName: params.repoName };\n}\n\nfunction cacheKey(params: DeepWikiParams): string {\n\tconst repos = Array.isArray(params.repoName) ? params.repoName : [params.repoName];\n\treturn JSON.stringify([params.action, repos, params.question ?? \"\"]);\n}\n\nfunction cloneResponse(response: DeepWikiResponse, cacheHit: boolean): DeepWikiResponse {\n\treturn {\n\t\t...response,\n\t\t...(response.pageTitles ? { pageTitles: [...response.pageTitles] } : {}),\n\t\tcacheHit,\n\t};\n}\n\nfunction truncate(text: string, maxLength = 500): string {\n\tif (text.length <= maxLength) return text;\n\treturn `${text.slice(0, maxLength - 3)}...`;\n}\n\nfunction parseJson(text: string): JsonRpcEnvelope {\n\ttry {\n\t\treturn JSON.parse(text) as JsonRpcEnvelope;\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tthrow new Error(`DeepWiki returned invalid JSON: ${message}`);\n\t}\n}\n\nfunction parseSse(text: string): JsonRpcEnvelope {\n\tconst blocks = text.split(/\\r?\\n\\r?\\n/);\n\tlet fallback: JsonRpcEnvelope | undefined;\n\tfor (const block of blocks) {\n\t\tconst dataLines = block\n\t\t\t.split(/\\r?\\n/)\n\t\t\t.filter((line) => line.startsWith(\"data:\"))\n\t\t\t.map((line) => line.slice(5).replace(/^ /, \"\"));\n\t\tif (dataLines.length === 0) continue;\n\n\t\tconst data = dataLines.join(\"\\n\").trim();\n\t\tif (!data || data === \"[DONE]\") continue;\n\t\tconst envelope = parseJson(data);\n\t\tif (envelope.error || envelope.result !== undefined) return envelope;\n\t\tfallback = envelope;\n\t}\n\tif (fallback) return fallback;\n\tthrow new Error(\"DeepWiki returned an empty event stream\");\n}\n\nfunction parseEnvelope(text: string, contentType: string | null): JsonRpcEnvelope {\n\tif (contentType?.includes(\"text/event-stream\")) return parseSse(text);\n\treturn parseJson(text);\n}\n\nfunction extractStructuredResult(result: McpToolCallResult): string | undefined {\n\tif (!result.structuredContent || typeof result.structuredContent !== \"object\") return undefined;\n\tconst value = (result.structuredContent as Record<string, unknown>).result;\n\treturn typeof value === \"string\" ? value : undefined;\n}\n\nfunction extractContentResult(result: McpToolCallResult): string {\n\tif (!Array.isArray(result.content)) return \"\";\n\treturn result.content\n\t\t.filter((part): part is McpTextContent => {\n\t\t\tif (!part || typeof part !== \"object\") return false;\n\t\t\tconst record = part as Record<string, unknown>;\n\t\t\treturn record.type === \"text\" && typeof record.text === \"string\";\n\t\t})\n\t\t.map((part) => part.text)\n\t\t.join(\"\\n\")\n\t\t.trim();\n}\n\nfunction isDeepWikiErrorText(text: string): boolean {\n\tconst trimmed = text.trim();\n\treturn (\n\t\t/^Error fetching wiki for .+?: Repository not found\\./i.test(trimmed) ||\n\t\t/^Error processing question: Repository not found\\./i.test(trimmed)\n\t);\n}\n\nfunction extractToolText(result: unknown): string {\n\tif (!result || typeof result !== \"object\") throw new Error(\"DeepWiki returned no result object\");\n\n\tconst toolResult = result as McpToolCallResult;\n\tconst text = extractStructuredResult(toolResult) ?? extractContentResult(toolResult);\n\tif (!text) throw new Error(\"DeepWiki returned no text content\");\n\tif (toolResult.isError || isDeepWikiErrorText(text)) throw new Error(text);\n\treturn text;\n}\n\nexport function extractStructureSections(text: string): string[] {\n\treturn uniqueTitles(text.split(\"\\n\").map((line) => line.match(/^\\s*-\\s+\\d+\\s+(.+)$/)?.[1]?.trim()));\n}\n\nexport function extractContentPages(text: string): string[] {\n\treturn uniqueTitles(text.split(\"\\n\").map((line) => line.match(PAGE_HEADER_RE)?.[1]?.trim()));\n}\n\nfunction uniqueTitles(titles: Array<string | undefined>): string[] {\n\tconst seen = new Set<string>();\n\tconst result: string[] = [];\n\tfor (const title of titles) {\n\t\tif (!title || seen.has(title)) continue;\n\t\tseen.add(title);\n\t\tresult.push(title);\n\t}\n\treturn result;\n}\n\nfunction extractPageTitles(toolName: DeepWikiToolName, text: string): string[] {\n\tif (toolName === \"read_wiki_structure\") return extractStructureSections(text);\n\tif (toolName === \"read_wiki_contents\") return extractContentPages(text);\n\treturn [];\n}\n\nexport async function callDeepWiki(params: DeepWikiParams, signal: AbortSignal | undefined): Promise<DeepWikiResponse> {\n\tsignal?.throwIfAborted();\n\tconst normalizedParams = normalizeDeepWikiParams(params);\n\tconst key = cacheKey(normalizedParams);\n\tconst cached = responseCache.get(key);\n\tif (cached && cached.expiresAt > Date.now()) {\n\t\treturn cloneResponse(cached.response, true);\n\t}\n\tif (cached) responseCache.delete(key);\n\n\tconst toolName = toolNameForAction(normalizedParams.action);\n\tconst bodyJson = JSON.stringify({\n\t\tjsonrpc: \"2.0\",\n\t\tid: 1,\n\t\tmethod: \"tools/call\",\n\t\tparams: {\n\t\t\tname: toolName,\n\t\t\targuments: argumentsForParams(normalizedParams),\n\t\t},\n\t});\n\n\t// Timeout + bounded transient retry live in this extension's http.ts; non-retryable\n\t// statuses come back here for the domain-specific HTTP error below.\n\tconst { response, text } = await fetchWithRetry(\n\t\tDEEPWIKI_MCP_URL,\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\tAccept: \"application/json, text/event-stream\",\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\"MCP-Protocol-Version\": MCP_PROTOCOL_VERSION,\n\t\t\t},\n\t\t\tbody: bodyJson,\n\t\t},\n\t\t{ timeoutMs: REQUEST_TIMEOUT_MS, retries: MAX_RETRIES, signal, label: \"DeepWiki\" },\n\t);\n\tsignal?.throwIfAborted();\n\n\tif (!response.ok) {\n\t\tthrow new Error(`DeepWiki HTTP ${response.status}: ${truncate(text)}`);\n\t}\n\n\tconst envelope = parseEnvelope(text, response.headers.get(\"content-type\"));\n\tif (envelope.error) {\n\t\tthrow new Error(envelope.error.message ?? `DeepWiki JSON-RPC error ${envelope.error.code ?? \"unknown\"}`);\n\t}\n\n\tconst resultText = extractToolText(envelope.result);\n\tconst pageTitles = extractPageTitles(toolName, resultText);\n\tconst result: DeepWikiResponse = {\n\t\ttoolName,\n\t\ttext: resultText,\n\t\toutputLength: resultText.length,\n\t\t...(pageTitles.length ? { pageTitles } : {}),\n\t};\n\tsignal?.throwIfAborted();\n\tresponseCache.set(key, { expiresAt: Date.now() + CACHE_TTL_MS, response: cloneResponse(result, false) });\n\twhile (responseCache.size > MAX_CACHE_ENTRIES) {\n\t\tconst oldest = responseCache.keys().next().value;\n\t\tif (oldest === undefined) break;\n\t\tresponseCache.delete(oldest);\n\t}\n\treturn result;\n}\n"]}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/extensions/deepwiki/client.ts"],"names":[],"mappings":"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","sourcesContent":["import type { CallToolResult, TextContent } from \"@earendil-works/pi-mcp\";\nimport { PAGE_HEADER_RE } from \"./contents.ts\";\nimport { type DeepWikiParams, normalizeDeepWikiParams } from \"./schema.ts\";\nimport { callDeepWikiTool } from \"./transport.ts\";\n\nconst CACHE_TTL_MS = 10 * 60_000;\n/** Long-lived process guard: entries are only otherwise dropped on re-read after expiry. */\nconst MAX_CACHE_ENTRIES = 50;\n\ntype DeepWikiToolName = \"read_wiki_structure\" | \"read_wiki_contents\" | \"ask_question\";\n\nexport interface DeepWikiResponse {\n\ttoolName: DeepWikiToolName;\n\ttext: string;\n\toutputLength: number;\n\tpageTitles?: string[];\n\tcacheHit?: boolean;\n}\n\ninterface CacheEntry {\n\texpiresAt: number;\n\tresponse: DeepWikiResponse;\n}\n\nconst responseCache = new Map<string, CacheEntry>();\n\nfunction toolNameForAction(action: DeepWikiParams[\"action\"]): DeepWikiToolName {\n\tif (action === \"structure\") return \"read_wiki_structure\";\n\tif (action === \"contents\") return \"read_wiki_contents\";\n\treturn \"ask_question\";\n}\n\n// Callers pass params already normalized by callDeepWiki's entry gate; no re-normalization.\nfunction argumentsForParams(params: DeepWikiParams): Record<string, unknown> {\n\tif (params.action === \"question\") {\n\t\tconst question = params.question?.trim();\n\t\tif (!question) throw new Error(\"question is required when action is question\");\n\t\treturn { repoName: params.repoName, question };\n\t}\n\treturn { repoName: params.repoName };\n}\n\nfunction cacheKey(params: DeepWikiParams): string {\n\tconst repos = Array.isArray(params.repoName) ? params.repoName : [params.repoName];\n\treturn JSON.stringify([params.action, repos, params.question ?? \"\"]);\n}\n\nfunction cloneResponse(response: DeepWikiResponse, cacheHit: boolean): DeepWikiResponse {\n\treturn {\n\t\t...response,\n\t\t...(response.pageTitles ? { pageTitles: [...response.pageTitles] } : {}),\n\t\tcacheHit,\n\t};\n}\n\nfunction extractStructuredResult(result: Partial<CallToolResult>): string | undefined {\n\tif (!result.structuredContent || typeof result.structuredContent !== \"object\") return undefined;\n\tconst value = (result.structuredContent as Record<string, unknown>).result;\n\treturn typeof value === \"string\" ? value : undefined;\n}\n\nfunction extractContentResult(result: Partial<CallToolResult>): string {\n\tif (!Array.isArray(result.content)) return \"\";\n\treturn result.content\n\t\t.filter((part): part is TextContent => {\n\t\t\tif (!part || typeof part !== \"object\") return false;\n\t\t\tconst record = part as unknown as Record<string, unknown>;\n\t\t\treturn record.type === \"text\" && typeof record.text === \"string\";\n\t\t})\n\t\t.map((part) => part.text)\n\t\t.join(\"\\n\")\n\t\t.trim();\n}\n\nfunction isDeepWikiErrorText(text: string): boolean {\n\tconst trimmed = text.trim();\n\treturn (\n\t\t/^Error fetching wiki for .+?: Repository not found\\./i.test(trimmed) ||\n\t\t/^Error processing question: Repository not found\\./i.test(trimmed)\n\t);\n}\n\nfunction extractToolText(result: unknown): string {\n\tif (!result || typeof result !== \"object\") throw new Error(\"DeepWiki returned no result object\");\n\n\tconst toolResult = result as Partial<CallToolResult>;\n\tconst text = extractStructuredResult(toolResult) ?? extractContentResult(toolResult);\n\tif (!text) throw new Error(\"DeepWiki returned no text content\");\n\tif (toolResult.isError || isDeepWikiErrorText(text)) throw new Error(text);\n\treturn text;\n}\n\nexport function extractStructureSections(text: string): string[] {\n\treturn uniqueTitles(text.split(\"\\n\").map((line) => line.match(/^\\s*-\\s+\\d+\\s+(.+)$/)?.[1]?.trim()));\n}\n\nexport function extractContentPages(text: string): string[] {\n\treturn uniqueTitles(text.split(\"\\n\").map((line) => line.match(PAGE_HEADER_RE)?.[1]?.trim()));\n}\n\nfunction uniqueTitles(titles: Array<string | undefined>): string[] {\n\tconst seen = new Set<string>();\n\tconst result: string[] = [];\n\tfor (const title of titles) {\n\t\tif (!title || seen.has(title)) continue;\n\t\tseen.add(title);\n\t\tresult.push(title);\n\t}\n\treturn result;\n}\n\nfunction extractPageTitles(toolName: DeepWikiToolName, text: string): string[] {\n\tif (toolName === \"read_wiki_structure\") return extractStructureSections(text);\n\tif (toolName === \"read_wiki_contents\") return extractContentPages(text);\n\treturn [];\n}\n\nexport async function callDeepWiki(params: DeepWikiParams, signal: AbortSignal | undefined): Promise<DeepWikiResponse> {\n\tsignal?.throwIfAborted();\n\tconst normalizedParams = normalizeDeepWikiParams(params);\n\tconst key = cacheKey(normalizedParams);\n\tconst cached = responseCache.get(key);\n\tif (cached && cached.expiresAt > Date.now()) {\n\t\treturn cloneResponse(cached.response, true);\n\t}\n\tif (cached) responseCache.delete(key);\n\n\tconst toolName = toolNameForAction(normalizedParams.action);\n\tconst result = await callDeepWikiTool(toolName, argumentsForParams(normalizedParams), signal);\n\tsignal?.throwIfAborted();\n\n\tconst resultText = extractToolText(result);\n\tconst pageTitles = extractPageTitles(toolName, resultText);\n\tconst response: DeepWikiResponse = {\n\t\ttoolName,\n\t\ttext: resultText,\n\t\toutputLength: resultText.length,\n\t\t...(pageTitles.length ? { pageTitles } : {}),\n\t};\n\tsignal?.throwIfAborted();\n\tresponseCache.set(key, { expiresAt: Date.now() + CACHE_TTL_MS, response: cloneResponse(response, false) });\n\twhile (responseCache.size > MAX_CACHE_ENTRIES) {\n\t\tconst oldest = responseCache.keys().next().value;\n\t\tif (oldest === undefined) break;\n\t\tresponseCache.delete(oldest);\n\t}\n\treturn response;\n}\n"]}
@@ -0,0 +1,3 @@
1
+ /** Call a DeepWiki MCP tool and return the raw `result` of the JSON-RPC response. */
2
+ export declare function callDeepWikiTool(toolName: string, args: Record<string, unknown>, signal: AbortSignal | undefined): Promise<unknown>;
3
+ //# sourceMappingURL=transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../../src/extensions/deepwiki/transport.ts"],"names":[],"mappings":"AA6DA,qFAAqF;AACrF,wBAAsB,gBAAgB,CACrC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,MAAM,EAAE,WAAW,GAAG,SAAS,GAC7B,OAAO,CAAC,OAAO,CAAC,CAoClB"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * transport.ts — one stateless MCP `tools/call` round trip to DeepWiki.
3
+ *
4
+ * DeepWiki's public endpoint issues no `Mcp-Session-Id`, so a session-bearing
5
+ * `McpClient` would only add an initialize handshake to every uncached call.
6
+ * The request therefore stays a single POST. Retry and abort semantics come from
7
+ * `management-http`, body bounding from `http-response`, and JSON-RPC validation
8
+ * from `@earendil-works/pi-mcp`. The SSE framing is the only local protocol code
9
+ * because pi-mcp does not export its stream parser.
10
+ */
11
+ import { isJsonRpcResponse, McpError, parseJsonRpcMessage, } from "@earendil-works/pi-mcp";
12
+ import { readResponseTextBounded } from "../../utils/http-response.js";
13
+ import { fetchWithRetry } from "../../utils/management-http.js";
14
+ const DEEPWIKI_MCP_URL = "https://mcp.deepwiki.com/mcp";
15
+ const REQUEST_TIMEOUT_MS = 45_000;
16
+ const RESPONSE_BODY_BYTES = 8 * 1024 * 1024;
17
+ const ERROR_BODY_CHARS = 500;
18
+ /** Pinned, not pi-mcp's latest: this call never negotiates, so it sends the version DeepWiki is known to accept. */
19
+ const PROTOCOL_VERSION = "2024-11-05";
20
+ function parseJson(text) {
21
+ try {
22
+ return JSON.parse(text);
23
+ }
24
+ catch (error) {
25
+ throw new Error(`DeepWiki returned invalid JSON: ${error instanceof Error ? error.message : String(error)}`);
26
+ }
27
+ }
28
+ function parseJsonRpcResponse(value) {
29
+ const message = parseJsonRpcMessage(value);
30
+ if (!isJsonRpcResponse(message))
31
+ throw new Error("DeepWiki returned a JSON-RPC message that is not a response");
32
+ return message;
33
+ }
34
+ /** The first JSON-RPC response in an SSE body; notifications and `[DONE]` are skipped. */
35
+ function parseSseResponse(text) {
36
+ for (const block of text.split(/\r?\n\r?\n/)) {
37
+ const data = block
38
+ .split(/\r?\n/)
39
+ .filter((line) => line.startsWith("data:"))
40
+ .map((line) => line.slice(5).replace(/^ /, ""))
41
+ .join("\n")
42
+ .trim();
43
+ if (!data || data === "[DONE]")
44
+ continue;
45
+ const message = parseJsonRpcMessage(parseJson(data));
46
+ if (isJsonRpcResponse(message))
47
+ return message;
48
+ }
49
+ throw new Error("DeepWiki returned an empty event stream");
50
+ }
51
+ function truncate(text) {
52
+ return text.length <= ERROR_BODY_CHARS ? text : `${text.slice(0, ERROR_BODY_CHARS - 3)}...`;
53
+ }
54
+ /** Call a DeepWiki MCP tool and return the raw `result` of the JSON-RPC response. */
55
+ export async function callDeepWikiTool(toolName, args, signal) {
56
+ // One budget for connect, retries, and body read; it is terminal, so a timeout is never retried.
57
+ const timeout = AbortSignal.timeout(REQUEST_TIMEOUT_MS);
58
+ const requestSignal = signal ? AbortSignal.any([signal, timeout]) : timeout;
59
+ try {
60
+ const response = await fetchWithRetry(DEEPWIKI_MCP_URL, {
61
+ method: "POST",
62
+ headers: {
63
+ Accept: "application/json, text/event-stream",
64
+ "Content-Type": "application/json",
65
+ "MCP-Protocol-Version": PROTOCOL_VERSION,
66
+ },
67
+ body: JSON.stringify({
68
+ jsonrpc: "2.0",
69
+ id: 1,
70
+ method: "tools/call",
71
+ params: { name: toolName, arguments: args },
72
+ }),
73
+ signal: requestSignal,
74
+ });
75
+ const text = await readResponseTextBounded(response, {
76
+ maxBytes: RESPONSE_BODY_BYTES,
77
+ overflowMessage: `DeepWiki response exceeded ${RESPONSE_BODY_BYTES} bytes`,
78
+ signal: requestSignal,
79
+ });
80
+ if (!response.ok)
81
+ throw new Error(`DeepWiki HTTP ${response.status}: ${truncate(text)}`);
82
+ const isSse = response.headers.get("content-type")?.includes("text/event-stream");
83
+ const message = isSse ? parseSseResponse(text) : parseJsonRpcResponse(parseJson(text));
84
+ if ("error" in message)
85
+ throw new McpError(message.error.code, message.error.message, message.error.data);
86
+ return message.result;
87
+ }
88
+ catch (error) {
89
+ if (signal?.aborted)
90
+ throw new Error("DeepWiki request aborted");
91
+ if (timeout.aborted)
92
+ throw new Error(`DeepWiki request timed out after ${REQUEST_TIMEOUT_MS / 1000}s`);
93
+ throw error;
94
+ }
95
+ }
96
+ //# sourceMappingURL=transport.js.map