whale-code 6.4.0

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 (319) hide show
  1. package/README.md +95 -0
  2. package/bin/swag-agent.js +9 -0
  3. package/bin/swagmanager-mcp.js +321 -0
  4. package/dist/cli/app.d.ts +26 -0
  5. package/dist/cli/app.js +64 -0
  6. package/dist/cli/chat/AgentSelector.d.ts +14 -0
  7. package/dist/cli/chat/AgentSelector.js +14 -0
  8. package/dist/cli/chat/ChatApp.d.ts +9 -0
  9. package/dist/cli/chat/ChatApp.js +267 -0
  10. package/dist/cli/chat/ChatInput.d.ts +39 -0
  11. package/dist/cli/chat/ChatInput.js +509 -0
  12. package/dist/cli/chat/MarkdownText.d.ts +10 -0
  13. package/dist/cli/chat/MarkdownText.js +20 -0
  14. package/dist/cli/chat/MessageList.d.ts +37 -0
  15. package/dist/cli/chat/MessageList.js +80 -0
  16. package/dist/cli/chat/ModelSelector.d.ts +20 -0
  17. package/dist/cli/chat/ModelSelector.js +73 -0
  18. package/dist/cli/chat/RewindViewer.d.ts +26 -0
  19. package/dist/cli/chat/RewindViewer.js +185 -0
  20. package/dist/cli/chat/StoreSelector.d.ts +14 -0
  21. package/dist/cli/chat/StoreSelector.js +24 -0
  22. package/dist/cli/chat/StreamingText.d.ts +12 -0
  23. package/dist/cli/chat/StreamingText.js +12 -0
  24. package/dist/cli/chat/SubagentPanel.d.ts +45 -0
  25. package/dist/cli/chat/SubagentPanel.js +110 -0
  26. package/dist/cli/chat/TeamPanel.d.ts +21 -0
  27. package/dist/cli/chat/TeamPanel.js +42 -0
  28. package/dist/cli/chat/ToolIndicator.d.ts +25 -0
  29. package/dist/cli/chat/ToolIndicator.js +436 -0
  30. package/dist/cli/chat/hooks/useAgentLoop.d.ts +39 -0
  31. package/dist/cli/chat/hooks/useAgentLoop.js +382 -0
  32. package/dist/cli/chat/hooks/useSlashCommands.d.ts +37 -0
  33. package/dist/cli/chat/hooks/useSlashCommands.js +387 -0
  34. package/dist/cli/commands/config-cmd.d.ts +10 -0
  35. package/dist/cli/commands/config-cmd.js +99 -0
  36. package/dist/cli/commands/doctor.d.ts +14 -0
  37. package/dist/cli/commands/doctor.js +172 -0
  38. package/dist/cli/commands/init.d.ts +16 -0
  39. package/dist/cli/commands/init.js +278 -0
  40. package/dist/cli/commands/mcp.d.ts +12 -0
  41. package/dist/cli/commands/mcp.js +162 -0
  42. package/dist/cli/login/LoginApp.d.ts +7 -0
  43. package/dist/cli/login/LoginApp.js +157 -0
  44. package/dist/cli/print-mode.d.ts +31 -0
  45. package/dist/cli/print-mode.js +202 -0
  46. package/dist/cli/serve-mode.d.ts +37 -0
  47. package/dist/cli/serve-mode.js +636 -0
  48. package/dist/cli/services/agent-definitions.d.ts +25 -0
  49. package/dist/cli/services/agent-definitions.js +91 -0
  50. package/dist/cli/services/agent-events.d.ts +178 -0
  51. package/dist/cli/services/agent-events.js +175 -0
  52. package/dist/cli/services/agent-loop.d.ts +90 -0
  53. package/dist/cli/services/agent-loop.js +762 -0
  54. package/dist/cli/services/agent-worker-base.d.ts +97 -0
  55. package/dist/cli/services/agent-worker-base.js +220 -0
  56. package/dist/cli/services/auth-service.d.ts +30 -0
  57. package/dist/cli/services/auth-service.js +160 -0
  58. package/dist/cli/services/background-processes.d.ts +126 -0
  59. package/dist/cli/services/background-processes.js +318 -0
  60. package/dist/cli/services/browser-auth.d.ts +24 -0
  61. package/dist/cli/services/browser-auth.js +180 -0
  62. package/dist/cli/services/claude-md-loader.d.ts +16 -0
  63. package/dist/cli/services/claude-md-loader.js +58 -0
  64. package/dist/cli/services/config-store.d.ts +47 -0
  65. package/dist/cli/services/config-store.js +79 -0
  66. package/dist/cli/services/debug-log.d.ts +10 -0
  67. package/dist/cli/services/debug-log.js +52 -0
  68. package/dist/cli/services/error-logger.d.ts +58 -0
  69. package/dist/cli/services/error-logger.js +269 -0
  70. package/dist/cli/services/file-history.d.ts +21 -0
  71. package/dist/cli/services/file-history.js +83 -0
  72. package/dist/cli/services/format-server-response.d.ts +16 -0
  73. package/dist/cli/services/format-server-response.js +440 -0
  74. package/dist/cli/services/git-context.d.ts +11 -0
  75. package/dist/cli/services/git-context.js +66 -0
  76. package/dist/cli/services/hooks.d.ts +85 -0
  77. package/dist/cli/services/hooks.js +258 -0
  78. package/dist/cli/services/interactive-tools.d.ts +125 -0
  79. package/dist/cli/services/interactive-tools.js +260 -0
  80. package/dist/cli/services/keybinding-manager.d.ts +52 -0
  81. package/dist/cli/services/keybinding-manager.js +115 -0
  82. package/dist/cli/services/local-tools.d.ts +22 -0
  83. package/dist/cli/services/local-tools.js +697 -0
  84. package/dist/cli/services/lsp-manager.d.ts +18 -0
  85. package/dist/cli/services/lsp-manager.js +717 -0
  86. package/dist/cli/services/mcp-client.d.ts +48 -0
  87. package/dist/cli/services/mcp-client.js +157 -0
  88. package/dist/cli/services/memory-manager.d.ts +16 -0
  89. package/dist/cli/services/memory-manager.js +57 -0
  90. package/dist/cli/services/model-manager.d.ts +18 -0
  91. package/dist/cli/services/model-manager.js +71 -0
  92. package/dist/cli/services/model-router.d.ts +26 -0
  93. package/dist/cli/services/model-router.js +149 -0
  94. package/dist/cli/services/permission-modes.d.ts +13 -0
  95. package/dist/cli/services/permission-modes.js +43 -0
  96. package/dist/cli/services/rewind.d.ts +84 -0
  97. package/dist/cli/services/rewind.js +194 -0
  98. package/dist/cli/services/ripgrep.d.ts +28 -0
  99. package/dist/cli/services/ripgrep.js +138 -0
  100. package/dist/cli/services/sandbox.d.ts +29 -0
  101. package/dist/cli/services/sandbox.js +97 -0
  102. package/dist/cli/services/server-tools.d.ts +61 -0
  103. package/dist/cli/services/server-tools.js +543 -0
  104. package/dist/cli/services/session-persistence.d.ts +23 -0
  105. package/dist/cli/services/session-persistence.js +99 -0
  106. package/dist/cli/services/subagent-worker.d.ts +19 -0
  107. package/dist/cli/services/subagent-worker.js +41 -0
  108. package/dist/cli/services/subagent.d.ts +47 -0
  109. package/dist/cli/services/subagent.js +647 -0
  110. package/dist/cli/services/system-prompt.d.ts +7 -0
  111. package/dist/cli/services/system-prompt.js +198 -0
  112. package/dist/cli/services/team-lead.d.ts +73 -0
  113. package/dist/cli/services/team-lead.js +512 -0
  114. package/dist/cli/services/team-state.d.ts +77 -0
  115. package/dist/cli/services/team-state.js +398 -0
  116. package/dist/cli/services/teammate.d.ts +31 -0
  117. package/dist/cli/services/teammate.js +689 -0
  118. package/dist/cli/services/telemetry.d.ts +61 -0
  119. package/dist/cli/services/telemetry.js +209 -0
  120. package/dist/cli/services/tools/agent-tools.d.ts +14 -0
  121. package/dist/cli/services/tools/agent-tools.js +347 -0
  122. package/dist/cli/services/tools/file-ops.d.ts +15 -0
  123. package/dist/cli/services/tools/file-ops.js +487 -0
  124. package/dist/cli/services/tools/search-tools.d.ts +8 -0
  125. package/dist/cli/services/tools/search-tools.js +186 -0
  126. package/dist/cli/services/tools/shell-exec.d.ts +10 -0
  127. package/dist/cli/services/tools/shell-exec.js +168 -0
  128. package/dist/cli/services/tools/task-manager.d.ts +28 -0
  129. package/dist/cli/services/tools/task-manager.js +209 -0
  130. package/dist/cli/services/tools/web-tools.d.ts +11 -0
  131. package/dist/cli/services/tools/web-tools.js +395 -0
  132. package/dist/cli/setup/SetupApp.d.ts +9 -0
  133. package/dist/cli/setup/SetupApp.js +191 -0
  134. package/dist/cli/shared/MatrixIntro.d.ts +4 -0
  135. package/dist/cli/shared/MatrixIntro.js +83 -0
  136. package/dist/cli/shared/Theme.d.ts +74 -0
  137. package/dist/cli/shared/Theme.js +127 -0
  138. package/dist/cli/shared/WhaleBanner.d.ts +10 -0
  139. package/dist/cli/shared/WhaleBanner.js +12 -0
  140. package/dist/cli/shared/markdown.d.ts +21 -0
  141. package/dist/cli/shared/markdown.js +756 -0
  142. package/dist/cli/status/StatusApp.d.ts +4 -0
  143. package/dist/cli/status/StatusApp.js +105 -0
  144. package/dist/cli/stores/StoreApp.d.ts +7 -0
  145. package/dist/cli/stores/StoreApp.js +81 -0
  146. package/dist/index.d.ts +15 -0
  147. package/dist/index.js +538 -0
  148. package/dist/local-agent/connection.d.ts +48 -0
  149. package/dist/local-agent/connection.js +332 -0
  150. package/dist/local-agent/discovery.d.ts +18 -0
  151. package/dist/local-agent/discovery.js +146 -0
  152. package/dist/local-agent/executor.d.ts +34 -0
  153. package/dist/local-agent/executor.js +241 -0
  154. package/dist/local-agent/index.d.ts +14 -0
  155. package/dist/local-agent/index.js +198 -0
  156. package/dist/node/adapters/base.d.ts +35 -0
  157. package/dist/node/adapters/base.js +10 -0
  158. package/dist/node/adapters/discord.d.ts +29 -0
  159. package/dist/node/adapters/discord.js +299 -0
  160. package/dist/node/adapters/email.d.ts +23 -0
  161. package/dist/node/adapters/email.js +218 -0
  162. package/dist/node/adapters/imessage.d.ts +17 -0
  163. package/dist/node/adapters/imessage.js +118 -0
  164. package/dist/node/adapters/slack.d.ts +26 -0
  165. package/dist/node/adapters/slack.js +259 -0
  166. package/dist/node/adapters/sms.d.ts +23 -0
  167. package/dist/node/adapters/sms.js +161 -0
  168. package/dist/node/adapters/telegram.d.ts +17 -0
  169. package/dist/node/adapters/telegram.js +101 -0
  170. package/dist/node/adapters/webchat.d.ts +27 -0
  171. package/dist/node/adapters/webchat.js +160 -0
  172. package/dist/node/adapters/whatsapp.d.ts +28 -0
  173. package/dist/node/adapters/whatsapp.js +230 -0
  174. package/dist/node/cli.d.ts +2 -0
  175. package/dist/node/cli.js +325 -0
  176. package/dist/node/config.d.ts +17 -0
  177. package/dist/node/config.js +31 -0
  178. package/dist/node/runtime.d.ts +50 -0
  179. package/dist/node/runtime.js +351 -0
  180. package/dist/server/handlers/__test-utils__/mock-supabase.d.ts +11 -0
  181. package/dist/server/handlers/__test-utils__/mock-supabase.js +393 -0
  182. package/dist/server/handlers/analytics.d.ts +17 -0
  183. package/dist/server/handlers/analytics.js +266 -0
  184. package/dist/server/handlers/api-keys.d.ts +6 -0
  185. package/dist/server/handlers/api-keys.js +221 -0
  186. package/dist/server/handlers/billing.d.ts +33 -0
  187. package/dist/server/handlers/billing.js +272 -0
  188. package/dist/server/handlers/browser.d.ts +10 -0
  189. package/dist/server/handlers/browser.js +517 -0
  190. package/dist/server/handlers/catalog.d.ts +99 -0
  191. package/dist/server/handlers/catalog.js +976 -0
  192. package/dist/server/handlers/comms.d.ts +254 -0
  193. package/dist/server/handlers/comms.js +588 -0
  194. package/dist/server/handlers/creations.d.ts +6 -0
  195. package/dist/server/handlers/creations.js +479 -0
  196. package/dist/server/handlers/crm.d.ts +89 -0
  197. package/dist/server/handlers/crm.js +538 -0
  198. package/dist/server/handlers/discovery.d.ts +6 -0
  199. package/dist/server/handlers/discovery.js +288 -0
  200. package/dist/server/handlers/embeddings.d.ts +92 -0
  201. package/dist/server/handlers/embeddings.js +197 -0
  202. package/dist/server/handlers/enrichment.d.ts +8 -0
  203. package/dist/server/handlers/enrichment.js +768 -0
  204. package/dist/server/handlers/image-gen.d.ts +6 -0
  205. package/dist/server/handlers/image-gen.js +409 -0
  206. package/dist/server/handlers/inventory.d.ts +319 -0
  207. package/dist/server/handlers/inventory.js +447 -0
  208. package/dist/server/handlers/kali.d.ts +10 -0
  209. package/dist/server/handlers/kali.js +210 -0
  210. package/dist/server/handlers/llm-providers.d.ts +6 -0
  211. package/dist/server/handlers/llm-providers.js +673 -0
  212. package/dist/server/handlers/local-agent.d.ts +6 -0
  213. package/dist/server/handlers/local-agent.js +118 -0
  214. package/dist/server/handlers/meta-ads.d.ts +111 -0
  215. package/dist/server/handlers/meta-ads.js +2279 -0
  216. package/dist/server/handlers/nodes.d.ts +33 -0
  217. package/dist/server/handlers/nodes.js +699 -0
  218. package/dist/server/handlers/operations.d.ts +138 -0
  219. package/dist/server/handlers/operations.js +131 -0
  220. package/dist/server/handlers/platform.d.ts +23 -0
  221. package/dist/server/handlers/platform.js +227 -0
  222. package/dist/server/handlers/supply-chain.d.ts +19 -0
  223. package/dist/server/handlers/supply-chain.js +327 -0
  224. package/dist/server/handlers/transcription.d.ts +17 -0
  225. package/dist/server/handlers/transcription.js +121 -0
  226. package/dist/server/handlers/video-gen.d.ts +6 -0
  227. package/dist/server/handlers/video-gen.js +466 -0
  228. package/dist/server/handlers/voice.d.ts +8 -0
  229. package/dist/server/handlers/voice.js +1146 -0
  230. package/dist/server/handlers/workflow-steps.d.ts +86 -0
  231. package/dist/server/handlers/workflow-steps.js +2349 -0
  232. package/dist/server/handlers/workflows.d.ts +7 -0
  233. package/dist/server/handlers/workflows.js +989 -0
  234. package/dist/server/index.d.ts +1 -0
  235. package/dist/server/index.js +2427 -0
  236. package/dist/server/lib/batch-client.d.ts +80 -0
  237. package/dist/server/lib/batch-client.js +467 -0
  238. package/dist/server/lib/code-worker-pool.d.ts +31 -0
  239. package/dist/server/lib/code-worker-pool.js +224 -0
  240. package/dist/server/lib/code-worker.d.ts +1 -0
  241. package/dist/server/lib/code-worker.js +188 -0
  242. package/dist/server/lib/compaction-service.d.ts +32 -0
  243. package/dist/server/lib/compaction-service.js +162 -0
  244. package/dist/server/lib/logger.d.ts +19 -0
  245. package/dist/server/lib/logger.js +46 -0
  246. package/dist/server/lib/otel.d.ts +38 -0
  247. package/dist/server/lib/otel.js +126 -0
  248. package/dist/server/lib/pg-rate-limiter.d.ts +21 -0
  249. package/dist/server/lib/pg-rate-limiter.js +86 -0
  250. package/dist/server/lib/prompt-sanitizer.d.ts +37 -0
  251. package/dist/server/lib/prompt-sanitizer.js +177 -0
  252. package/dist/server/lib/provider-capabilities.d.ts +85 -0
  253. package/dist/server/lib/provider-capabilities.js +190 -0
  254. package/dist/server/lib/provider-failover.d.ts +74 -0
  255. package/dist/server/lib/provider-failover.js +210 -0
  256. package/dist/server/lib/rate-limiter.d.ts +39 -0
  257. package/dist/server/lib/rate-limiter.js +147 -0
  258. package/dist/server/lib/server-agent-loop.d.ts +107 -0
  259. package/dist/server/lib/server-agent-loop.js +667 -0
  260. package/dist/server/lib/server-subagent.d.ts +78 -0
  261. package/dist/server/lib/server-subagent.js +203 -0
  262. package/dist/server/lib/session-checkpoint.d.ts +51 -0
  263. package/dist/server/lib/session-checkpoint.js +145 -0
  264. package/dist/server/lib/ssrf-guard.d.ts +13 -0
  265. package/dist/server/lib/ssrf-guard.js +240 -0
  266. package/dist/server/lib/supabase-client.d.ts +7 -0
  267. package/dist/server/lib/supabase-client.js +78 -0
  268. package/dist/server/lib/template-resolver.d.ts +31 -0
  269. package/dist/server/lib/template-resolver.js +215 -0
  270. package/dist/server/lib/utils.d.ts +16 -0
  271. package/dist/server/lib/utils.js +147 -0
  272. package/dist/server/local-agent-gateway.d.ts +82 -0
  273. package/dist/server/local-agent-gateway.js +426 -0
  274. package/dist/server/providers/anthropic.d.ts +20 -0
  275. package/dist/server/providers/anthropic.js +199 -0
  276. package/dist/server/providers/bedrock.d.ts +20 -0
  277. package/dist/server/providers/bedrock.js +194 -0
  278. package/dist/server/providers/gemini.d.ts +24 -0
  279. package/dist/server/providers/gemini.js +486 -0
  280. package/dist/server/providers/openai.d.ts +24 -0
  281. package/dist/server/providers/openai.js +522 -0
  282. package/dist/server/providers/registry.d.ts +32 -0
  283. package/dist/server/providers/registry.js +58 -0
  284. package/dist/server/providers/shared.d.ts +32 -0
  285. package/dist/server/providers/shared.js +124 -0
  286. package/dist/server/providers/types.d.ts +92 -0
  287. package/dist/server/providers/types.js +12 -0
  288. package/dist/server/proxy-handlers.d.ts +6 -0
  289. package/dist/server/proxy-handlers.js +89 -0
  290. package/dist/server/tool-router.d.ts +149 -0
  291. package/dist/server/tool-router.js +803 -0
  292. package/dist/server/validation.d.ts +24 -0
  293. package/dist/server/validation.js +301 -0
  294. package/dist/server/worker.d.ts +19 -0
  295. package/dist/server/worker.js +201 -0
  296. package/dist/setup.d.ts +8 -0
  297. package/dist/setup.js +181 -0
  298. package/dist/shared/agent-core.d.ts +157 -0
  299. package/dist/shared/agent-core.js +534 -0
  300. package/dist/shared/anthropic-types.d.ts +105 -0
  301. package/dist/shared/anthropic-types.js +7 -0
  302. package/dist/shared/api-client.d.ts +90 -0
  303. package/dist/shared/api-client.js +379 -0
  304. package/dist/shared/constants.d.ts +33 -0
  305. package/dist/shared/constants.js +80 -0
  306. package/dist/shared/sse-parser.d.ts +26 -0
  307. package/dist/shared/sse-parser.js +259 -0
  308. package/dist/shared/tool-dispatch.d.ts +52 -0
  309. package/dist/shared/tool-dispatch.js +191 -0
  310. package/dist/shared/types.d.ts +72 -0
  311. package/dist/shared/types.js +7 -0
  312. package/dist/updater.d.ts +25 -0
  313. package/dist/updater.js +140 -0
  314. package/dist/webchat/widget.d.ts +0 -0
  315. package/dist/webchat/widget.js +397 -0
  316. package/package.json +95 -0
  317. package/src/cli/services/builtin-skills/commit.md +19 -0
  318. package/src/cli/services/builtin-skills/review-pr.md +21 -0
  319. package/src/cli/services/builtin-skills/review.md +18 -0
@@ -0,0 +1,534 @@
1
+ /**
2
+ * Shared Agent Core — single source of truth for CLI + server agent
3
+ *
4
+ * Pure TypeScript, no runtime-specific APIs (no Deno.env, no process.env, no fs).
5
+ * Both the CLI (Node.js) and server (Fly container) import from here.
6
+ */
7
+ import { getProvider } from "./constants.js";
8
+ /**
9
+ * Resolve the effective tool_choice for a given turn.
10
+ *
11
+ * Priority:
12
+ * 1. Explicit override from caller (toolChoice option)
13
+ * 2. Loop detection: if last 3 turns all used the same tool, force "none"
14
+ * 3. Keyword detection: if user message mentions a tool name, force that tool
15
+ * 4. Default: "auto"
16
+ */
17
+ export function resolveToolChoice(opts) {
18
+ // 1. Explicit override always wins
19
+ if (opts.toolChoice !== undefined)
20
+ return opts.toolChoice;
21
+ // 2. Loop detection: if last 3+ turns all used the exact same tool, force text
22
+ if (opts.recentToolUses.length >= 3) {
23
+ const last3 = opts.recentToolUses.slice(-3);
24
+ if (last3[0] === last3[1] && last3[1] === last3[2]) {
25
+ return "none";
26
+ }
27
+ }
28
+ // 3. Keyword matching: check if the user message mentions a specific tool name
29
+ // Only on the first turn (avoids false positives on multi-turn conversations)
30
+ if (opts.turnCount === 1 && opts.userMessage && opts.availableToolNames.length > 0) {
31
+ const msgLower = opts.userMessage.toLowerCase();
32
+ for (const toolName of opts.availableToolNames) {
33
+ // Match tool name as a whole word (e.g. "blender_do" not "do")
34
+ // Only consider tool names >= 4 chars to avoid false positives
35
+ if (toolName.length >= 4 && msgLower.includes(toolName.toLowerCase())) {
36
+ return { type: "tool", name: toolName };
37
+ }
38
+ }
39
+ }
40
+ // 4. Default
41
+ return "auto";
42
+ }
43
+ // ============================================================================
44
+ // MODEL-AWARE CONTEXT MANAGEMENT
45
+ // ============================================================================
46
+ /** Compaction trigger threshold — shared so agent loops can track budget */
47
+ export const COMPACTION_TRIGGER_TOKENS = 120_000;
48
+ /** Max cumulative tokens before forcing wrap-up (prevents runaway compaction cost) */
49
+ export const COMPACTION_TOTAL_BUDGET = 2_000_000;
50
+ /** Default session cost budget in USD — Infinity = no limit (budget enforcement disabled by default) */
51
+ export const DEFAULT_SESSION_COST_BUDGET_USD = Infinity;
52
+ export function getCompactionConfig(model) {
53
+ const provider = getProvider(model);
54
+ switch (provider) {
55
+ case "gemini":
56
+ return { triggerTokens: 700_000, totalBudget: 4_000_000, isNative: false };
57
+ case "openai":
58
+ return { triggerTokens: 120_000, totalBudget: 2_000_000, isNative: false };
59
+ case "bedrock":
60
+ case "anthropic":
61
+ default:
62
+ return { triggerTokens: COMPACTION_TRIGGER_TOKENS, totalBudget: COMPACTION_TOTAL_BUDGET, isNative: true };
63
+ }
64
+ }
65
+ /**
66
+ * Returns Anthropic beta flags and context_management config for the given model.
67
+ * - Opus 4.6 / Sonnet 4.6: compact at 120K (pause after) + clear thinking + clear tools at 80K/keep 3
68
+ * - All other Claude models: clear thinking + clear tools at 80K/keep 3
69
+ * - Non-Anthropic models (Gemini, OpenAI): no betas, no context management
70
+ */
71
+ export function getContextManagement(model) {
72
+ // Non-Anthropic models don't use Anthropic betas or context management
73
+ const provider = getProvider(model);
74
+ if (provider === "gemini" || provider === "openai") {
75
+ return { betas: [], config: { edits: [] } };
76
+ }
77
+ const edits = [];
78
+ const betas = ["context-management-2025-06-27"];
79
+ // Thinking block clearing — must come FIRST in edits array (API requirement).
80
+ // Keeps last 2 turns of thinking to maintain reasoning continuity while
81
+ // preventing unbounded growth from extended thinking.
82
+ edits.push({
83
+ type: "clear_thinking_20251015",
84
+ keep: { type: "thinking_turns", value: 2 },
85
+ });
86
+ // Server-side compaction for models that support compact_20260112.
87
+ // pause_after_compaction: true enables the loop to preserve recent messages
88
+ // and track compaction count for budget enforcement.
89
+ const supportsCompaction = model.includes("opus-4-6") || model.includes("sonnet-4-6");
90
+ if (supportsCompaction) {
91
+ edits.push({
92
+ type: "compact_20260112",
93
+ trigger: { type: "input_tokens", value: COMPACTION_TRIGGER_TOKENS },
94
+ pause_after_compaction: true,
95
+ instructions: "Summarize the conversation preserving: (1) task goals and constraints, (2) files created/modified with paths, (3) decisions made and rationale, (4) errors encountered and resolutions, (5) exact next steps. Be concise but preserve all state needed to continue work without repeating mistakes.",
96
+ });
97
+ betas.push("compact-2026-01-12");
98
+ }
99
+ edits.push({
100
+ type: "clear_tool_uses_20250919",
101
+ trigger: { type: "input_tokens", value: 80_000 },
102
+ keep: { type: "tool_uses", value: 3 },
103
+ });
104
+ return { betas, config: { edits } };
105
+ }
106
+ /**
107
+ * Model-aware max output tokens.
108
+ * Agent config max_tokens takes priority but is capped at model maximum.
109
+ *
110
+ * DEFAULT_OUTPUT_TOKENS is the sensible per-response cap (like Claude Code's ~16K).
111
+ * The full MODEL_MAX is only used when explicitly requested via agentMax.
112
+ */
113
+ const DEFAULT_OUTPUT_TOKENS = 16384; // 16K — sane default, prevents single-response burns
114
+ const MODEL_MAX_OUTPUT_TOKENS = {
115
+ // Anthropic — current models (actual API-enforced limits)
116
+ "claude-opus-4-6": 128000, // 128K
117
+ "claude-sonnet-4-6": 64000, // 64K (API enforces 64000, not 65536)
118
+ "claude-haiku-4-5-20251001": 64000, // 64K
119
+ // Anthropic — legacy models
120
+ "claude-sonnet-4-5-20250929": 64000, // 64K (API enforces 64000)
121
+ "claude-opus-4-5-20251101": 64000, // 64K
122
+ "claude-opus-4-1-20250805": 32768, // 32K
123
+ "claude-sonnet-4-20250514": 64000, // 64K
124
+ "claude-opus-4-20250514": 32768, // 32K
125
+ "claude-3-7-sonnet-20250219": 64000, // 64K
126
+ "claude-3-haiku-20240307": 4096, // 4K
127
+ // Bedrock — same limits as direct API
128
+ "anthropic.claude-sonnet-4-6": 64000,
129
+ "us.anthropic.claude-sonnet-4-20250514-v1:0": 64000,
130
+ "us.anthropic.claude-sonnet-4-5-20250929-v1:0": 64000,
131
+ "us.anthropic.claude-haiku-4-5-20251001-v1:0": 64000,
132
+ // Google Gemini
133
+ "gemini-3-pro-preview": 65536,
134
+ "gemini-3-flash-preview": 65536,
135
+ "gemini-2.5-pro": 65536,
136
+ "gemini-2.5-flash": 65536,
137
+ "gemini-2.5-flash-lite": 65536,
138
+ // OpenAI — GPT-5 family: 128K max output, o-series: 100K
139
+ "gpt-5": 128000,
140
+ "gpt-5-mini": 128000,
141
+ "gpt-5-nano": 128000,
142
+ "o3": 100000,
143
+ "o4-mini": 100000,
144
+ "gpt-4o": 16384,
145
+ };
146
+ export function getMaxOutputTokens(model, agentMax) {
147
+ const modelMax = MODEL_MAX_OUTPUT_TOKENS[model] ?? DEFAULT_OUTPUT_TOKENS;
148
+ // If caller explicitly sets agentMax, respect it (capped at model max).
149
+ // Otherwise use sane default — never auto-request 128K output.
150
+ if (agentMax)
151
+ return Math.min(agentMax, modelMax);
152
+ return Math.min(DEFAULT_OUTPUT_TOKENS, modelMax);
153
+ }
154
+ // ============================================================================
155
+ // MULTI-BREAKPOINT PROMPT CACHING
156
+ // ============================================================================
157
+ /**
158
+ * Add prompt cache breakpoints to tools and messages.
159
+ * Uses 2 of 4 allowed breakpoints:
160
+ * - Last tool definition
161
+ * - Turn boundary (second-to-last message)
162
+ * System prompt caching is handled by the caller.
163
+ */
164
+ export function addPromptCaching(tools, messages) {
165
+ const cachedTools = tools.length > 0
166
+ ? [...tools.slice(0, -1), { ...tools[tools.length - 1], cache_control: { type: "ephemeral" } }]
167
+ : [...tools];
168
+ const cachedMessages = [...messages];
169
+ if (cachedMessages.length >= 2) {
170
+ const idx = cachedMessages.length - 2;
171
+ const msg = cachedMessages[idx];
172
+ if (typeof msg.content === "string") {
173
+ cachedMessages[idx] = {
174
+ ...msg,
175
+ content: [{ type: "text", text: msg.content, cache_control: { type: "ephemeral" } }],
176
+ };
177
+ }
178
+ else if (Array.isArray(msg.content)) {
179
+ const blocks = [...msg.content];
180
+ blocks[blocks.length - 1] = { ...blocks[blocks.length - 1], cache_control: { type: "ephemeral" } };
181
+ cachedMessages[idx] = { ...msg, content: blocks };
182
+ }
183
+ }
184
+ return { tools: cachedTools, messages: cachedMessages };
185
+ }
186
+ // ============================================================================
187
+ // LOOP DETECTION
188
+ // ============================================================================
189
+ /** djb2 string hash — fast, deterministic, no dependencies */
190
+ function djb2Hash(str) {
191
+ let hash = 5381;
192
+ for (let i = 0; i < str.length; i++) {
193
+ hash = ((hash << 5) + hash + str.charCodeAt(i)) & 0xffffffff;
194
+ }
195
+ return hash.toString(36);
196
+ }
197
+ export class LoopDetector {
198
+ history = [];
199
+ consecutiveErrors = new Map();
200
+ turnErrors = 0;
201
+ turnHadErrors = false;
202
+ sessionErrors = new Map();
203
+ failedStrategies = new Set();
204
+ consecutiveFailedTurns = 0;
205
+ totalSessionErrors = 0;
206
+ static IDENTICAL_CALL_LIMIT = 4;
207
+ static CONSECUTIVE_ERROR_LIMIT = 3;
208
+ static TURN_ERROR_LIMIT = 5;
209
+ static WINDOW = 20;
210
+ static SESSION_TOOL_ERROR_LIMIT = 10;
211
+ static CONSECUTIVE_FAILED_TURN_LIMIT = 3;
212
+ /** Get the error-tracking key for a tool call. Tools with an `action` param
213
+ * are tracked per-action so e.g. voice/speak failing won't block voice/music_compose. */
214
+ errorKey(name, input) {
215
+ if (input && typeof input.action === "string")
216
+ return `${name}:${input.action}`;
217
+ return name;
218
+ }
219
+ recordCall(name, input) {
220
+ const inputHash = djb2Hash(JSON.stringify({ name, ...input }));
221
+ const eKey = this.errorKey(name, input);
222
+ if (this.failedStrategies.has(inputHash)) {
223
+ return {
224
+ blocked: true,
225
+ reason: `Blocked: this exact "${name}" call failed in a previous turn. Try a fundamentally different approach.`,
226
+ };
227
+ }
228
+ const sessionErrorCount = this.sessionErrors.get(eKey) || 0;
229
+ if (sessionErrorCount >= LoopDetector.SESSION_TOOL_ERROR_LIMIT) {
230
+ return {
231
+ blocked: true,
232
+ reason: `Tool "${name}" (action: ${input.action || "default"}) has failed ${sessionErrorCount} times this session. Stop using this tool and try a different approach.`,
233
+ };
234
+ }
235
+ if (this.turnErrors >= LoopDetector.TURN_ERROR_LIMIT) {
236
+ return {
237
+ blocked: true,
238
+ reason: `${this.turnErrors} errors this turn. Stop and re-assess your approach.`,
239
+ };
240
+ }
241
+ const errorCount = this.consecutiveErrors.get(eKey) || 0;
242
+ if (errorCount >= LoopDetector.CONSECUTIVE_ERROR_LIMIT) {
243
+ return {
244
+ blocked: true,
245
+ reason: `Tool "${name}" (action: ${input.action || "default"}) blocked: failed ${errorCount} times consecutively. Try a different approach or action.`,
246
+ };
247
+ }
248
+ const windowSlice = this.history.slice(-LoopDetector.WINDOW);
249
+ const identicalCount = windowSlice.filter((h) => h.inputHash === inputHash).length;
250
+ if (identicalCount >= LoopDetector.IDENTICAL_CALL_LIMIT) {
251
+ return {
252
+ blocked: true,
253
+ reason: `Tool "${name}" blocked: identical call made ${identicalCount} times. Try different parameters.`,
254
+ };
255
+ }
256
+ this.history.push({ name, inputHash });
257
+ if (this.history.length > LoopDetector.WINDOW * 2) {
258
+ this.history = this.history.slice(-LoopDetector.WINDOW);
259
+ }
260
+ return { blocked: false };
261
+ }
262
+ recordResult(name, success, input) {
263
+ const eKey = this.errorKey(name, input);
264
+ if (success) {
265
+ this.consecutiveErrors.delete(eKey);
266
+ }
267
+ else {
268
+ const current = this.consecutiveErrors.get(eKey) || 0;
269
+ this.consecutiveErrors.set(eKey, current + 1);
270
+ this.turnErrors++;
271
+ this.turnHadErrors = true;
272
+ const sessionCount = this.sessionErrors.get(eKey) || 0;
273
+ this.sessionErrors.set(eKey, sessionCount + 1);
274
+ this.totalSessionErrors++;
275
+ if (input) {
276
+ const inputHash = djb2Hash(JSON.stringify({ name, ...input }));
277
+ this.failedStrategies.add(inputHash);
278
+ if (this.failedStrategies.size > 200) {
279
+ const arr = Array.from(this.failedStrategies);
280
+ this.failedStrategies = new Set(arr.slice(-100));
281
+ }
282
+ }
283
+ }
284
+ }
285
+ endTurn() {
286
+ if (this.turnHadErrors) {
287
+ this.consecutiveFailedTurns++;
288
+ }
289
+ else {
290
+ this.consecutiveFailedTurns = 0;
291
+ // A clean turn means the agent has recovered — unblock previously failed strategies
292
+ // so it can retry calls that failed due to bad params (e.g., wrong UUID format)
293
+ this.failedStrategies.clear();
294
+ // Decay session error counts on clean turns so tools aren't permanently blocked.
295
+ // Each clean turn halves all session error counts, allowing recovery from
296
+ // transient issues while still blocking persistently broken tools.
297
+ if (this.sessionErrors.size > 0) {
298
+ for (const [key, count] of this.sessionErrors) {
299
+ const decayed = Math.floor(count / 2);
300
+ if (decayed <= 0) {
301
+ this.sessionErrors.delete(key);
302
+ }
303
+ else {
304
+ this.sessionErrors.set(key, decayed);
305
+ }
306
+ }
307
+ }
308
+ }
309
+ // Reset turn-level counters so the NEXT dispatch batch starts fresh.
310
+ // Session-level counters (sessionErrors, consecutiveFailedTurns) persist.
311
+ this.turnErrors = 0;
312
+ this.turnHadErrors = false;
313
+ if (this.consecutiveFailedTurns >= LoopDetector.CONSECUTIVE_FAILED_TURN_LIMIT) {
314
+ return {
315
+ shouldBail: true,
316
+ message: `You have had errors in ${this.consecutiveFailedTurns} consecutive turns (${this.totalSessionErrors} total errors). Your approach is not working. STOP and explain to the user what's failing.`,
317
+ };
318
+ }
319
+ return { shouldBail: false };
320
+ }
321
+ resetTurn() {
322
+ this.history = [];
323
+ this.consecutiveErrors.clear();
324
+ this.turnErrors = 0;
325
+ this.turnHadErrors = false;
326
+ }
327
+ reset() {
328
+ this.resetTurn();
329
+ this.sessionErrors.clear();
330
+ this.failedStrategies.clear();
331
+ this.consecutiveFailedTurns = 0;
332
+ this.totalSessionErrors = 0;
333
+ }
334
+ getSessionStats() {
335
+ return {
336
+ totalErrors: this.totalSessionErrors,
337
+ failedStrategies: this.failedStrategies.size,
338
+ consecutiveFailedTurns: this.consecutiveFailedTurns,
339
+ };
340
+ }
341
+ }
342
+ /**
343
+ * Returns the thinking configuration and required beta string for the given model.
344
+ * - Opus 4.6: adaptive thinking (no budget needed)
345
+ * - Sonnet/Haiku: enabled with 10000 token budget
346
+ * - budget_tokens must be strictly < max_tokens
347
+ */
348
+ export function getThinkingConfig(model, enabled) {
349
+ if (!enabled) {
350
+ return { thinking: { type: "disabled" }, beta: "" };
351
+ }
352
+ const provider = getProvider(model);
353
+ // Gemini models: thinking is always-on for 2.5+/3.x — signal pass-through
354
+ if (provider === "gemini") {
355
+ return { thinking: { type: "enabled" }, beta: "" };
356
+ }
357
+ // OpenAI models: reasoning models (o-series) have built-in reasoning, GPT models don't support thinking
358
+ if (provider === "openai") {
359
+ const isReasoning = /^o\d/.test(model);
360
+ return { thinking: { type: isReasoning ? "enabled" : "disabled" }, beta: "" };
361
+ }
362
+ if (model.includes("opus-4-6") || model.includes("sonnet-4-6")) {
363
+ return {
364
+ thinking: { type: "adaptive" },
365
+ beta: "adaptive-thinking-2026-01-28",
366
+ };
367
+ }
368
+ // Sonnet 4.5/4.0 / Haiku: fixed budget
369
+ return {
370
+ thinking: { type: "enabled", budget_tokens: 10_000 },
371
+ beta: "interleaved-thinking-2025-05-14",
372
+ };
373
+ }
374
+ // ============================================================================
375
+ // COST TRACKING
376
+ // ============================================================================
377
+ export const MODEL_PRICING = {
378
+ // Anthropic direct — Claude 4.x
379
+ "claude-sonnet-4-6": { inputPer1M: 3.0, outputPer1M: 15.0, thinkingPer1M: 15.0 },
380
+ "claude-sonnet-4-20250514": { inputPer1M: 3.0, outputPer1M: 15.0, thinkingPer1M: 15.0 },
381
+ "claude-sonnet-4-5-20250929": { inputPer1M: 3.0, outputPer1M: 15.0, thinkingPer1M: 15.0 },
382
+ "claude-opus-4-6": { inputPer1M: 5.0, outputPer1M: 25.0, thinkingPer1M: 25.0 },
383
+ "claude-opus-4-20250514": { inputPer1M: 5.0, outputPer1M: 25.0, thinkingPer1M: 25.0 },
384
+ "claude-opus-4-5-20251101": { inputPer1M: 5.0, outputPer1M: 25.0, thinkingPer1M: 25.0 },
385
+ "claude-haiku-4-20250514": { inputPer1M: 1.0, outputPer1M: 5.0, thinkingPer1M: 5.0 },
386
+ "claude-haiku-4-5-20251001": { inputPer1M: 1.0, outputPer1M: 5.0, thinkingPer1M: 5.0 },
387
+ // Anthropic direct — Claude 3.5
388
+ "claude-3-5-sonnet-20241022": { inputPer1M: 3.0, outputPer1M: 15.0 },
389
+ "claude-3-5-haiku-20241022": { inputPer1M: 0.80, outputPer1M: 4.0 },
390
+ // Bedrock — Claude 4.x
391
+ "anthropic.claude-sonnet-4-6": { inputPer1M: 3.0, outputPer1M: 15.0, thinkingPer1M: 15.0 },
392
+ "us.anthropic.claude-sonnet-4-20250514-v1:0": { inputPer1M: 3.0, outputPer1M: 15.0, thinkingPer1M: 15.0 },
393
+ "us.anthropic.claude-sonnet-4-5-20250929-v1:0": { inputPer1M: 3.0, outputPer1M: 15.0, thinkingPer1M: 15.0 },
394
+ "us.anthropic.claude-haiku-4-5-20251001-v1:0": { inputPer1M: 1.0, outputPer1M: 5.0, thinkingPer1M: 5.0 },
395
+ // Bedrock — Claude 3.5
396
+ "us.anthropic.claude-3-5-haiku-20241022-v1:0": { inputPer1M: 0.80, outputPer1M: 4.0 },
397
+ // Bedrock — Llama & Nova
398
+ "us.meta.llama3-1-70b-instruct-v1:0": { inputPer1M: 0.72, outputPer1M: 0.72 },
399
+ "us.amazon.nova-pro-v1:0": { inputPer1M: 0.80, outputPer1M: 3.20 },
400
+ // Gemini (thinking tokens are cheaper than output)
401
+ "gemini-3-pro-preview": { inputPer1M: 1.25, outputPer1M: 10.0, thinkingPer1M: 2.50 },
402
+ "gemini-3-flash-preview": { inputPer1M: 0.15, outputPer1M: 0.60, thinkingPer1M: 0.15 },
403
+ "gemini-2.5-pro": { inputPer1M: 1.25, outputPer1M: 10.0, thinkingPer1M: 2.50 },
404
+ "gemini-2.5-flash": { inputPer1M: 0.15, outputPer1M: 0.60, thinkingPer1M: 0.15 },
405
+ "gemini-2.5-flash-lite": { inputPer1M: 0.075, outputPer1M: 0.30, thinkingPer1M: 0.075 },
406
+ "gemini-2.0-flash": { inputPer1M: 0.10, outputPer1M: 0.40 },
407
+ "gemini-2.0-flash-lite": { inputPer1M: 0.075, outputPer1M: 0.30 },
408
+ // OpenAI — GPT
409
+ "gpt-5": { inputPer1M: 1.25, outputPer1M: 10.0 },
410
+ "gpt-5-mini": { inputPer1M: 0.25, outputPer1M: 2.0 },
411
+ "gpt-5-nano": { inputPer1M: 0.05, outputPer1M: 0.40 },
412
+ "gpt-4o": { inputPer1M: 2.50, outputPer1M: 10.0 },
413
+ "gpt-4o-mini": { inputPer1M: 0.15, outputPer1M: 0.60 },
414
+ "gpt-4-turbo": { inputPer1M: 10.0, outputPer1M: 30.0 },
415
+ // OpenAI — reasoning models
416
+ "o1": { inputPer1M: 15.0, outputPer1M: 60.0, thinkingPer1M: 60.0 },
417
+ "o1-mini": { inputPer1M: 3.0, outputPer1M: 12.0, thinkingPer1M: 12.0 },
418
+ "o3": { inputPer1M: 2.0, outputPer1M: 8.0, thinkingPer1M: 8.0 },
419
+ "o3-mini": { inputPer1M: 1.10, outputPer1M: 4.40, thinkingPer1M: 4.40 },
420
+ "o4-mini": { inputPer1M: 1.10, outputPer1M: 4.40, thinkingPer1M: 4.40 },
421
+ };
422
+ export function estimateCostUsd(inputTokens, outputTokens, model, thinkingTokens = 0, cacheReadTokens = 0, cacheCreationTokens = 0) {
423
+ // Exact match first, then find a pricing key that is a prefix of the model ID
424
+ const pricing = MODEL_PRICING[model]
425
+ || MODEL_PRICING[Object.keys(MODEL_PRICING).find((k) => model.startsWith(k)) ?? ""]
426
+ || MODEL_PRICING["claude-sonnet-4-6"];
427
+ const thinkingRate = pricing.thinkingPer1M || pricing.outputPer1M;
428
+ const inputRate = pricing.inputPer1M;
429
+ // Base cost
430
+ let cost = (inputTokens / 1_000_000) * inputRate
431
+ + (outputTokens / 1_000_000) * pricing.outputPer1M
432
+ + (thinkingTokens / 1_000_000) * thinkingRate;
433
+ // Cache pricing — subtract savings for cached tokens
434
+ // Anthropic/Bedrock: cache reads 90% cheaper, cache creation 25% surcharge
435
+ // OpenAI: cache reads 50% cheaper, no creation surcharge
436
+ // Gemini: cache reads 75% cheaper
437
+ if (cacheReadTokens > 0 || cacheCreationTokens > 0) {
438
+ const provider = getProvider(model);
439
+ if (provider === "anthropic" || provider === "bedrock") {
440
+ cost -= (cacheReadTokens / 1_000_000) * inputRate * 0.9;
441
+ cost += (cacheCreationTokens / 1_000_000) * inputRate * 0.25;
442
+ }
443
+ else if (provider === "openai") {
444
+ cost -= (cacheReadTokens / 1_000_000) * inputRate * 0.5;
445
+ }
446
+ else if (provider === "gemini") {
447
+ cost -= (cacheReadTokens / 1_000_000) * inputRate * 0.75;
448
+ }
449
+ }
450
+ return cost;
451
+ }
452
+ // ============================================================================
453
+ // MODEL ROUTING BY TASK COMPLEXITY
454
+ // ============================================================================
455
+ /**
456
+ * Route to cheaper model when the task is simple enough.
457
+ * Returns the model to actually use.
458
+ */
459
+ export function routeModel(message, requestedModel, forceModel) {
460
+ // If user explicitly picked a model, respect it
461
+ if (forceModel)
462
+ return requestedModel;
463
+ // Estimate token count (rough: 1 token ~= 4 chars)
464
+ const estimatedTokens = Math.ceil(message.length / 4);
465
+ // Simple queries → Haiku (30x cheaper than Opus)
466
+ const simplePatterns = /^(what|who|when|where|show|list|get|find|look ?up|check)\b/i;
467
+ const complexPatterns = /\b(analyze|implement|refactor|design|architect|debug|explain why|compare|evaluate|write|create|build|fix)\b/i;
468
+ if (estimatedTokens < 50 && simplePatterns.test(message) && !complexPatterns.test(message)) {
469
+ return "claude-haiku-4-5-20251001";
470
+ }
471
+ // Medium queries → Sonnet
472
+ if (estimatedTokens < 200 && !complexPatterns.test(message)) {
473
+ return "claude-sonnet-4-5-20250929";
474
+ }
475
+ // Complex → honor requested model
476
+ return requestedModel;
477
+ }
478
+ export function categorizeError(err) {
479
+ const e = err;
480
+ const status = e?.status || e?.statusCode;
481
+ const msg = String(e?.message || "").toLowerCase();
482
+ if (status === 429 || msg.includes("rate limit")) {
483
+ return { category: "RATE_LIMIT", retryable: true };
484
+ }
485
+ if (status === 401 || status === 403 || msg.includes("unauthorized") || msg.includes("forbidden") || msg.includes("invalid api key")) {
486
+ return { category: "AUTH", retryable: false };
487
+ }
488
+ if (status === 400 || msg.includes("malformed") || msg.includes("invalid") || msg.includes("validation")) {
489
+ return { category: "MALFORMED", retryable: false };
490
+ }
491
+ if (status === 500 || status === 502 || status === 503 || status === 529 || msg.includes("overloaded") || msg.includes("internal server error")) {
492
+ return { category: "PROVIDER_DOWN", retryable: true };
493
+ }
494
+ if (msg.includes("timeout") || msg.includes("timed out") || msg.includes("deadline") || msg.includes("econnreset")) {
495
+ return { category: "TIMEOUT", retryable: true };
496
+ }
497
+ if (msg.includes("econnrefused") || msg.includes("enetunreach") || msg.includes("enotfound") || msg.includes("fetch failed") || msg.includes("network")) {
498
+ return { category: "NETWORK", retryable: true };
499
+ }
500
+ return { category: "UNKNOWN", retryable: false };
501
+ }
502
+ // ============================================================================
503
+ // RETRY LOGIC
504
+ // ============================================================================
505
+ export function isRetryableError(err) {
506
+ return categorizeError(err).retryable;
507
+ }
508
+ // ============================================================================
509
+ // TOOL RESULT TRUNCATION (deprecated — Anthropic context_management handles limits)
510
+ // ============================================================================
511
+ /** @deprecated — Anthropic context_management handles limits. Use SAFETY_MAX_CHARS in tool-dispatch instead. */
512
+ export function truncateToolResult(content, maxChars) {
513
+ if (content.length <= maxChars)
514
+ return content;
515
+ return content.slice(0, maxChars) + `\n\n... (truncated — ${content.length.toLocaleString()} chars total)`;
516
+ }
517
+ /** @deprecated — Anthropic context_management handles limits. */
518
+ export function getMaxToolResultChars(contextConfig) {
519
+ return contextConfig?.max_tool_result_chars || 80_000;
520
+ }
521
+ // ============================================================================
522
+ // UTILITY — sanitize errors (strip API keys, passwords)
523
+ // ============================================================================
524
+ export function sanitizeError(err) {
525
+ const msg = String(err);
526
+ return msg
527
+ .replace(/sk-[a-zA-Z0-9_-]+/g, "sk-***")
528
+ .replace(/AIzaSy[a-zA-Z0-9_-]+/g, "AIzaSy***")
529
+ .replace(/AKIA[A-Z0-9]{16}/g, "AKIA***")
530
+ .replace(/key[=:]\s*["']?[a-zA-Z0-9_-]{20,}["']?/gi, "key=***")
531
+ .replace(/password[=:]\s*["']?[^\s"']+["']?/gi, "password=***")
532
+ .replace(/\n\s+at\s+.*/g, "")
533
+ .substring(0, 500);
534
+ }
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Typed Anthropic SSE Events — replaces `as any` casts in stream processing.
3
+ *
4
+ * Based on Anthropic API streaming specification.
5
+ * Pure TypeScript, no runtime dependencies.
6
+ */
7
+ export interface TextContentBlock {
8
+ type: "text";
9
+ text: string;
10
+ }
11
+ export interface ToolUseContentBlock {
12
+ type: "tool_use";
13
+ id: string;
14
+ name: string;
15
+ }
16
+ export interface ThinkingContentBlock {
17
+ type: "thinking";
18
+ }
19
+ export interface CompactionContentBlock {
20
+ type: "compaction";
21
+ }
22
+ export interface CiteContentBlock {
23
+ type: "cite";
24
+ cited_text: string;
25
+ document_index: number;
26
+ document_title?: string;
27
+ start_char_index: number;
28
+ end_char_index: number;
29
+ }
30
+ export type ContentBlock = TextContentBlock | ToolUseContentBlock | ThinkingContentBlock | CompactionContentBlock | CiteContentBlock;
31
+ export interface TextDelta {
32
+ type: "text_delta";
33
+ text: string;
34
+ }
35
+ export interface InputJsonDelta {
36
+ type: "input_json_delta";
37
+ partial_json: string;
38
+ }
39
+ export interface ThinkingDelta {
40
+ type: "thinking_delta";
41
+ thinking: string;
42
+ }
43
+ export interface SignatureDelta {
44
+ type: "signature_delta";
45
+ signature: string;
46
+ }
47
+ export interface CompactionDelta {
48
+ type: "compaction_delta";
49
+ content: string;
50
+ }
51
+ export interface CiteDelta {
52
+ type: "cite_delta";
53
+ cited_text?: string;
54
+ document_index?: number;
55
+ document_title?: string;
56
+ start_char_index?: number;
57
+ end_char_index?: number;
58
+ }
59
+ export type ContentBlockDelta = TextDelta | InputJsonDelta | ThinkingDelta | SignatureDelta | CompactionDelta | CiteDelta;
60
+ export interface BetaUsage {
61
+ input_tokens: number;
62
+ output_tokens: number;
63
+ cache_creation_input_tokens?: number;
64
+ cache_read_input_tokens?: number;
65
+ }
66
+ export interface MessageStartEvent {
67
+ type: "message_start";
68
+ message: {
69
+ usage: BetaUsage;
70
+ };
71
+ }
72
+ export interface ContentBlockStartEvent {
73
+ type: "content_block_start";
74
+ index: number;
75
+ content_block: ContentBlock;
76
+ }
77
+ export interface ContentBlockDeltaEvent {
78
+ type: "content_block_delta";
79
+ index: number;
80
+ delta: ContentBlockDelta;
81
+ }
82
+ export interface ContentBlockStopEvent {
83
+ type: "content_block_stop";
84
+ index: number;
85
+ }
86
+ export interface MessageDeltaEvent {
87
+ type: "message_delta";
88
+ delta: {
89
+ stop_reason?: string;
90
+ context_management?: {
91
+ applied_edits?: Array<Record<string, unknown>>;
92
+ };
93
+ };
94
+ usage: {
95
+ output_tokens: number;
96
+ input_tokens?: number;
97
+ cache_read_input_tokens?: number;
98
+ thinking_tokens?: number;
99
+ };
100
+ }
101
+ export interface StreamErrorEvent {
102
+ type: "error";
103
+ error: string | Record<string, unknown>;
104
+ }
105
+ export type BetaStreamEvent = MessageStartEvent | ContentBlockStartEvent | ContentBlockDeltaEvent | ContentBlockStopEvent | MessageDeltaEvent | StreamErrorEvent;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Typed Anthropic SSE Events — replaces `as any` casts in stream processing.
3
+ *
4
+ * Based on Anthropic API streaming specification.
5
+ * Pure TypeScript, no runtime dependencies.
6
+ */
7
+ export {};