@ggui-ai/ui-gen 0.28.0 → 0.29.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 (308) hide show
  1. package/dist/{adapter-h4_V69LI.d.ts → adapter-DLQ-5COm.d.ts} +1 -1
  2. package/dist/adapters/base.js +2 -27
  3. package/dist/adapters/base.js.map +1 -1
  4. package/dist/adapters/generation-dispatch.d.ts +14 -6
  5. package/dist/adapters/generation-dispatch.js +40 -19260
  6. package/dist/adapters/generation-dispatch.js.map +1 -1
  7. package/dist/adapters/index.d.ts +1 -1
  8. package/dist/adapters/index.js +25 -3561
  9. package/dist/adapters/index.js.map +1 -1
  10. package/dist/adapters/types.js +2 -9
  11. package/dist/adapters/types.js.map +1 -1
  12. package/dist/advanced/index.d.ts +13 -12
  13. package/dist/advanced/index.js +63 -21954
  14. package/dist/advanced/index.js.map +1 -1
  15. package/dist/{bank-Ctdp3FUA.d.ts → bank-LWaiAtaA.d.ts} +1 -1
  16. package/dist/blueprint-validator.js +6 -2301
  17. package/dist/blueprint-validator.js.map +1 -1
  18. package/dist/boilerplate.d.ts +3 -2
  19. package/dist/boilerplate.js +6 -1867
  20. package/dist/boilerplate.js.map +1 -1
  21. package/dist/check/index.d.ts +3 -2
  22. package/dist/check/index.js +6 -1923
  23. package/dist/check/index.js.map +1 -1
  24. package/dist/chunk-33B3BAWS.js +24 -0
  25. package/dist/chunk-33B3BAWS.js.map +1 -0
  26. package/dist/chunk-3HSGA6NI.js +591 -0
  27. package/dist/chunk-3HSGA6NI.js.map +1 -0
  28. package/dist/chunk-3NNNGNTO.js +1707 -0
  29. package/dist/chunk-3NNNGNTO.js.map +1 -0
  30. package/dist/chunk-3T2HMTON.js +79 -0
  31. package/dist/chunk-3T2HMTON.js.map +1 -0
  32. package/dist/chunk-3ZSSDV3L.js +123 -0
  33. package/dist/chunk-3ZSSDV3L.js.map +1 -0
  34. package/dist/chunk-4MBR5GA3.js +99 -0
  35. package/dist/chunk-4MBR5GA3.js.map +1 -0
  36. package/dist/chunk-56NJFD55.js +337 -0
  37. package/dist/chunk-56NJFD55.js.map +1 -0
  38. package/dist/chunk-5AGTY555.js +26 -0
  39. package/dist/chunk-5AGTY555.js.map +1 -0
  40. package/dist/chunk-5EJM7EGY.js +115 -0
  41. package/dist/chunk-5EJM7EGY.js.map +1 -0
  42. package/dist/chunk-5KXCQLLR.js +529 -0
  43. package/dist/chunk-5KXCQLLR.js.map +1 -0
  44. package/dist/chunk-5VBXHVEH.js +171 -0
  45. package/dist/chunk-5VBXHVEH.js.map +1 -0
  46. package/dist/chunk-6KDDEING.js +515 -0
  47. package/dist/chunk-6KDDEING.js.map +1 -0
  48. package/dist/chunk-6VDRWAP6.js +123 -0
  49. package/dist/chunk-6VDRWAP6.js.map +1 -0
  50. package/dist/chunk-732IBFEJ.js +2400 -0
  51. package/dist/chunk-732IBFEJ.js.map +1 -0
  52. package/dist/chunk-73AURS3I.js +46 -0
  53. package/dist/chunk-73AURS3I.js.map +1 -0
  54. package/dist/chunk-77H5SKLX.js +8 -0
  55. package/dist/chunk-77H5SKLX.js.map +1 -0
  56. package/dist/chunk-7QE3AHLE.js +35 -0
  57. package/dist/chunk-7QE3AHLE.js.map +1 -0
  58. package/dist/chunk-A375NNWN.js +11 -0
  59. package/dist/chunk-A375NNWN.js.map +1 -0
  60. package/dist/chunk-AOU33EFS.js +140 -0
  61. package/dist/chunk-AOU33EFS.js.map +1 -0
  62. package/dist/chunk-BA4QYA6E.js +461 -0
  63. package/dist/chunk-BA4QYA6E.js.map +1 -0
  64. package/dist/chunk-DBP764GL.js +3 -0
  65. package/dist/chunk-DBP764GL.js.map +1 -0
  66. package/dist/chunk-DUU3ZJDQ.js +41 -0
  67. package/dist/chunk-DUU3ZJDQ.js.map +1 -0
  68. package/dist/chunk-DXKRBXNU.js +30 -0
  69. package/dist/chunk-DXKRBXNU.js.map +1 -0
  70. package/dist/chunk-FB7OEEAH.js +1467 -0
  71. package/dist/chunk-FB7OEEAH.js.map +1 -0
  72. package/dist/chunk-FLRNMZV2.js +18 -0
  73. package/dist/chunk-FLRNMZV2.js.map +1 -0
  74. package/dist/chunk-FWJIMKFZ.js +2032 -0
  75. package/dist/chunk-FWJIMKFZ.js.map +1 -0
  76. package/dist/chunk-FXRHWYIQ.js +163 -0
  77. package/dist/chunk-FXRHWYIQ.js.map +1 -0
  78. package/dist/chunk-G3PMV62Z.js +33 -0
  79. package/dist/chunk-G3PMV62Z.js.map +1 -0
  80. package/dist/chunk-GAJI7Y3P.js +13 -0
  81. package/dist/chunk-GAJI7Y3P.js.map +1 -0
  82. package/dist/chunk-GYINWMPR.js +2052 -0
  83. package/dist/chunk-GYINWMPR.js.map +1 -0
  84. package/dist/chunk-HD6ABIK7.js +484 -0
  85. package/dist/chunk-HD6ABIK7.js.map +1 -0
  86. package/dist/chunk-HDQVHYO6.js +100 -0
  87. package/dist/chunk-HDQVHYO6.js.map +1 -0
  88. package/dist/chunk-IHUT3SIW.js +7 -0
  89. package/dist/chunk-IHUT3SIW.js.map +1 -0
  90. package/dist/chunk-JDH5EGNZ.js +126 -0
  91. package/dist/chunk-JDH5EGNZ.js.map +1 -0
  92. package/dist/chunk-JMD4RGSV.js +37 -0
  93. package/dist/chunk-JMD4RGSV.js.map +1 -0
  94. package/dist/chunk-KJWGZL5B.js +33 -0
  95. package/dist/chunk-KJWGZL5B.js.map +1 -0
  96. package/dist/chunk-MKMDQFZ7.js +86 -0
  97. package/dist/chunk-MKMDQFZ7.js.map +1 -0
  98. package/dist/chunk-NF6JUZWF.js +85 -0
  99. package/dist/chunk-NF6JUZWF.js.map +1 -0
  100. package/dist/chunk-O2YC6XDC.js +1933 -0
  101. package/dist/chunk-O2YC6XDC.js.map +1 -0
  102. package/dist/chunk-OVLH5ICV.js +29 -0
  103. package/dist/chunk-OVLH5ICV.js.map +1 -0
  104. package/dist/chunk-PBOYHGSD.js +106 -0
  105. package/dist/chunk-PBOYHGSD.js.map +1 -0
  106. package/dist/chunk-PJFH7QWA.js +81 -0
  107. package/dist/chunk-PJFH7QWA.js.map +1 -0
  108. package/dist/chunk-PUH4CXGZ.js +284 -0
  109. package/dist/chunk-PUH4CXGZ.js.map +1 -0
  110. package/dist/chunk-QLXXPGTV.js +18 -0
  111. package/dist/chunk-QLXXPGTV.js.map +1 -0
  112. package/dist/chunk-R5GIGPW2.js +2845 -0
  113. package/dist/chunk-R5GIGPW2.js.map +1 -0
  114. package/dist/chunk-RH4IUSTO.js +83 -0
  115. package/dist/chunk-RH4IUSTO.js.map +1 -0
  116. package/dist/chunk-S2GVXKKY.js +153 -0
  117. package/dist/chunk-S2GVXKKY.js.map +1 -0
  118. package/dist/chunk-TVHGKFJS.js +58 -0
  119. package/dist/chunk-TVHGKFJS.js.map +1 -0
  120. package/dist/chunk-U7GALWLG.js +66 -0
  121. package/dist/chunk-U7GALWLG.js.map +1 -0
  122. package/dist/chunk-VTC263LT.js +256 -0
  123. package/dist/chunk-VTC263LT.js.map +1 -0
  124. package/dist/chunk-WEOXFEVE.js +739 -0
  125. package/dist/chunk-WEOXFEVE.js.map +1 -0
  126. package/dist/chunk-WYHP46LT.js +28 -0
  127. package/dist/chunk-WYHP46LT.js.map +1 -0
  128. package/dist/chunk-XGLO5LGG.js +274 -0
  129. package/dist/chunk-XGLO5LGG.js.map +1 -0
  130. package/dist/chunk-XRBCRKA2.js +29 -0
  131. package/dist/chunk-XRBCRKA2.js.map +1 -0
  132. package/dist/chunk-XW7ADFFV.js +340 -0
  133. package/dist/chunk-XW7ADFFV.js.map +1 -0
  134. package/dist/chunk-YIQ42IUX.js +34 -0
  135. package/dist/chunk-YIQ42IUX.js.map +1 -0
  136. package/dist/chunk-YJZTUY7B.js +101 -0
  137. package/dist/chunk-YJZTUY7B.js.map +1 -0
  138. package/dist/chunk-Z3CXME3K.js +107 -0
  139. package/dist/chunk-Z3CXME3K.js.map +1 -0
  140. package/dist/classifier/index.js +2 -527
  141. package/dist/classifier/index.js.map +1 -1
  142. package/dist/client-JBPCXU7W.js +4 -0
  143. package/dist/client-JBPCXU7W.js.map +1 -0
  144. package/dist/compose.js +3 -367
  145. package/dist/compose.js.map +1 -1
  146. package/dist/cost-tracker-HHGQ4FTW.js +67 -0
  147. package/dist/cost-tracker-HHGQ4FTW.js.map +1 -0
  148. package/dist/{create-ui-generator-BrZHF0cf.d.ts → create-ui-generator-BY-WeaPi.d.ts} +3 -3
  149. package/dist/{design-mode-DGWN_Gkm.d.ts → design-mode-XyOQmzE0.d.ts} +4 -3
  150. package/dist/{dispatch-B07TI30d.d.ts → dispatch-8LzTddKw.d.ts} +3 -2
  151. package/dist/dist-UALN6ZVB.js +543 -0
  152. package/dist/dist-UALN6ZVB.js.map +1 -0
  153. package/dist/dupe-break-X6K7QFIS.js +4 -0
  154. package/dist/dupe-break-X6K7QFIS.js.map +1 -0
  155. package/dist/evaluation/axis-checks/index.d.ts +5 -4
  156. package/dist/evaluation/axis-checks/index.js +6 -1914
  157. package/dist/evaluation/axis-checks/index.js.map +1 -1
  158. package/dist/evaluation/axis-checks/registry.d.ts +3 -2
  159. package/dist/evaluation/axis-checks/registry.js +3 -1844
  160. package/dist/evaluation/axis-checks/registry.js.map +1 -1
  161. package/dist/evaluation/index.d.ts +49 -132
  162. package/dist/evaluation/index.js +302 -6466
  163. package/dist/evaluation/index.js.map +1 -1
  164. package/dist/evaluation/types.d.ts +3 -2
  165. package/dist/evaluation/types.js +2 -5
  166. package/dist/evaluation/types.js.map +1 -1
  167. package/dist/fragments/index.js +2 -338
  168. package/dist/fragments/index.js.map +1 -1
  169. package/dist/{generate-tSO_shQ0.d.ts → generate-DQ4ci90s.d.ts} +1 -1
  170. package/dist/harness/check/runtime-render/index.d.ts +35 -15
  171. package/dist/harness/check/runtime-render/index.js +6 -2390
  172. package/dist/harness/check/runtime-render/index.js.map +1 -1
  173. package/dist/harness/check/runtime-render/render-check-worker.js +4 -1739
  174. package/dist/harness/check/runtime-render/render-check-worker.js.map +1 -1
  175. package/dist/harness/index.d.ts +3 -2
  176. package/dist/harness/index.js +23 -5126
  177. package/dist/harness/index.js.map +1 -1
  178. package/dist/harness/llm-trace-sink.js +2 -24
  179. package/dist/harness/llm-trace-sink.js.map +1 -1
  180. package/dist/harness/prompts.d.ts +2 -2
  181. package/dist/harness/prompts.js +8 -324
  182. package/dist/harness/prompts.js.map +1 -1
  183. package/dist/harness/result-types.d.ts +6 -5
  184. package/dist/harness/result-types.js +2 -0
  185. package/dist/harness/result-types.js.map +1 -1
  186. package/dist/harness/runtime.d.ts +22 -5
  187. package/dist/harness/runtime.js +10 -2313
  188. package/dist/harness/runtime.js.map +1 -1
  189. package/dist/harness/types-public.d.ts +3 -2
  190. package/dist/harness/types-public.js +16 -2954
  191. package/dist/harness/types-public.js.map +1 -1
  192. package/dist/harness/validator-trace-sink.d.ts +3 -2
  193. package/dist/harness/validator-trace-sink.js +2 -26
  194. package/dist/harness/validator-trace-sink.js.map +1 -1
  195. package/dist/hash.js +2 -39
  196. package/dist/hash.js.map +1 -1
  197. package/dist/hashline-5OKREIRM.js +4 -0
  198. package/dist/hashline-5OKREIRM.js.map +1 -0
  199. package/dist/index.d.ts +50 -11
  200. package/dist/index.js +57 -22015
  201. package/dist/index.js.map +1 -1
  202. package/dist/llm-evaluator-PN3WFCS4.js +653 -0
  203. package/dist/llm-evaluator-PN3WFCS4.js.map +1 -0
  204. package/dist/magic-string.es-2NZTC4GZ.js +1307 -0
  205. package/dist/magic-string.es-2NZTC4GZ.js.map +1 -0
  206. package/dist/patch.d.ts +3 -2
  207. package/dist/patch.js +2 -56
  208. package/dist/patch.js.map +1 -1
  209. package/dist/policy-VCYKGSAC.js +5 -0
  210. package/dist/policy-VCYKGSAC.js.map +1 -0
  211. package/dist/policy.js +2 -35
  212. package/dist/policy.js.map +1 -1
  213. package/dist/primitive-index-2OF6F4TZ.js +131 -0
  214. package/dist/primitive-index-2OF6F4TZ.js.map +1 -0
  215. package/dist/primitive-slice-WTMBLYFJ.js +235 -0
  216. package/dist/primitive-slice-WTMBLYFJ.js.map +1 -0
  217. package/dist/{prompts-BhNUuRoZ.d.ts → prompts-CLZGywMP.d.ts} +1 -1
  218. package/dist/provider-adapter-contract.js +10407 -14204
  219. package/dist/provider-adapter-contract.js.map +1 -1
  220. package/dist/provider-adapter.js +2 -44
  221. package/dist/provider-adapter.js.map +1 -1
  222. package/dist/providers/index.d.ts +89 -1
  223. package/dist/providers/index.js +115 -44
  224. package/dist/providers/index.js.map +1 -1
  225. package/dist/raw-C6KBH5I3.js +141 -0
  226. package/dist/raw-C6KBH5I3.js.map +1 -0
  227. package/dist/raw-FXSBXQTZ.js +7 -0
  228. package/dist/raw-FXSBXQTZ.js.map +1 -0
  229. package/dist/raw-MURDUPGK.js +8 -0
  230. package/dist/raw-MURDUPGK.js.map +1 -0
  231. package/dist/raw-T3GW3UVX.js +8 -0
  232. package/dist/raw-T3GW3UVX.js.map +1 -0
  233. package/dist/render-check-host-ZNIS6BJ5.js +5 -0
  234. package/dist/render-check-host-ZNIS6BJ5.js.map +1 -0
  235. package/dist/sdk-4JNSDNSR.js +6 -0
  236. package/dist/sdk-4JNSDNSR.js.map +1 -0
  237. package/dist/sdk-DIVJMHCX.js +7 -0
  238. package/dist/sdk-DIVJMHCX.js.map +1 -0
  239. package/dist/sdk-QRJOCRTS.js +7 -0
  240. package/dist/sdk-QRJOCRTS.js.map +1 -0
  241. package/dist/tag-balance-CTHCTRJ7.js +123 -0
  242. package/dist/tag-balance-CTHCTRJ7.js.map +1 -0
  243. package/dist/tools/get-primitives-ts.d.ts +1 -1
  244. package/dist/tools/get-primitives-ts.js +3 -1
  245. package/dist/tools/get-primitives-ts.js.map +1 -1
  246. package/dist/tools/render-check-worker.js +3 -40
  247. package/dist/tools/render-check-worker.js.map +1 -1
  248. package/dist/{types-public-h8IMnYuh.d.ts → types-public-hYlrerZh.d.ts} +35 -2
  249. package/dist/validation/index.d.ts +2 -2
  250. package/dist/validation/index.js +3 -498
  251. package/dist/validation/index.js.map +1 -1
  252. package/dist/{visual-evaluator-C2strxqD.d.ts → visual-evaluator-KskajxD5.d.ts} +14 -4
  253. package/dist/visual-evaluator-TGEXYQTP.js +12 -0
  254. package/dist/visual-evaluator-TGEXYQTP.js.map +1 -0
  255. package/dist/workflows.d.ts +3 -2
  256. package/dist/workflows.js +2 -151
  257. package/dist/workflows.js.map +1 -1
  258. package/package.json +20 -16
  259. package/src/adapters/claude/sdk.ts +4 -2
  260. package/src/adapters/claude/tool-bridge.ts +2 -1
  261. package/src/adapters/generation-dispatch.ts +9 -0
  262. package/src/adapters/openai/raw.ts +4 -0
  263. package/src/adapters/openai/sdk.ts +6 -1
  264. package/src/adapters/openai/tokens.ts +21 -7
  265. package/src/adapters/tool-bridge.ts +1 -1
  266. package/src/adapters/tools.ts +1 -1
  267. package/src/advanced/feedback.ts +1 -2
  268. package/src/advanced/generator.ts +4 -3
  269. package/src/boilerplate/free-design-prompt.ts +7 -0
  270. package/src/boilerplate/generate.ts +18 -5
  271. package/src/boilerplate/hard-sections.ts +8 -3
  272. package/src/boilerplate/react-hooks.ts +56 -0
  273. package/src/boilerplate/system-prompt.ts +12 -3
  274. package/src/check/run-tier0.ts +233 -8
  275. package/src/check/type-checker.ts +128 -16
  276. package/src/coding-agent/tools.ts +26 -3
  277. package/src/design-mode.ts +4 -9
  278. package/src/evaluation/axis-checks/checks/state-merge.ts +2 -4
  279. package/src/evaluation/axis-checks/checks/state-payload.ts +1 -28
  280. package/src/evaluation/axis-checks/checks/tint.ts +197 -0
  281. package/src/evaluation/axis-checks/helpers.ts +121 -0
  282. package/src/evaluation/axis-checks/registry.ts +2 -0
  283. package/src/evaluation/criteria/__fixtures__/edge-v2-draw2.json +4629 -0
  284. package/src/evaluation/criteria/bank.ts +1 -1
  285. package/src/evaluation/criteria/edge-instruments.ts +14 -0
  286. package/src/evaluation/criteria/edge-probe-v2.ts +352 -0
  287. package/src/evaluation/criteria/edge-v2.ts +242 -0
  288. package/src/evaluation/criteria/resolve.ts +24 -5
  289. package/src/evaluation/index.ts +3 -5
  290. package/src/evaluation/mcp-server.ts +0 -50
  291. package/src/evaluation/types-public.ts +6 -1
  292. package/src/evaluation/visual-evaluator.ts +161 -30
  293. package/src/harness/check/runtime-render/index.ts +28 -4
  294. package/src/harness/check/runtime-render/render-check.ts +20 -21
  295. package/src/harness/claude-agent-sdk-loader.ts +93 -0
  296. package/src/harness/claude-code-login-query.ts +1 -1
  297. package/src/harness/coding/generate-task-runner.ts +1 -0
  298. package/src/harness/coding/run-coding-turn.ts +3 -0
  299. package/src/harness/coding/run-eval-round.ts +9 -0
  300. package/src/harness/llm-router.ts +50 -2
  301. package/src/harness/runtime.ts +17 -0
  302. package/src/index.ts +9 -0
  303. package/src/providers/anthropic-tool-call.ts +263 -0
  304. package/src/providers/index.ts +13 -0
  305. package/src/tools/get-primitives-ts.ts +1 -1
  306. package/src/tools/render-check.ts +4 -4
  307. package/src/validation/primitives.ts +2 -1
  308. package/src/evaluation/loop.ts +0 -218
@@ -0,0 +1,1933 @@
1
+ import { createAnthropicClient } from './chunk-QLXXPGTV.js';
2
+ import { CLAUDE_CODE_LOGIN_CREDENTIAL, PROVIDER_KEY_ENV_NAMES, loadClaudeAgentSdk, MCP_SERVER_NAME, claudeCodeLoginQueryOptions, loginChildEnv } from './chunk-JDH5EGNZ.js';
3
+ import { splitCacheInclusiveUsage, splitOpenAiUsage } from './chunk-OVLH5ICV.js';
4
+ import { toolArgsToJsonObject } from './chunk-7QE3AHLE.js';
5
+ import { newLlmTraceId, emitLlmTraceEvent, summarizeTools } from './chunk-5AGTY555.js';
6
+ import { anthropicRejectsForcedToolChoice, normalizeAnthropicModelId, isRecord, anthropicRejectsSamplingParams } from '@ggui-ai/protocol';
7
+ import * as z from 'zod';
8
+
9
+ // src/adapters/provider-router.ts
10
+ function getBedrockModelId(model) {
11
+ const BEDROCK_MAP = {
12
+ "anthropic/claude-haiku-4-5": "us.anthropic.claude-haiku-4-5-20251001-v1:0",
13
+ "anthropic/claude-haiku-5-5": "us.anthropic.claude-haiku-5-5",
14
+ // listed ACTIVE in us-east-1 (ggui#1743)
15
+ "anthropic/claude-sonnet-4-6": "us.anthropic.claude-sonnet-4-6",
16
+ "anthropic/claude-opus-4-6": "us.anthropic.claude-opus-4-6-v1:0",
17
+ // Claude 5-family (ggui#706, strings verified 2026-09-02 against
18
+ // platform.claude.com; Bedrock base form `anthropic.<id>`, upcast to
19
+ // the cross-region profile). Explicit so the ids are pinned by test,
20
+ // not merely reachable through the generic fallback below.
21
+ "anthropic/claude-fable-5-1": "us.anthropic.claude-fable-5-1",
22
+ "anthropic/claude-opus-5": "us.anthropic.claude-opus-5",
23
+ "anthropic/claude-sonnet-5": "us.anthropic.claude-sonnet-5",
24
+ // Legacy (still available; registry state `legacy`, not deprecated).
25
+ "anthropic/claude-fable-5": "us.anthropic.claude-fable-5"
26
+ };
27
+ const normalized = model.replace(/^anthropic\./, "anthropic/");
28
+ if (BEDROCK_MAP[normalized]) return BEDROCK_MAP[normalized];
29
+ if (normalized.startsWith("us.anthropic.") || normalized.startsWith("arn:")) {
30
+ return normalized;
31
+ }
32
+ const stripped = normalized.replace(/^anthropic\//, "");
33
+ return `us.anthropic.${stripped}`;
34
+ }
35
+ var SIBLING_ENV_KEYS = [
36
+ "ANTHROPIC_API_KEY",
37
+ "ANTHROPIC_BASE_URL",
38
+ "CLAUDE_CODE_USE_BEDROCK",
39
+ "OPENAI_API_KEY",
40
+ "GEMINI_API_KEY",
41
+ "GOOGLE_API_KEY",
42
+ "OPENROUTER_API_KEY"
43
+ ];
44
+ function clearSiblings(keep) {
45
+ const out = {};
46
+ for (const k of SIBLING_ENV_KEYS) {
47
+ if (!keep.has(k)) out[k] = void 0;
48
+ }
49
+ return out;
50
+ }
51
+ function resolveRoute(input) {
52
+ const { route, apiKey, env } = input;
53
+ if (apiKey === CLAUDE_CODE_LOGIN_CREDENTIAL && route.provider !== "anthropic") {
54
+ throw new Error(
55
+ `${route.provider}:${route.model} cannot use the claude-code-login credential \u2014 the machine's Claude Code login authenticates the Claude Code binary only; pick an anthropic route or supply a ${route.provider} API key.`
56
+ );
57
+ }
58
+ switch (route.provider) {
59
+ case "openai": {
60
+ if (!apiKey) {
61
+ throw new Error(
62
+ `openai:${route.model} requires an API key. Set 'apiKey' on the RoutingInput (BYOK) or supply your key via your dispatch layer.`
63
+ );
64
+ }
65
+ return {
66
+ model: route.model,
67
+ env: {
68
+ OPENAI_API_KEY: apiKey,
69
+ ...clearSiblings(/* @__PURE__ */ new Set(["OPENAI_API_KEY"]))
70
+ }
71
+ };
72
+ }
73
+ case "google": {
74
+ if (!apiKey) {
75
+ throw new Error(
76
+ `google:${route.model} requires an API key. Set 'apiKey' on the RoutingInput (BYOK) or supply your key via your dispatch layer.`
77
+ );
78
+ }
79
+ return {
80
+ model: route.model,
81
+ env: {
82
+ // `harness/llm-router.ts`'s GoogleAgent reads `GEMINI_API_KEY
83
+ // || GOOGLE_API_KEY`. Set both so either slot wins;
84
+ // unsetting one would leave a stale value behind.
85
+ GEMINI_API_KEY: apiKey,
86
+ GOOGLE_API_KEY: apiKey,
87
+ ...clearSiblings(/* @__PURE__ */ new Set(["GEMINI_API_KEY", "GOOGLE_API_KEY"]))
88
+ }
89
+ };
90
+ }
91
+ case "openrouter": {
92
+ if (!apiKey) {
93
+ throw new Error(
94
+ `openrouter:${route.model} requires an API key. Set 'apiKey' on the RoutingInput (BYOK) or supply your key via your dispatch layer.`
95
+ );
96
+ }
97
+ return {
98
+ model: route.model,
99
+ env: {
100
+ OPENROUTER_API_KEY: apiKey,
101
+ ...clearSiblings(/* @__PURE__ */ new Set(["OPENROUTER_API_KEY"]))
102
+ }
103
+ };
104
+ }
105
+ case "bedrock": {
106
+ return {
107
+ model: route.model,
108
+ env: {
109
+ CLAUDE_CODE_USE_BEDROCK: "1",
110
+ ...clearSiblings(/* @__PURE__ */ new Set(["CLAUDE_CODE_USE_BEDROCK"]))
111
+ }
112
+ };
113
+ }
114
+ case "anthropic": {
115
+ if (apiKey === CLAUDE_CODE_LOGIN_CREDENTIAL) {
116
+ const cleared = {};
117
+ for (const k of PROVIDER_KEY_ENV_NAMES) cleared[k] = void 0;
118
+ return {
119
+ model: route.model,
120
+ env: { ...cleared, ...clearSiblings(/* @__PURE__ */ new Set()) },
121
+ auth: "claude-code-login"
122
+ };
123
+ }
124
+ if (apiKey) {
125
+ return {
126
+ model: route.model,
127
+ env: {
128
+ ANTHROPIC_API_KEY: apiKey,
129
+ ...clearSiblings(/* @__PURE__ */ new Set(["ANTHROPIC_API_KEY"]))
130
+ }
131
+ };
132
+ }
133
+ const explicitBedrock = env.CLAUDE_CODE_USE_BEDROCK === "1";
134
+ const noApiKey = !env.ANTHROPIC_API_KEY;
135
+ if (explicitBedrock || noApiKey) {
136
+ return {
137
+ model: getBedrockModelId(route.model),
138
+ env: {
139
+ CLAUDE_CODE_USE_BEDROCK: "1",
140
+ ...clearSiblings(/* @__PURE__ */ new Set(["CLAUDE_CODE_USE_BEDROCK"]))
141
+ }
142
+ };
143
+ }
144
+ return {
145
+ model: route.model,
146
+ env: clearSiblings(/* @__PURE__ */ new Set(["ANTHROPIC_API_KEY"]))
147
+ };
148
+ }
149
+ }
150
+ }
151
+ function applyRouteToEnv(baseEnv, route) {
152
+ const result = { ...baseEnv };
153
+ for (const [key, value] of Object.entries(route.env)) {
154
+ if (value === void 0) {
155
+ delete result[key];
156
+ } else {
157
+ result[key] = value;
158
+ }
159
+ }
160
+ return result;
161
+ }
162
+ var MCP_TOOL_PREFIX = `mcp__${MCP_SERVER_NAME}__`;
163
+ function stripMcpToolPrefix(name) {
164
+ return name.startsWith(MCP_TOOL_PREFIX) ? name.slice(MCP_TOOL_PREFIX.length) : name;
165
+ }
166
+ var LOGIN_API_KEY_SOURCE = "none";
167
+ var ClaudeCodeLoginAuthError = class extends Error {
168
+ apiKeySource;
169
+ constructor(apiKeySource) {
170
+ super(
171
+ `claude-code-login: the Claude Code binary authenticated from apiKeySource=${apiKeySource}, not from its login \u2014 a provider key reached the spawned process; the run was stopped before it spent a turn`
172
+ );
173
+ this.name = "ClaudeCodeLoginAuthError";
174
+ this.apiKeySource = apiKeySource;
175
+ }
176
+ };
177
+ var ClaudeCodeLoginResultError = class extends Error {
178
+ subtype;
179
+ errors;
180
+ status;
181
+ constructor(subtype, errors, status) {
182
+ super(
183
+ `claude-code-login: query() ended with ${subtype}` + (status !== void 0 ? ` (api status ${status})` : "") + `: ${errors.length > 0 ? errors.join("; ") : "no detail from the binary"}`
184
+ );
185
+ this.name = "ClaudeCodeLoginResultError";
186
+ this.subtype = subtype;
187
+ this.errors = errors;
188
+ this.status = status;
189
+ }
190
+ };
191
+ function createDenyingIntercept(intercepted) {
192
+ return async (toolName, input) => {
193
+ intercepted.push({ name: toolName, input });
194
+ return {
195
+ behavior: "deny",
196
+ message: "ggui executes tools in its harness; the binary only proposes them."
197
+ };
198
+ };
199
+ }
200
+ async function* imageUserMessage(text, image) {
201
+ const message = {
202
+ role: "user",
203
+ content: [
204
+ {
205
+ type: "image",
206
+ source: { type: "base64", media_type: image.mediaType, data: image.base64 }
207
+ },
208
+ { type: "text", text }
209
+ ]
210
+ };
211
+ yield { type: "user", message, parent_tool_use_id: null };
212
+ }
213
+ function buildLoginQueryOptions(params) {
214
+ return {
215
+ model: params.model,
216
+ systemPrompt: params.systemPrompt,
217
+ maxTurns: 1,
218
+ thinking: { type: "disabled" },
219
+ env: loginChildEnv(params.env ?? process.env),
220
+ ...claudeCodeLoginQueryOptions(),
221
+ ...params.tools !== void 0 ? {
222
+ mcpServers: { [MCP_SERVER_NAME]: params.tools.server },
223
+ canUseTool: params.tools.canUseTool
224
+ } : {}
225
+ };
226
+ }
227
+ var JSON_SCHEMA_TYPES = [
228
+ "object",
229
+ "array",
230
+ "string",
231
+ "number",
232
+ "boolean",
233
+ "null",
234
+ "integer"
235
+ ];
236
+ function isJsonSchemaType(value) {
237
+ return typeof value === "string" && JSON_SCHEMA_TYPES.includes(value);
238
+ }
239
+ function isJsonObject(value) {
240
+ return typeof value === "object" && value !== null && !Array.isArray(value);
241
+ }
242
+ function schemaAt(value, path) {
243
+ if (typeof value === "boolean") return value;
244
+ if (!isJsonObject(value)) {
245
+ throw new Error(
246
+ `claude-code-login: ${path} must be a JSON-Schema object or boolean, got ${JSON.stringify(value)}`
247
+ );
248
+ }
249
+ return toZodJsonSchema(value, path);
250
+ }
251
+ function toZodJsonSchema(parameters, path = "parameters") {
252
+ const out = {};
253
+ for (const [key, value] of Object.entries(parameters)) {
254
+ if (value === void 0) continue;
255
+ switch (key) {
256
+ case "type": {
257
+ if (!isJsonSchemaType(value)) {
258
+ throw new Error(
259
+ `claude-code-login: ${path}.type must be one of ${JSON_SCHEMA_TYPES.join("|")}, got ${JSON.stringify(value)}`
260
+ );
261
+ }
262
+ out.type = value;
263
+ break;
264
+ }
265
+ case "properties":
266
+ case "patternProperties":
267
+ case "$defs": {
268
+ if (!isJsonObject(value))
269
+ throw new Error(`claude-code-login: ${path}.${key} must be an object`);
270
+ const record = {};
271
+ for (const [name, sub] of Object.entries(value)) {
272
+ if (sub === void 0) continue;
273
+ record[name] = schemaAt(sub, `${path}.${key}.${name}`);
274
+ }
275
+ if (key === "$defs") {
276
+ const defs = {};
277
+ for (const [name, sub] of Object.entries(record)) {
278
+ if (typeof sub === "boolean")
279
+ throw new Error(`claude-code-login: ${path}.$defs.${name} must be an object`);
280
+ defs[name] = sub;
281
+ }
282
+ out.$defs = defs;
283
+ } else {
284
+ out[key] = record;
285
+ }
286
+ break;
287
+ }
288
+ case "items": {
289
+ out.items = Array.isArray(value) ? value.map((sub, i) => schemaAt(sub, `${path}.items[${i}]`)) : schemaAt(value, `${path}.items`);
290
+ break;
291
+ }
292
+ case "additionalProperties":
293
+ case "not": {
294
+ out[key] = schemaAt(value, `${path}.${key}`);
295
+ break;
296
+ }
297
+ case "anyOf":
298
+ case "oneOf":
299
+ case "allOf": {
300
+ if (!Array.isArray(value))
301
+ throw new Error(`claude-code-login: ${path}.${key} must be an array`);
302
+ out[key] = value.map((sub, i) => {
303
+ const s = schemaAt(sub, `${path}.${key}[${i}]`);
304
+ if (typeof s === "boolean")
305
+ throw new Error(`claude-code-login: ${path}.${key}[${i}] must be an object`);
306
+ return s;
307
+ });
308
+ break;
309
+ }
310
+ case "required": {
311
+ if (!Array.isArray(value) || !value.every((v) => typeof v === "string")) {
312
+ throw new Error(`claude-code-login: ${path}.required must be an array of strings`);
313
+ }
314
+ out.required = value;
315
+ break;
316
+ }
317
+ case "enum": {
318
+ if (!Array.isArray(value) || !value.every(
319
+ (v) => v === null || typeof v === "string" || typeof v === "number" || typeof v === "boolean"
320
+ )) {
321
+ throw new Error(`claude-code-login: ${path}.enum must be an array of primitives`);
322
+ }
323
+ out.enum = value;
324
+ break;
325
+ }
326
+ default:
327
+ out[key] = value;
328
+ }
329
+ }
330
+ return out;
331
+ }
332
+ function createLoginToolServer(sdk, tools) {
333
+ const sdkTools = tools.map((def) => {
334
+ const schema = z.fromJSONSchema(toZodJsonSchema(def.parameters, `${def.name}.parameters`));
335
+ if (!(schema instanceof z.ZodObject)) {
336
+ throw new Error(
337
+ `claude-code-login: tool ${def.name}'s parameters must be a JSON-Schema object`
338
+ );
339
+ }
340
+ return sdk.tool(def.name, def.description, schema.shape, async () => {
341
+ throw new Error(
342
+ `claude-code-login: the binary tried to execute ${def.name} \u2014 ggui's tools are proposed to the harness and never run in the binary (the intercept denies every call)`
343
+ );
344
+ });
345
+ });
346
+ return sdk.createSdkMcpServer({ name: MCP_SERVER_NAME, version: "1.0.0", tools: sdkTools });
347
+ }
348
+ var LOGIN_REQUIRED_TOOL_INSTRUCTION = "Respond only with tool calls: do not write any text before, between or after them. Text outside a tool call is not read.";
349
+ function describeCalls(calls) {
350
+ return JSON.stringify(calls.map((c) => ({ name: stripMcpToolPrefix(c.name), input: c.input })));
351
+ }
352
+ async function collectLoginTurn(stream, opts) {
353
+ let init;
354
+ const toolCalls = [];
355
+ let text = "";
356
+ let thinkingBlocks = 0;
357
+ let result;
358
+ try {
359
+ for await (const msg of stream) {
360
+ if (msg.type === "system" && msg.subtype === "init") {
361
+ const measured = msg;
362
+ init = measured;
363
+ opts.log(
364
+ `[claude-code-login] apiKeySource=${measured.apiKeySource} claude_code_version=${measured.claude_code_version} model=${measured.model}`
365
+ );
366
+ if (measured.apiKeySource !== LOGIN_API_KEY_SOURCE) {
367
+ throw new ClaudeCodeLoginAuthError(measured.apiKeySource);
368
+ }
369
+ } else if (msg.type === "assistant") {
370
+ for (const block of msg.message.content) {
371
+ if (block.type === "tool_use") {
372
+ toolCalls.push({
373
+ id: block.id,
374
+ name: stripMcpToolPrefix(block.name),
375
+ input: isRecord(block.input) ? toolArgsToJsonObject(block.input) : {}
376
+ });
377
+ } else if (block.type === "text") {
378
+ text += block.text;
379
+ } else if (block.type === "thinking" || block.type === "redacted_thinking") {
380
+ thinkingBlocks += 1;
381
+ }
382
+ }
383
+ } else if (msg.type === "result") {
384
+ result = msg;
385
+ }
386
+ }
387
+ } catch (e) {
388
+ if (result === void 0) throw e;
389
+ opts.log(
390
+ `[claude-code-login] query() threw after its result message \u2014 the result decides (${e instanceof Error ? e.message : String(e)})`
391
+ );
392
+ }
393
+ if (result === void 0) {
394
+ throw new Error("claude-code-login: query() ended without a result message");
395
+ }
396
+ const usage = result.usage;
397
+ const thinkingTokens = usage.output_tokens_details?.thinking_tokens ?? 0;
398
+ opts.log(
399
+ `[claude-code-login] usage kind=${opts.kind} input=${usage.input_tokens} output=${usage.output_tokens} cacheRead=${usage.cache_read_input_tokens} cacheCreated=${usage.cache_creation_input_tokens} thinking=${thinkingTokens} thinkingBlocks=${thinkingBlocks} textChars=${text.length} toolCalls=${toolCalls.length} stop=${result.stop_reason ?? "none"} choice=${opts.choice}`
400
+ );
401
+ const streamSaw = describeCalls(toolCalls);
402
+ const interceptSaw = describeCalls(opts.intercepted);
403
+ if (streamSaw !== interceptSaw) {
404
+ opts.warn(
405
+ `[claude-code-login] intercept/stream disagreement \u2014 the stream's blocks win. intercepted=${interceptSaw} stream=${streamSaw}`
406
+ );
407
+ }
408
+ if (result.subtype === "success") {
409
+ if (result.is_error) {
410
+ throw new ClaudeCodeLoginResultError(
411
+ "success",
412
+ [result.result],
413
+ result.api_error_status ?? void 0
414
+ );
415
+ }
416
+ } else if (result.subtype !== "error_max_turns") {
417
+ const failed = result;
418
+ throw new ClaudeCodeLoginResultError(failed.subtype, failed.errors, void 0);
419
+ }
420
+ return { init, toolCalls, text, result };
421
+ }
422
+
423
+ // src/harness/llm-router.ts
424
+ function servedModelOf(name) {
425
+ return typeof name === "string" && name.length > 0 ? { servedModel: name } : {};
426
+ }
427
+ function anthropicFinishReason(stopReason) {
428
+ if (stopReason === null || stopReason === void 0) return void 0;
429
+ if (stopReason === "end_turn" || stopReason === "stop_sequence") return "stop";
430
+ if (stopReason === "max_tokens") return "length";
431
+ if (stopReason === "refusal") return "content-filter";
432
+ return "other";
433
+ }
434
+ function googleFinishReason(finishReason) {
435
+ if (finishReason === null || finishReason === void 0) return void 0;
436
+ if (finishReason === "STOP") return "stop";
437
+ if (finishReason === "MAX_TOKENS") return "length";
438
+ if (["SAFETY", "RECITATION", "BLOCKLIST", "PROHIBITED_CONTENT", "SPII", "IMAGE_SAFETY"].includes(finishReason)) return "content-filter";
439
+ return "other";
440
+ }
441
+ function visionFinish(finishReason) {
442
+ return finishReason !== void 0 ? { finishReason } : {};
443
+ }
444
+ function addStrictSchemaConstraints(schema) {
445
+ const result = { ...schema };
446
+ if (result.type === "object" && result.properties) {
447
+ result.additionalProperties = false;
448
+ const propKeys = Object.keys(result.properties);
449
+ if (!result.required || result.required.length < propKeys.length) {
450
+ result.required = propKeys;
451
+ }
452
+ const props = { ...result.properties };
453
+ for (const [key, value] of Object.entries(props)) {
454
+ if (typeof value === "object" && value !== null) {
455
+ props[key] = addStrictSchemaConstraints(value);
456
+ }
457
+ }
458
+ result.properties = props;
459
+ }
460
+ if (result.type === "array" && result.items && typeof result.items === "object") {
461
+ result.items = addStrictSchemaConstraints(result.items);
462
+ }
463
+ return result;
464
+ }
465
+ function errorSummary(e) {
466
+ if (!(e instanceof Error)) return String(e);
467
+ const status = "status" in e ? ` (${e.status})` : "";
468
+ const code = "code" in e ? ` [${e.code}]` : "";
469
+ let body = "";
470
+ if ("error" in e && typeof e.error === "object") {
471
+ const errObj = e.error;
472
+ if (errObj?.message) body = ` body="${errObj.message.slice(0, 200)}"`;
473
+ }
474
+ return `${e.constructor.name}${status}${code}: ${e.message.slice(0, 120)}${body}`;
475
+ }
476
+ function isRateLimited(e) {
477
+ if (!(e instanceof Error) || !("status" in e)) return false;
478
+ return e.status === 429;
479
+ }
480
+ function extractStatus(e) {
481
+ if (!(e instanceof Error) || !("status" in e)) return void 0;
482
+ const status = e.status;
483
+ return typeof status === "number" ? status : void 0;
484
+ }
485
+ function extractRetryAfterSec(e) {
486
+ if (!(e instanceof Error) || !("headers" in e)) return void 0;
487
+ const headers = e.headers;
488
+ if (headers === null || typeof headers !== "object" || !("get" in headers) || typeof headers.get !== "function") {
489
+ return void 0;
490
+ }
491
+ const raw = headers.get("retry-after");
492
+ if (raw === null) return void 0;
493
+ const parsed = Number(raw);
494
+ return Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : void 0;
495
+ }
496
+ function sleep(ms) {
497
+ return new Promise((resolve) => setTimeout(resolve, ms));
498
+ }
499
+ var MAX_RETRY_ATTEMPTS = 2;
500
+ var RETRY_AFTER_CAP_SEC = 15;
501
+ var DEFAULT_BACKOFF_BASE_MS = 1e3;
502
+ var DEFAULT_BACKOFF_MAX_MS = 8e3;
503
+ var MAX_TOTAL_RETRY_DELAY_MS = 2e4;
504
+ var LLMAgent = class {
505
+ client = null;
506
+ // ── Session state for server-side chaining ──────────────
507
+ // Google: previous_interaction_id, OpenAI: previous_response_id
508
+ // Enables turn 2+ to skip re-sending system prompt + history.
509
+ // Call resetSession() between independent generation runs.
510
+ lastSessionId;
511
+ /**
512
+ * Explicit per-call routing override — see `AgentConfig.routeOverride`.
513
+ * Absent when constructed via the bare `new XAgent()` form (every
514
+ * existing call site), so `createClient()`/`resolveModel()` fall
515
+ * back to `process.env` exactly as before.
516
+ */
517
+ routeOverride;
518
+ /** See `AgentConfig.onRetry` (#489). Absent is a no-op. */
519
+ onRetry;
520
+ constructor(routeOverride, onRetry) {
521
+ this.routeOverride = routeOverride;
522
+ this.onRetry = onRetry;
523
+ }
524
+ async getClient() {
525
+ if (!this.client) {
526
+ this.client = await this.createClient();
527
+ }
528
+ return this.client;
529
+ }
530
+ /**
531
+ * Pre-warm cache for repeated callTools() calls with the same system prompt + tools.
532
+ * Override in providers that support server-side context caching (e.g., Google).
533
+ * No-op by default (Anthropic/OpenAI handle caching automatically per-request).
534
+ */
535
+ async warmCache(_model, _systemPrompt, _tools, _toolChoice) {
536
+ }
537
+ /** Cleanup any cached resources. Call after generation completes. No-op by default. */
538
+ async cleanup() {
539
+ }
540
+ /** Reset session state between independent generation runs. */
541
+ resetSession() {
542
+ this.lastSessionId = void 0;
543
+ }
544
+ /**
545
+ * Send tool execution results back to the provider to close the API contract.
546
+ * Call this after executing tools from callTools() and before the next callTools().
547
+ *
548
+ * For providers with server-side state (Google, OpenAI), this sends the
549
+ * function results so the next callTools() can chain properly.
550
+ * For stateless providers (Anthropic), this is a no-op.
551
+ *
552
+ * Override in providers that need it.
553
+ */
554
+ async sendToolResult(_results) {
555
+ }
556
+ /**
557
+ * Execute an API call, retrying once or twice on HTTP 429
558
+ * (rate-limited) before giving up (#489).
559
+ *
560
+ * Policy: on a 429, honor the provider's `Retry-After` header when
561
+ * present (capped at `RETRY_AFTER_CAP_SEC` — a provider asking for a
562
+ * longer wait is treated as "don't retry", not "wait longer": making
563
+ * the caller sit through 15+ more seconds on a single failed call is
564
+ * a bad experience regardless of what's driving the request);
565
+ * otherwise fall back to exponential backoff with jitter, capped at
566
+ * `DEFAULT_BACKOFF_MAX_MS` per attempt. Stops after
567
+ * `MAX_RETRY_ATTEMPTS` retries OR once the cumulative delay would
568
+ * exceed `MAX_TOTAL_RETRY_DELAY_MS`, whichever comes first. Any
569
+ * non-429 error, or a 429 with no attempts left, is logged and
570
+ * re-thrown immediately — unchanged from the pre-#489 behavior.
571
+ *
572
+ * **Budget scope (final-review correction):** `MAX_TOTAL_RETRY_DELAY_MS`
573
+ * bounds a single `apiCall()` invocation, not a whole generation. A
574
+ * generation issues many sequential `apiCall()` calls (coding turns,
575
+ * tool-result round-trips, eval rounds); under sustained throttling
576
+ * each one can independently add up to `MAX_TOTAL_RETRY_DELAY_MS`, so
577
+ * a generation with k rate-limited calls can add up to roughly `k *
578
+ * MAX_TOTAL_RETRY_DELAY_MS` to how long it holds its caller's
579
+ * resources (e.g. the caller's concurrency slot) — not a flat 20s
580
+ * ceiling. No shared per-generation budget is threaded here by
581
+ * design (accepted ruling, not an oversight): the caller's own
582
+ * queue/admission backstop is what bounds how long other
583
+ * queued work waits, independent of any one generation's retry
584
+ * total.
585
+ */
586
+ async apiCall(fn) {
587
+ const start = Date.now();
588
+ let totalDelayMs = 0;
589
+ for (let attempt = 0; ; attempt++) {
590
+ try {
591
+ return await fn();
592
+ } catch (e) {
593
+ const ms = Date.now() - start;
594
+ if (attempt >= MAX_RETRY_ATTEMPTS || !isRateLimited(e)) {
595
+ console.error(`[${this.provider}] API error after ${ms}ms: ${errorSummary(e)}`);
596
+ throw e;
597
+ }
598
+ const retryAfterSec = extractRetryAfterSec(e);
599
+ if (retryAfterSec !== void 0 && retryAfterSec > RETRY_AFTER_CAP_SEC) {
600
+ console.error(
601
+ `[${this.provider}] API error after ${ms}ms (retry-after ${retryAfterSec}s exceeds ${RETRY_AFTER_CAP_SEC}s cap, not retrying): ${errorSummary(e)}`
602
+ );
603
+ throw e;
604
+ }
605
+ const delayMs = retryAfterSec !== void 0 ? retryAfterSec * 1e3 : Math.min(DEFAULT_BACKOFF_BASE_MS * 2 ** attempt, DEFAULT_BACKOFF_MAX_MS) + Math.floor(Math.random() * 250);
606
+ if (totalDelayMs + delayMs > MAX_TOTAL_RETRY_DELAY_MS) {
607
+ console.error(
608
+ `[${this.provider}] API error after ${ms}ms (retry budget exhausted, not retrying): ${errorSummary(e)}`
609
+ );
610
+ throw e;
611
+ }
612
+ totalDelayMs += delayMs;
613
+ const info = {
614
+ provider: this.provider,
615
+ attempt: attempt + 1,
616
+ maxAttempts: MAX_RETRY_ATTEMPTS,
617
+ status: extractStatus(e),
618
+ retryAfterSec,
619
+ delayMs,
620
+ message: errorSummary(e)
621
+ };
622
+ console.warn(
623
+ `[${this.provider}] rate-limited (attempt ${info.attempt}/${info.maxAttempts}), retrying in ${delayMs}ms`
624
+ );
625
+ try {
626
+ this.onRetry?.(info);
627
+ } catch (observerError) {
628
+ console.warn(
629
+ `[${this.provider}] onRetry observer threw (ignored, retry proceeds): ${errorSummary(observerError)}`
630
+ );
631
+ }
632
+ await sleep(delayMs);
633
+ }
634
+ }
635
+ }
636
+ };
637
+ function resolveAnthropicSampling(model, temperature) {
638
+ if (temperature === void 0) {
639
+ return { request: {}, applied: { temperature: "provider-default" } };
640
+ }
641
+ if (anthropicRejectsSamplingParams(model)) {
642
+ return {
643
+ request: {},
644
+ applied: {
645
+ temperature: "provider-default",
646
+ strippedReason: `${normalizeAnthropicModelId(model)} rejects non-default sampling params (HTTP 400 on Opus 4.7+/5-family); caller's temperature=${temperature} was not sent`
647
+ }
648
+ };
649
+ }
650
+ return { request: { temperature }, applied: { temperature } };
651
+ }
652
+ var AnthropicAgent = class extends LLMAgent {
653
+ provider = "anthropic";
654
+ resolveModel(model) {
655
+ const useBedrock = this.routeOverride?.useBedrock ?? process.env.CLAUDE_CODE_USE_BEDROCK === "1";
656
+ if (useBedrock) {
657
+ return getBedrockModelId(model);
658
+ }
659
+ return model.startsWith("anthropic/") ? model.slice("anthropic/".length) : model;
660
+ }
661
+ async createClient() {
662
+ const useBedrock = this.routeOverride?.useBedrock ?? process.env.CLAUDE_CODE_USE_BEDROCK === "1";
663
+ if (useBedrock) {
664
+ const { AnthropicBedrock } = await import('@anthropic-ai/bedrock-sdk');
665
+ return new AnthropicBedrock({
666
+ awsRegion: process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION ?? "us-east-1"
667
+ });
668
+ }
669
+ return createAnthropicClient(this.routeOverride?.apiKey ?? process.env.ANTHROPIC_API_KEY);
670
+ }
671
+ async callText(model, systemPrompt, userPrompt, maxTokens, temperature) {
672
+ const client = await this.getClient();
673
+ const traceId = newLlmTraceId();
674
+ const startedAt = Date.now();
675
+ const resolvedModel = this.resolveModel(model);
676
+ const system = [
677
+ {
678
+ type: "text",
679
+ text: systemPrompt,
680
+ cache_control: { type: "ephemeral" }
681
+ }
682
+ ];
683
+ const sampling = resolveAnthropicSampling(resolvedModel, temperature);
684
+ if (sampling.applied.strippedReason !== void 0) {
685
+ console.warn(`[anthropic] callText: ${sampling.applied.strippedReason}`);
686
+ }
687
+ try {
688
+ const response = await this.apiCall(
689
+ () => client.messages.create({
690
+ model: resolvedModel,
691
+ max_tokens: maxTokens ?? 4096,
692
+ system,
693
+ messages: [{ role: "user", content: userPrompt }],
694
+ ...sampling.request
695
+ })
696
+ );
697
+ const usage = response.usage;
698
+ const cacheCreated = usage.cache_creation_input_tokens ?? 0;
699
+ const cacheRead = usage.cache_read_input_tokens ?? 0;
700
+ if (cacheCreated || cacheRead) {
701
+ console.log(`[anthropic] callText cache: created=${cacheCreated} read=${cacheRead} input=${usage.input_tokens} output=${usage.output_tokens}`);
702
+ }
703
+ const text = response.content.filter((block) => block.type === "text").map((block) => block.text).join("");
704
+ const endedAt = Date.now();
705
+ emitLlmTraceEvent({
706
+ id: traceId,
707
+ at: startedAt,
708
+ endedAt,
709
+ durationMs: endedAt - startedAt,
710
+ provider: "anthropic",
711
+ model: resolvedModel,
712
+ kind: "callText",
713
+ systemPrompt,
714
+ userPrompt,
715
+ result: {
716
+ inputTokens: response.usage.input_tokens,
717
+ outputTokens: response.usage.output_tokens,
718
+ cacheCreated,
719
+ cacheRead,
720
+ text
721
+ }
722
+ });
723
+ return {
724
+ text,
725
+ inputTokens: response.usage.input_tokens,
726
+ outputTokens: response.usage.output_tokens,
727
+ sampling: sampling.applied
728
+ };
729
+ } catch (e) {
730
+ const endedAt = Date.now();
731
+ emitLlmTraceEvent({
732
+ id: traceId,
733
+ at: startedAt,
734
+ endedAt,
735
+ durationMs: endedAt - startedAt,
736
+ provider: "anthropic",
737
+ model: resolvedModel,
738
+ kind: "callText",
739
+ systemPrompt,
740
+ userPrompt,
741
+ error: { message: e instanceof Error ? e.message : String(e) }
742
+ });
743
+ throw e;
744
+ }
745
+ }
746
+ async callVision(model, systemPrompt, userPrompt, image, maxTokens) {
747
+ const client = await this.getClient();
748
+ const traceId = newLlmTraceId();
749
+ const startedAt = Date.now();
750
+ const resolvedModel = this.resolveModel(model);
751
+ const imageBlock = {
752
+ type: "image",
753
+ source: {
754
+ type: "base64",
755
+ media_type: image.mediaType,
756
+ data: image.base64
757
+ }
758
+ };
759
+ try {
760
+ const response = await this.apiCall(
761
+ () => client.messages.create({
762
+ model: resolvedModel,
763
+ max_tokens: maxTokens ?? 1500,
764
+ system: systemPrompt,
765
+ messages: [
766
+ {
767
+ role: "user",
768
+ content: [imageBlock, { type: "text", text: userPrompt }]
769
+ }
770
+ ]
771
+ })
772
+ );
773
+ const text = response.content.filter((block) => block.type === "text").map((block) => block.text).join("");
774
+ const endedAt = Date.now();
775
+ emitLlmTraceEvent({
776
+ id: traceId,
777
+ at: startedAt,
778
+ endedAt,
779
+ durationMs: endedAt - startedAt,
780
+ provider: "anthropic",
781
+ model: resolvedModel,
782
+ kind: "callVision",
783
+ systemPrompt,
784
+ userPrompt,
785
+ result: {
786
+ inputTokens: response.usage.input_tokens,
787
+ outputTokens: response.usage.output_tokens,
788
+ text
789
+ }
790
+ });
791
+ return {
792
+ text,
793
+ inputTokens: response.usage.input_tokens,
794
+ outputTokens: response.usage.output_tokens,
795
+ ...visionFinish(anthropicFinishReason(response.stop_reason)),
796
+ ...servedModelOf(response.model)
797
+ };
798
+ } catch (e) {
799
+ const endedAt = Date.now();
800
+ emitLlmTraceEvent({
801
+ id: traceId,
802
+ at: startedAt,
803
+ endedAt,
804
+ durationMs: endedAt - startedAt,
805
+ provider: "anthropic",
806
+ model: resolvedModel,
807
+ kind: "callVision",
808
+ systemPrompt,
809
+ userPrompt,
810
+ error: { message: e instanceof Error ? e.message : String(e) }
811
+ });
812
+ throw e;
813
+ }
814
+ }
815
+ async callTools(model, systemPrompt, userPrompt, tools, toolChoice = "required", _scopedTools) {
816
+ const client = await this.getClient();
817
+ const traceId = newLlmTraceId();
818
+ const startedAt = Date.now();
819
+ const resolvedModel = this.resolveModel(model);
820
+ const system = [
821
+ {
822
+ type: "text",
823
+ text: systemPrompt,
824
+ cache_control: { type: "ephemeral" }
825
+ }
826
+ ];
827
+ const cachedTools = tools.map((t, i) => ({
828
+ name: t.name,
829
+ description: t.description,
830
+ input_schema: t.parameters,
831
+ // Mark last tool to cache the entire tool list
832
+ ...i === tools.length - 1 ? { cache_control: { type: "ephemeral" } } : {}
833
+ }));
834
+ try {
835
+ const appliedToolChoice = toolChoice === "required" && anthropicRejectsForcedToolChoice(resolvedModel) ? "auto" : toolChoice;
836
+ if (appliedToolChoice !== toolChoice) {
837
+ console.warn(
838
+ `[anthropic] callTools: ${normalizeAnthropicModelId(resolvedModel)} would reject a forced tool_choice (the API answers 400); caller's 'required' downgraded to 'auto' before sending`
839
+ );
840
+ }
841
+ const response = await this.apiCall(
842
+ () => client.messages.stream({
843
+ model: resolvedModel,
844
+ max_tokens: 32768,
845
+ system,
846
+ messages: [{ role: "user", content: userPrompt }],
847
+ tools: cachedTools,
848
+ tool_choice: { type: appliedToolChoice === "required" ? "any" : "auto" }
849
+ }).finalMessage()
850
+ );
851
+ const usage = response.usage;
852
+ const cacheCreated = usage.cache_creation_input_tokens ?? 0;
853
+ const cacheRead = usage.cache_read_input_tokens ?? 0;
854
+ if (cacheCreated || cacheRead) {
855
+ console.log(`[anthropic] callTools cache: created=${cacheCreated} read=${cacheRead} input=${usage.input_tokens} output=${usage.output_tokens}`);
856
+ }
857
+ const toolUses = response.content.filter(
858
+ (block) => block.type === "tool_use"
859
+ );
860
+ const toolCalls = toolUses.map((tu) => ({
861
+ id: tu.id,
862
+ name: tu.name,
863
+ input: tu.input ?? {}
864
+ }));
865
+ const endedAt = Date.now();
866
+ emitLlmTraceEvent({
867
+ id: traceId,
868
+ at: startedAt,
869
+ endedAt,
870
+ durationMs: endedAt - startedAt,
871
+ provider: "anthropic",
872
+ model: resolvedModel,
873
+ kind: "callTools",
874
+ systemPrompt,
875
+ userPrompt,
876
+ tools: summarizeTools(tools),
877
+ result: {
878
+ inputTokens: response.usage.input_tokens,
879
+ outputTokens: response.usage.output_tokens,
880
+ cacheCreated,
881
+ cacheRead,
882
+ toolCalls: toolCalls.map((c) => ({ name: c.name, input: c.input }))
883
+ }
884
+ });
885
+ return {
886
+ toolCalls,
887
+ inputTokens: response.usage.input_tokens,
888
+ outputTokens: response.usage.output_tokens,
889
+ cacheReadTokens: cacheRead,
890
+ cacheCreationTokens: cacheCreated,
891
+ appliedToolChoice
892
+ };
893
+ } catch (e) {
894
+ const endedAt = Date.now();
895
+ emitLlmTraceEvent({
896
+ id: traceId,
897
+ at: startedAt,
898
+ endedAt,
899
+ durationMs: endedAt - startedAt,
900
+ provider: "anthropic",
901
+ model: resolvedModel,
902
+ kind: "callTools",
903
+ systemPrompt,
904
+ userPrompt,
905
+ tools: summarizeTools(tools),
906
+ error: { message: e instanceof Error ? e.message : String(e) }
907
+ });
908
+ throw e;
909
+ }
910
+ }
911
+ async callWithTools(model, systemPrompt, userPrompt, tools, maxTurns) {
912
+ const client = await this.getClient();
913
+ const resolvedModel = this.resolveModel(model);
914
+ const traceId = newLlmTraceId();
915
+ const startedAt = Date.now();
916
+ const anthropicTools = tools.map((t) => ({
917
+ name: t.name,
918
+ description: t.description,
919
+ input_schema: t.parameters
920
+ }));
921
+ const messages = [
922
+ { role: "user", content: userPrompt }
923
+ ];
924
+ let totalIn = 0;
925
+ let totalOut = 0;
926
+ let allText = "";
927
+ try {
928
+ for (let turn = 0; turn < maxTurns; turn++) {
929
+ const response = await this.apiCall(
930
+ () => client.messages.stream({
931
+ model: resolvedModel,
932
+ max_tokens: 32768,
933
+ system: systemPrompt,
934
+ messages,
935
+ tools: anthropicTools
936
+ }).finalMessage()
937
+ );
938
+ totalIn += response.usage.input_tokens;
939
+ totalOut += response.usage.output_tokens;
940
+ for (const block of response.content) {
941
+ if (block.type === "text") allText += block.text;
942
+ }
943
+ const toolUses = response.content.filter(
944
+ (b) => b.type === "tool_use"
945
+ );
946
+ if (toolUses.length === 0) break;
947
+ messages.push({ role: "assistant", content: response.content });
948
+ const toolResults = [];
949
+ for (const tu of toolUses) {
950
+ const tool = tools.find((t) => t.name === tu.name);
951
+ if (!tool) {
952
+ toolResults.push({
953
+ type: "tool_result",
954
+ tool_use_id: tu.id,
955
+ content: `Tool '${tu.name}' not found`
956
+ });
957
+ continue;
958
+ }
959
+ const result = await tool.handler(
960
+ tu.input ?? {}
961
+ );
962
+ toolResults.push({
963
+ type: "tool_result",
964
+ tool_use_id: tu.id,
965
+ content: result.content[0]?.text ?? ""
966
+ });
967
+ }
968
+ messages.push({ role: "user", content: toolResults });
969
+ }
970
+ const endedAt = Date.now();
971
+ emitLlmTraceEvent({
972
+ id: traceId,
973
+ at: startedAt,
974
+ endedAt,
975
+ durationMs: endedAt - startedAt,
976
+ provider: "anthropic",
977
+ model: resolvedModel,
978
+ kind: "callWithTools",
979
+ systemPrompt,
980
+ userPrompt,
981
+ tools: tools.map((t) => ({ name: t.name, description: t.description })),
982
+ result: {
983
+ inputTokens: totalIn,
984
+ outputTokens: totalOut,
985
+ text: allText,
986
+ turnsUsed: messages.length
987
+ }
988
+ });
989
+ return {
990
+ text: allText,
991
+ inputTokens: totalIn,
992
+ outputTokens: totalOut,
993
+ turnsUsed: messages.length
994
+ };
995
+ } catch (e) {
996
+ const endedAt = Date.now();
997
+ emitLlmTraceEvent({
998
+ id: traceId,
999
+ at: startedAt,
1000
+ endedAt,
1001
+ durationMs: endedAt - startedAt,
1002
+ provider: "anthropic",
1003
+ model: resolvedModel,
1004
+ kind: "callWithTools",
1005
+ systemPrompt,
1006
+ userPrompt,
1007
+ tools: tools.map((t) => ({ name: t.name, description: t.description })),
1008
+ error: { message: e instanceof Error ? e.message : String(e) }
1009
+ });
1010
+ throw e;
1011
+ }
1012
+ }
1013
+ };
1014
+ var ClaudeCodeLoginAgent = class extends LLMAgent {
1015
+ provider = "anthropic";
1016
+ resolveModel(model) {
1017
+ return model.startsWith("anthropic/") ? model.slice("anthropic/".length) : model;
1018
+ }
1019
+ async createClient() {
1020
+ const { query, tool, createSdkMcpServer } = await loadClaudeAgentSdk();
1021
+ return { query, tool, createSdkMcpServer };
1022
+ }
1023
+ async callText(model, systemPrompt, userPrompt, _maxTokens, _temperature) {
1024
+ const sdk = await this.getClient();
1025
+ const traceId = newLlmTraceId();
1026
+ const startedAt = Date.now();
1027
+ const resolvedModel = this.resolveModel(model);
1028
+ try {
1029
+ const turn = await this.apiCall(
1030
+ () => collectLoginTurn(
1031
+ sdk.query({
1032
+ prompt: userPrompt,
1033
+ options: buildLoginQueryOptions({ model: resolvedModel, systemPrompt })
1034
+ }),
1035
+ { intercepted: [], kind: "callText", choice: "none", log: console.log, warn: console.warn }
1036
+ )
1037
+ );
1038
+ const usage = turn.result.usage;
1039
+ const endedAt = Date.now();
1040
+ emitLlmTraceEvent({
1041
+ id: traceId,
1042
+ at: startedAt,
1043
+ endedAt,
1044
+ durationMs: endedAt - startedAt,
1045
+ provider: "claude-code-login",
1046
+ model: resolvedModel,
1047
+ kind: "callText",
1048
+ systemPrompt,
1049
+ userPrompt,
1050
+ result: {
1051
+ inputTokens: usage.input_tokens,
1052
+ outputTokens: usage.output_tokens,
1053
+ cacheCreated: usage.cache_creation_input_tokens,
1054
+ cacheRead: usage.cache_read_input_tokens,
1055
+ text: turn.text
1056
+ }
1057
+ });
1058
+ return { text: turn.text, inputTokens: usage.input_tokens, outputTokens: usage.output_tokens };
1059
+ } catch (e) {
1060
+ const endedAt = Date.now();
1061
+ emitLlmTraceEvent({
1062
+ id: traceId,
1063
+ at: startedAt,
1064
+ endedAt,
1065
+ durationMs: endedAt - startedAt,
1066
+ provider: "claude-code-login",
1067
+ model: resolvedModel,
1068
+ kind: "callText",
1069
+ systemPrompt,
1070
+ userPrompt,
1071
+ error: { message: e instanceof Error ? e.message : String(e) }
1072
+ });
1073
+ throw e;
1074
+ }
1075
+ }
1076
+ async callVision(model, systemPrompt, userPrompt, image, _maxTokens) {
1077
+ const sdk = await this.getClient();
1078
+ const traceId = newLlmTraceId();
1079
+ const startedAt = Date.now();
1080
+ const resolvedModel = this.resolveModel(model);
1081
+ try {
1082
+ const turn = await this.apiCall(
1083
+ () => collectLoginTurn(
1084
+ sdk.query({
1085
+ prompt: imageUserMessage(userPrompt, image),
1086
+ options: buildLoginQueryOptions({ model: resolvedModel, systemPrompt })
1087
+ }),
1088
+ { intercepted: [], kind: "callVision", choice: "none", log: console.log, warn: console.warn }
1089
+ )
1090
+ );
1091
+ const usage = turn.result.usage;
1092
+ const endedAt = Date.now();
1093
+ emitLlmTraceEvent({
1094
+ id: traceId,
1095
+ at: startedAt,
1096
+ endedAt,
1097
+ durationMs: endedAt - startedAt,
1098
+ provider: "claude-code-login",
1099
+ model: resolvedModel,
1100
+ kind: "callVision",
1101
+ systemPrompt,
1102
+ userPrompt,
1103
+ result: { inputTokens: usage.input_tokens, outputTokens: usage.output_tokens, text: turn.text }
1104
+ });
1105
+ return { text: turn.text, inputTokens: usage.input_tokens, outputTokens: usage.output_tokens };
1106
+ } catch (e) {
1107
+ const endedAt = Date.now();
1108
+ emitLlmTraceEvent({
1109
+ id: traceId,
1110
+ at: startedAt,
1111
+ endedAt,
1112
+ durationMs: endedAt - startedAt,
1113
+ provider: "claude-code-login",
1114
+ model: resolvedModel,
1115
+ kind: "callVision",
1116
+ systemPrompt,
1117
+ userPrompt,
1118
+ error: { message: e instanceof Error ? e.message : String(e) }
1119
+ });
1120
+ throw e;
1121
+ }
1122
+ }
1123
+ async callTools(model, systemPrompt, userPrompt, tools, toolChoice = "required", _scopedTools) {
1124
+ const sdk = await this.getClient();
1125
+ const traceId = newLlmTraceId();
1126
+ const startedAt = Date.now();
1127
+ const resolvedModel = this.resolveModel(model);
1128
+ const sentUserPrompt = toolChoice === "required" ? `${userPrompt}
1129
+
1130
+ ${LOGIN_REQUIRED_TOOL_INSTRUCTION}` : userPrompt;
1131
+ try {
1132
+ const turn = await this.apiCall(() => {
1133
+ const intercepted = [];
1134
+ const server = createLoginToolServer(sdk, tools);
1135
+ return collectLoginTurn(
1136
+ sdk.query({
1137
+ prompt: sentUserPrompt,
1138
+ options: buildLoginQueryOptions({
1139
+ model: resolvedModel,
1140
+ systemPrompt,
1141
+ tools: { server, canUseTool: createDenyingIntercept(intercepted) }
1142
+ })
1143
+ }),
1144
+ { intercepted, kind: "callTools", choice: toolChoice === "required" ? "required:instructed" : "auto", log: console.log, warn: console.warn }
1145
+ );
1146
+ });
1147
+ const usage = turn.result.usage;
1148
+ const cacheCreated = usage.cache_creation_input_tokens;
1149
+ const cacheRead = usage.cache_read_input_tokens;
1150
+ if (cacheCreated || cacheRead) {
1151
+ console.log(
1152
+ `[claude-code-login] callTools cache: created=${cacheCreated} read=${cacheRead} input=${usage.input_tokens} output=${usage.output_tokens}`
1153
+ );
1154
+ }
1155
+ const toolCalls = turn.toolCalls.map((c) => ({ id: c.id, name: c.name, input: c.input }));
1156
+ const endedAt = Date.now();
1157
+ emitLlmTraceEvent({
1158
+ id: traceId,
1159
+ at: startedAt,
1160
+ endedAt,
1161
+ durationMs: endedAt - startedAt,
1162
+ provider: "claude-code-login",
1163
+ model: resolvedModel,
1164
+ kind: "callTools",
1165
+ systemPrompt,
1166
+ userPrompt: sentUserPrompt,
1167
+ tools: summarizeTools(tools),
1168
+ result: {
1169
+ inputTokens: usage.input_tokens,
1170
+ outputTokens: usage.output_tokens,
1171
+ cacheCreated,
1172
+ cacheRead,
1173
+ toolCalls: toolCalls.map((c) => ({ name: c.name, input: c.input }))
1174
+ }
1175
+ });
1176
+ return {
1177
+ toolCalls,
1178
+ inputTokens: usage.input_tokens,
1179
+ outputTokens: usage.output_tokens,
1180
+ cacheReadTokens: cacheRead,
1181
+ cacheCreationTokens: cacheCreated,
1182
+ appliedToolChoice: "auto"
1183
+ };
1184
+ } catch (e) {
1185
+ const endedAt = Date.now();
1186
+ emitLlmTraceEvent({
1187
+ id: traceId,
1188
+ at: startedAt,
1189
+ endedAt,
1190
+ durationMs: endedAt - startedAt,
1191
+ provider: "claude-code-login",
1192
+ model: resolvedModel,
1193
+ kind: "callTools",
1194
+ systemPrompt,
1195
+ userPrompt: sentUserPrompt,
1196
+ tools: summarizeTools(tools),
1197
+ error: { message: e instanceof Error ? e.message : String(e) }
1198
+ });
1199
+ throw e;
1200
+ }
1201
+ }
1202
+ async callWithTools() {
1203
+ throw new Error(
1204
+ "claude-code-login: callWithTools (an in-client agentic loop) is not part of the login client \u2014 ggui's harness runs the loop through callTools"
1205
+ );
1206
+ }
1207
+ };
1208
+ var OpenAIAgent = class extends LLMAgent {
1209
+ provider = "openai";
1210
+ resolveModel(model) {
1211
+ return model.startsWith("openai/") ? model.slice("openai/".length) : model;
1212
+ }
1213
+ async createClient() {
1214
+ const { default: OpenAISDK } = await import('openai');
1215
+ return new OpenAISDK({ apiKey: this.routeOverride?.apiKey ?? process.env.OPENAI_API_KEY });
1216
+ }
1217
+ async callText(model, systemPrompt, userPrompt, maxTokens, temperature) {
1218
+ const client = await this.getClient();
1219
+ const response = await this.apiCall(
1220
+ () => client.responses.create({
1221
+ model: this.resolveModel(model),
1222
+ instructions: systemPrompt,
1223
+ input: [{ role: "user", content: userPrompt }],
1224
+ ...maxTokens && { max_output_tokens: maxTokens },
1225
+ ...temperature !== void 0 && { temperature }
1226
+ })
1227
+ );
1228
+ let text = "";
1229
+ for (const item of response.output) {
1230
+ if (item.type === "message") {
1231
+ for (const part of item.content) {
1232
+ if (part.type === "output_text") text += part.text;
1233
+ }
1234
+ }
1235
+ }
1236
+ const textUsage = splitOpenAiUsage(
1237
+ response.usage?.input_tokens ?? 0,
1238
+ response.usage?.output_tokens ?? 0,
1239
+ response.usage?.input_tokens_details?.cached_tokens ?? 0,
1240
+ 0
1241
+ );
1242
+ return {
1243
+ text,
1244
+ inputTokens: textUsage.tokens.input,
1245
+ outputTokens: textUsage.tokens.output,
1246
+ sampling: { temperature: temperature ?? "provider-default" }
1247
+ };
1248
+ }
1249
+ async callTools(model, systemPrompt, userPrompt, tools, toolChoice = "required", _scopedTools) {
1250
+ const client = await this.getClient();
1251
+ const openaiTools = tools.map((t) => ({
1252
+ type: "function",
1253
+ name: t.name,
1254
+ description: t.description,
1255
+ parameters: addStrictSchemaConstraints(t.parameters),
1256
+ strict: true
1257
+ }));
1258
+ const pending = this.pendingToolResults;
1259
+ this.pendingToolResults = [];
1260
+ const input = pending.length > 0 ? [...pending, { role: "user", content: userPrompt }] : [{ role: "user", content: userPrompt }];
1261
+ const response = await this.apiCall(
1262
+ () => client.responses.create({
1263
+ model: this.resolveModel(model),
1264
+ instructions: systemPrompt,
1265
+ input,
1266
+ tools: openaiTools,
1267
+ tool_choice: toolChoice,
1268
+ store: true,
1269
+ // Enable response storage + server-side chaining
1270
+ ...this.lastSessionId && { previous_response_id: this.lastSessionId }
1271
+ })
1272
+ );
1273
+ this.lastSessionId = response.id;
1274
+ const usage = response.usage;
1275
+ if (usage) {
1276
+ const cached = usage.input_tokens_details;
1277
+ if (cached?.cached_tokens) {
1278
+ console.log(`[openai] session ${this.lastSessionId ? "chained" : "new"}: ${cached.cached_tokens} cached of ${usage.input_tokens} input`);
1279
+ }
1280
+ }
1281
+ const functionCalls = response.output.filter(
1282
+ (o) => o.type === "function_call"
1283
+ );
1284
+ const toolsUsage = splitOpenAiUsage(
1285
+ response.usage?.input_tokens ?? 0,
1286
+ response.usage?.output_tokens ?? 0,
1287
+ response.usage?.input_tokens_details?.cached_tokens ?? 0,
1288
+ response.usage?.input_tokens_details?.cache_write_tokens ?? 0
1289
+ );
1290
+ return {
1291
+ toolCalls: functionCalls.map((fc) => ({
1292
+ id: fc.call_id,
1293
+ name: fc.name,
1294
+ input: JSON.parse(fc.arguments ?? "{}")
1295
+ })),
1296
+ inputTokens: toolsUsage.tokens.input,
1297
+ outputTokens: toolsUsage.tokens.output,
1298
+ cacheReadTokens: toolsUsage.cacheReadTokens,
1299
+ cacheCreationTokens: toolsUsage.cacheCreationTokens
1300
+ };
1301
+ }
1302
+ // Pending tool results — stored by sendToolResult, consumed by next callTools
1303
+ pendingToolResults = [];
1304
+ async sendToolResult(results) {
1305
+ this.pendingToolResults = results.map((r) => ({
1306
+ type: "function_call_output",
1307
+ call_id: r.callId ?? "",
1308
+ output: r.result
1309
+ }));
1310
+ }
1311
+ async callWithTools(model, systemPrompt, userPrompt, tools, maxTurns) {
1312
+ const client = await this.getClient();
1313
+ const resolvedModel = this.resolveModel(model);
1314
+ const openaiTools = tools.map((t) => ({
1315
+ type: "function",
1316
+ name: t.name,
1317
+ description: t.description,
1318
+ parameters: t.parameters,
1319
+ strict: false
1320
+ }));
1321
+ let input = [
1322
+ { role: "user", content: userPrompt }
1323
+ ];
1324
+ let totalIn = 0;
1325
+ let totalOut = 0;
1326
+ let totalCacheRead = 0;
1327
+ let totalCacheWrite = 0;
1328
+ let allText = "";
1329
+ for (let turn = 0; turn < maxTurns; turn++) {
1330
+ const response = await this.apiCall(
1331
+ () => client.responses.create({
1332
+ model: resolvedModel,
1333
+ instructions: systemPrompt,
1334
+ input,
1335
+ tools: openaiTools
1336
+ })
1337
+ );
1338
+ const turnUsage = splitOpenAiUsage(
1339
+ response.usage?.input_tokens ?? 0,
1340
+ response.usage?.output_tokens ?? 0,
1341
+ response.usage?.input_tokens_details?.cached_tokens ?? 0,
1342
+ response.usage?.input_tokens_details?.cache_write_tokens ?? 0
1343
+ );
1344
+ totalIn += turnUsage.tokens.input;
1345
+ totalOut += turnUsage.tokens.output;
1346
+ totalCacheRead += turnUsage.cacheReadTokens;
1347
+ totalCacheWrite += turnUsage.cacheCreationTokens;
1348
+ const functionCalls = response.output.filter(
1349
+ (o) => o.type === "function_call"
1350
+ );
1351
+ for (const item of response.output) {
1352
+ if (item.type === "message") {
1353
+ for (const part of item.content) {
1354
+ if (part.type === "output_text") allText += part.text;
1355
+ }
1356
+ }
1357
+ }
1358
+ if (functionCalls.length === 0) break;
1359
+ input = [
1360
+ ...response.output
1361
+ ];
1362
+ for (const fc of functionCalls) {
1363
+ const tool = tools.find((t) => t.name === fc.name);
1364
+ const result = tool ? await tool.handler(
1365
+ JSON.parse(fc.arguments ?? "{}")
1366
+ ) : { content: [{ text: `Tool '${fc.name}' not found` }] };
1367
+ input.push({
1368
+ type: "function_call_output",
1369
+ call_id: fc.call_id,
1370
+ output: result.content[0]?.text ?? ""
1371
+ });
1372
+ }
1373
+ }
1374
+ return {
1375
+ text: allText,
1376
+ inputTokens: totalIn,
1377
+ outputTokens: totalOut,
1378
+ cacheReadTokens: totalCacheRead,
1379
+ cacheCreationTokens: totalCacheWrite,
1380
+ turnsUsed: input.length
1381
+ };
1382
+ }
1383
+ };
1384
+ function interactionText(interaction) {
1385
+ let text = "";
1386
+ for (const step of interaction.steps ?? []) {
1387
+ if (step.type === "model_output") {
1388
+ for (const content of step.content ?? []) {
1389
+ if (content.type === "text") {
1390
+ text += content.text;
1391
+ }
1392
+ }
1393
+ }
1394
+ }
1395
+ return text;
1396
+ }
1397
+ function functionCallSteps(interaction) {
1398
+ return (interaction.steps ?? []).filter(
1399
+ (s) => s.type === "function_call"
1400
+ );
1401
+ }
1402
+ function toUserInputStep(text) {
1403
+ return { type: "user_input", content: [{ type: "text", text }] };
1404
+ }
1405
+ function googleInteractionUsage(usage) {
1406
+ const split = splitCacheInclusiveUsage(usage?.total_input_tokens ?? 0, usage?.total_output_tokens ?? 0, usage?.total_cached_tokens ?? 0);
1407
+ return { inputTokens: split.tokens.input, outputTokens: split.tokens.output, cacheReadTokens: split.cacheReadTokens };
1408
+ }
1409
+ var GoogleAgent = class extends LLMAgent {
1410
+ provider = "google";
1411
+ resolveModel(model) {
1412
+ return model.startsWith("gemini/") ? model.slice("gemini/".length) : model;
1413
+ }
1414
+ async createClient() {
1415
+ const { GoogleGenAI: GoogleGenAISDK } = await import('@google/genai');
1416
+ return new GoogleGenAISDK({
1417
+ apiKey: this.routeOverride?.apiKey ?? (process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY),
1418
+ httpOptions: { timeout: 3e5 }
1419
+ // 5 min — Pro models can be slow
1420
+ });
1421
+ }
1422
+ async callText(model, systemPrompt, userPrompt, maxTokens, temperature) {
1423
+ const client = await this.getClient();
1424
+ const generationConfig = {
1425
+ ...maxTokens ? { max_output_tokens: maxTokens } : {},
1426
+ ...temperature !== void 0 ? { temperature } : {}
1427
+ };
1428
+ const interaction = await this.apiCall(
1429
+ () => client.interactions.create({
1430
+ model: this.resolveModel(model),
1431
+ system_instruction: systemPrompt,
1432
+ input: userPrompt,
1433
+ generation_config: Object.keys(generationConfig).length > 0 ? generationConfig : void 0
1434
+ })
1435
+ );
1436
+ return {
1437
+ text: interactionText(interaction),
1438
+ // ggui#1186 (third site): `total_input_tokens` INCLUDES the cached part of the prompt — report the non-cached input.
1439
+ inputTokens: googleInteractionUsage(interaction.usage).inputTokens,
1440
+ outputTokens: googleInteractionUsage(interaction.usage).outputTokens,
1441
+ sampling: { temperature: temperature ?? "provider-default" }
1442
+ };
1443
+ }
1444
+ async callVision(model, systemPrompt, userPrompt, image, maxTokens) {
1445
+ const client = await this.getClient();
1446
+ const traceId = newLlmTraceId();
1447
+ const startedAt = Date.now();
1448
+ const resolvedModel = this.resolveModel(model);
1449
+ try {
1450
+ const response = await this.apiCall(
1451
+ () => client.models.generateContent({
1452
+ model: resolvedModel,
1453
+ contents: [
1454
+ {
1455
+ role: "user",
1456
+ parts: [
1457
+ { inlineData: { mimeType: image.mediaType, data: image.base64 } },
1458
+ { text: userPrompt }
1459
+ ]
1460
+ }
1461
+ ],
1462
+ config: {
1463
+ systemInstruction: systemPrompt,
1464
+ ...maxTokens ? { maxOutputTokens: maxTokens } : {}
1465
+ }
1466
+ })
1467
+ );
1468
+ const text = response.candidates?.[0]?.content?.parts?.map((p) => p.text || "").join("") || "";
1469
+ const usage = response.usageMetadata;
1470
+ const result = {
1471
+ text,
1472
+ // ggui#1186 (third site): `promptTokenCount` includes `cachedContentTokenCount` — report the non-cached input.
1473
+ inputTokens: splitCacheInclusiveUsage(usage?.promptTokenCount ?? 0, usage?.candidatesTokenCount ?? 0, usage?.cachedContentTokenCount ?? 0).tokens.input,
1474
+ outputTokens: usage?.candidatesTokenCount ?? 0,
1475
+ ...visionFinish(googleFinishReason(response.candidates?.[0]?.finishReason)),
1476
+ ...servedModelOf(response.modelVersion)
1477
+ };
1478
+ const endedAt = Date.now();
1479
+ emitLlmTraceEvent({
1480
+ id: traceId,
1481
+ at: startedAt,
1482
+ endedAt,
1483
+ durationMs: endedAt - startedAt,
1484
+ provider: "google",
1485
+ model: resolvedModel,
1486
+ kind: "callVision",
1487
+ systemPrompt,
1488
+ userPrompt,
1489
+ result: {
1490
+ inputTokens: result.inputTokens,
1491
+ outputTokens: result.outputTokens,
1492
+ text
1493
+ }
1494
+ });
1495
+ return result;
1496
+ } catch (e) {
1497
+ const endedAt = Date.now();
1498
+ emitLlmTraceEvent({
1499
+ id: traceId,
1500
+ at: startedAt,
1501
+ endedAt,
1502
+ durationMs: endedAt - startedAt,
1503
+ provider: "google",
1504
+ model: resolvedModel,
1505
+ kind: "callVision",
1506
+ systemPrompt,
1507
+ userPrompt,
1508
+ error: { message: e instanceof Error ? e.message : String(e) }
1509
+ });
1510
+ throw e;
1511
+ }
1512
+ }
1513
+ async callTools(model, systemPrompt, userPrompt, tools, _toolChoice = "required", scopedTools) {
1514
+ const client = await this.getClient();
1515
+ const interactionTools = tools.map((t) => ({
1516
+ type: "function",
1517
+ name: t.name,
1518
+ description: t.description,
1519
+ parameters: t.parameters
1520
+ }));
1521
+ const scopedInteractionTools = scopedTools?.map((t) => ({
1522
+ type: "function",
1523
+ name: t.name,
1524
+ description: t.description,
1525
+ parameters: t.parameters
1526
+ }));
1527
+ const resolvedModel = this.resolveModel(model);
1528
+ const pending = this.pendingToolResults;
1529
+ this.pendingToolResults = [];
1530
+ const input = pending.length > 0 ? [...pending, toUserInputStep(userPrompt)] : userPrompt;
1531
+ const createInteraction = () => this.lastSessionId ? client.interactions.create({
1532
+ model: resolvedModel,
1533
+ previous_interaction_id: this.lastSessionId,
1534
+ input,
1535
+ tools: interactionTools,
1536
+ generation_config: { max_output_tokens: 16384 }
1537
+ }) : client.interactions.create({
1538
+ model: resolvedModel,
1539
+ system_instruction: systemPrompt,
1540
+ tools: interactionTools,
1541
+ input: userPrompt,
1542
+ generation_config: { max_output_tokens: 16384 }
1543
+ });
1544
+ let interaction;
1545
+ const MAX_MALFORMED_RETRIES = 3;
1546
+ let succeeded = false;
1547
+ for (let attempt = 0; attempt < MAX_MALFORMED_RETRIES; attempt++) {
1548
+ try {
1549
+ if (attempt === 0) {
1550
+ interaction = await this.apiCall(createInteraction);
1551
+ } else {
1552
+ const jsonHint = "\n\nIMPORTANT: Your previous response had invalid JSON. Produce valid JSON in your tool call arguments.";
1553
+ const retryPrompt = userPrompt + jsonHint;
1554
+ interaction = await this.apiCall(
1555
+ () => this.lastSessionId ? client.interactions.create({
1556
+ model: resolvedModel,
1557
+ previous_interaction_id: this.lastSessionId,
1558
+ input: pending.length > 0 ? [...pending, toUserInputStep(retryPrompt)] : retryPrompt,
1559
+ tools: interactionTools,
1560
+ generation_config: { max_output_tokens: 16384 }
1561
+ }) : client.interactions.create({
1562
+ model: resolvedModel,
1563
+ system_instruction: systemPrompt,
1564
+ tools: interactionTools,
1565
+ input: retryPrompt,
1566
+ generation_config: { max_output_tokens: 16384 }
1567
+ })
1568
+ );
1569
+ }
1570
+ succeeded = true;
1571
+ break;
1572
+ } catch (e) {
1573
+ const msg = e instanceof Error ? e.message : String(e);
1574
+ if (msg.includes("malformed_tool_call") && attempt < MAX_MALFORMED_RETRIES - 1) {
1575
+ console.warn(`[google] malformed_tool_call (attempt ${attempt + 1}/${MAX_MALFORMED_RETRIES}) \u2014 retrying`);
1576
+ continue;
1577
+ }
1578
+ if (msg.includes("malformed_tool_call") && scopedInteractionTools) {
1579
+ console.warn("[google] malformed_tool_call exhausted \u2014 retrying with scoped tool schema");
1580
+ const scopedHint = "\n\nYour previous response exceeded the tool-call payload budget. Produce a SINGLE small change (\u226420 lines) using the narrowed tool schema.";
1581
+ const scopedPrompt = userPrompt + scopedHint;
1582
+ try {
1583
+ interaction = await this.apiCall(
1584
+ () => this.lastSessionId ? client.interactions.create({
1585
+ model: resolvedModel,
1586
+ previous_interaction_id: this.lastSessionId,
1587
+ input: pending.length > 0 ? [...pending, toUserInputStep(scopedPrompt)] : scopedPrompt,
1588
+ tools: scopedInteractionTools,
1589
+ generation_config: { max_output_tokens: 16384 }
1590
+ }) : client.interactions.create({
1591
+ model: resolvedModel,
1592
+ system_instruction: systemPrompt,
1593
+ tools: scopedInteractionTools,
1594
+ input: scopedPrompt,
1595
+ generation_config: { max_output_tokens: 16384 }
1596
+ })
1597
+ );
1598
+ succeeded = true;
1599
+ break;
1600
+ } catch (e2) {
1601
+ const msg2 = e2 instanceof Error ? e2.message : String(e2);
1602
+ console.error(`[google] scoped retry FAILED: ${msg2.slice(0, 300)}`);
1603
+ throw e2;
1604
+ }
1605
+ }
1606
+ console.error(`[google] callTools FAILED: ${msg.slice(0, 300)}`);
1607
+ throw e;
1608
+ }
1609
+ }
1610
+ if (!succeeded) throw new Error("[google] callTools: exhausted retries without success");
1611
+ this.lastSessionId = interaction.id;
1612
+ const toolCalls = functionCallSteps(interaction).map((fc) => ({
1613
+ id: fc.id,
1614
+ name: fc.name,
1615
+ input: toolArgsToJsonObject(fc.arguments)
1616
+ }));
1617
+ return {
1618
+ toolCalls,
1619
+ // ggui#1186 (third site): the coding loop reads this per-turn usage — the non-cached prompt as input, the cached subset reported.
1620
+ ...googleInteractionUsage(interaction.usage)
1621
+ };
1622
+ }
1623
+ // Pending tool results — stored by sendToolResult, consumed by next callTools
1624
+ pendingToolResults = [];
1625
+ async sendToolResult(results) {
1626
+ this.pendingToolResults = results.map((r) => ({
1627
+ type: "function_result",
1628
+ call_id: r.callId ?? "",
1629
+ name: r.name,
1630
+ result: r.result,
1631
+ is_error: r.isError
1632
+ }));
1633
+ }
1634
+ async callWithTools(model, systemPrompt, userPrompt, tools, maxTurns) {
1635
+ const client = await this.getClient();
1636
+ const interactionTools = tools.map((t) => ({
1637
+ type: "function",
1638
+ name: t.name,
1639
+ description: t.description,
1640
+ parameters: t.parameters
1641
+ }));
1642
+ let totalIn = 0;
1643
+ let totalOut = 0;
1644
+ let totalCacheRead = 0;
1645
+ let allText = "";
1646
+ let turnsUsed = 0;
1647
+ let interaction = await this.apiCall(
1648
+ () => client.interactions.create({
1649
+ model: this.resolveModel(model),
1650
+ system_instruction: systemPrompt,
1651
+ tools: interactionTools,
1652
+ input: userPrompt
1653
+ })
1654
+ );
1655
+ for (let turn = 0; turn < maxTurns; turn++) {
1656
+ turnsUsed = turn + 1;
1657
+ if (interaction.usage) {
1658
+ const turnUsage = googleInteractionUsage(interaction.usage);
1659
+ totalIn += turnUsage.inputTokens;
1660
+ totalOut += turnUsage.outputTokens;
1661
+ totalCacheRead += turnUsage.cacheReadTokens;
1662
+ }
1663
+ allText += interactionText(interaction);
1664
+ const fnCalls = functionCallSteps(interaction);
1665
+ if (fnCalls.length === 0) break;
1666
+ const results = [];
1667
+ for (const fc of fnCalls) {
1668
+ const tool = tools.find((t) => t.name === fc.name);
1669
+ if (!tool) {
1670
+ results.push({
1671
+ type: "function_result",
1672
+ call_id: fc.id,
1673
+ name: fc.name,
1674
+ result: JSON.stringify({ error: `Tool '${fc.name}' not found` }),
1675
+ is_error: true
1676
+ });
1677
+ continue;
1678
+ }
1679
+ const result = await tool.handler(toolArgsToJsonObject(fc.arguments));
1680
+ results.push({
1681
+ type: "function_result",
1682
+ call_id: fc.id,
1683
+ name: fc.name,
1684
+ result: result.content.map((c) => c.text).join("\n")
1685
+ });
1686
+ }
1687
+ interaction = await this.apiCall(
1688
+ () => client.interactions.create({
1689
+ model: this.resolveModel(model),
1690
+ previous_interaction_id: interaction.id,
1691
+ input: results
1692
+ })
1693
+ );
1694
+ }
1695
+ return {
1696
+ text: allText,
1697
+ inputTokens: totalIn,
1698
+ outputTokens: totalOut,
1699
+ cacheReadTokens: totalCacheRead,
1700
+ turnsUsed
1701
+ };
1702
+ }
1703
+ };
1704
+ var OpenRouterAgent = class extends LLMAgent {
1705
+ provider = "openrouter";
1706
+ // Client-side session history — system prompt sent once, subsequent
1707
+ // callTools() calls append to the conversation without re-sending it.
1708
+ sessionMessages = [];
1709
+ resolveModel(model) {
1710
+ return model.startsWith("openrouter/") ? model.slice("openrouter/".length) : model;
1711
+ }
1712
+ resetSession() {
1713
+ super.resetSession();
1714
+ this.sessionMessages = [];
1715
+ }
1716
+ async createClient() {
1717
+ const { OpenRouterClient } = await import('./client-JBPCXU7W.js');
1718
+ return new OpenRouterClient({
1719
+ apiKey: this.routeOverride?.apiKey ?? process.env.OPENROUTER_API_KEY
1720
+ });
1721
+ }
1722
+ async callText(model, systemPrompt, userPrompt, maxTokens, temperature) {
1723
+ const client = await this.getClient();
1724
+ const resolved = this.resolveModel(model);
1725
+ const response = await this.apiCall(
1726
+ () => client.chatCompletion({
1727
+ model: resolved,
1728
+ messages: [
1729
+ { role: "system", content: systemPrompt },
1730
+ { role: "user", content: userPrompt }
1731
+ ],
1732
+ max_tokens: maxTokens ?? 4096,
1733
+ ...temperature !== void 0 && { temperature }
1734
+ })
1735
+ );
1736
+ const text = response.choices[0]?.message.content ?? "";
1737
+ return {
1738
+ text,
1739
+ inputTokens: response.usage.prompt_tokens,
1740
+ outputTokens: response.usage.completion_tokens,
1741
+ sampling: { temperature: temperature ?? "provider-default" }
1742
+ };
1743
+ }
1744
+ async callTools(model, systemPrompt, userPrompt, tools, toolChoice, _scopedTools) {
1745
+ const client = await this.getClient();
1746
+ const resolved = this.resolveModel(model);
1747
+ const orTools = tools.map((t) => ({
1748
+ type: "function",
1749
+ function: { name: t.name, description: t.description, parameters: t.parameters }
1750
+ }));
1751
+ if (this.sessionMessages.length === 0) {
1752
+ this.sessionMessages.push({ role: "system", content: systemPrompt });
1753
+ }
1754
+ this.sessionMessages.push({ role: "user", content: userPrompt });
1755
+ const requested = toolChoice ?? "auto";
1756
+ const appliedToolChoice = requested === "required" && anthropicRejectsForcedToolChoice(resolved) ? "auto" : requested;
1757
+ if (appliedToolChoice !== requested) {
1758
+ console.warn(
1759
+ `[openrouter] callTools: ${resolved} would reject a forced tool_choice (the API answers 400); caller's 'required' downgraded to 'auto' before sending`
1760
+ );
1761
+ }
1762
+ const response = await this.apiCall(
1763
+ () => client.chatCompletion({
1764
+ model: resolved,
1765
+ messages: this.sessionMessages,
1766
+ tools: orTools,
1767
+ tool_choice: appliedToolChoice
1768
+ })
1769
+ );
1770
+ const choice = response.choices[0];
1771
+ if (choice?.message) {
1772
+ this.sessionMessages.push({
1773
+ role: "assistant",
1774
+ content: choice.message.content,
1775
+ tool_calls: choice.message.tool_calls
1776
+ });
1777
+ }
1778
+ const toolCalls = (choice?.message.tool_calls ?? []).map((tc) => ({
1779
+ id: tc.id,
1780
+ name: tc.function.name,
1781
+ input: JSON.parse(tc.function.arguments || "{}")
1782
+ }));
1783
+ return {
1784
+ toolCalls,
1785
+ inputTokens: response.usage.prompt_tokens,
1786
+ outputTokens: response.usage.completion_tokens,
1787
+ appliedToolChoice
1788
+ };
1789
+ }
1790
+ async sendToolResult(results) {
1791
+ for (const r of results) {
1792
+ this.sessionMessages.push({
1793
+ role: "tool",
1794
+ tool_call_id: r.callId,
1795
+ content: r.result
1796
+ });
1797
+ }
1798
+ }
1799
+ async callWithTools(model, systemPrompt, userPrompt, tools, maxTurns) {
1800
+ const client = await this.getClient();
1801
+ const resolved = this.resolveModel(model);
1802
+ const orTools = tools.map((t) => ({
1803
+ type: "function",
1804
+ function: { name: t.name, description: t.description, parameters: t.parameters }
1805
+ }));
1806
+ const messages = [
1807
+ { role: "system", content: systemPrompt },
1808
+ { role: "user", content: userPrompt }
1809
+ ];
1810
+ let totalInput = 0;
1811
+ let totalOutput = 0;
1812
+ let turnsUsed = 0;
1813
+ let finalText = "";
1814
+ for (let turn = 0; turn < maxTurns; turn++) {
1815
+ turnsUsed = turn + 1;
1816
+ const response = await this.apiCall(
1817
+ () => client.chatCompletion({
1818
+ model: resolved,
1819
+ messages,
1820
+ tools: orTools,
1821
+ tool_choice: "auto"
1822
+ })
1823
+ );
1824
+ totalInput += response.usage.prompt_tokens;
1825
+ totalOutput += response.usage.completion_tokens;
1826
+ const choice = response.choices[0];
1827
+ if (!choice) break;
1828
+ if (choice.message.content) {
1829
+ finalText = choice.message.content;
1830
+ }
1831
+ const toolCalls = choice.message.tool_calls;
1832
+ if (!toolCalls || toolCalls.length === 0) break;
1833
+ messages.push({
1834
+ role: "assistant",
1835
+ content: choice.message.content,
1836
+ tool_calls: toolCalls
1837
+ });
1838
+ for (const tc of toolCalls) {
1839
+ const toolDef = tools.find((t) => t.name === tc.function.name);
1840
+ let resultText;
1841
+ if (!toolDef) {
1842
+ resultText = JSON.stringify({ error: `Tool '${tc.function.name}' not found` });
1843
+ } else {
1844
+ const args = JSON.parse(tc.function.arguments || "{}");
1845
+ const result = await toolDef.handler(args);
1846
+ resultText = result.content.map((c) => c.text).join("\n");
1847
+ }
1848
+ messages.push({ role: "tool", tool_call_id: tc.id, content: resultText });
1849
+ }
1850
+ }
1851
+ return { text: finalText, inputTokens: totalInput, outputTokens: totalOutput, turnsUsed };
1852
+ }
1853
+ };
1854
+ function createAgent(providerOrConfig) {
1855
+ const provider = typeof providerOrConfig === "string" ? providerOrConfig : providerOrConfig.provider;
1856
+ const routeOverride = typeof providerOrConfig === "string" ? void 0 : providerOrConfig.routeOverride;
1857
+ const onRetry = typeof providerOrConfig === "string" ? void 0 : providerOrConfig.onRetry;
1858
+ if (routeOverride?.claudeCodeLogin === true) {
1859
+ if (provider !== "anthropic") {
1860
+ throw new Error(
1861
+ `${provider}: the claude-code-login credential authenticates the Claude Code binary only \u2014 pick an anthropic route or supply a ${provider} API key`
1862
+ );
1863
+ }
1864
+ return new ClaudeCodeLoginAgent(routeOverride, onRetry);
1865
+ }
1866
+ switch (provider) {
1867
+ case "anthropic":
1868
+ return new AnthropicAgent(routeOverride, onRetry);
1869
+ case "openai":
1870
+ return new OpenAIAgent(routeOverride, onRetry);
1871
+ case "google":
1872
+ return new GoogleAgent(routeOverride, onRetry);
1873
+ case "openrouter":
1874
+ return new OpenRouterAgent(routeOverride, onRetry);
1875
+ }
1876
+ }
1877
+ function createVisionAgent(config) {
1878
+ if (config.routeOverride?.claudeCodeLogin === true) {
1879
+ if (config.provider !== "anthropic") {
1880
+ throw new Error(
1881
+ `${config.provider}: the claude-code-login credential authenticates the Claude Code binary only \u2014 pick an anthropic route or supply a ${config.provider} API key`
1882
+ );
1883
+ }
1884
+ return new ClaudeCodeLoginAgent(config.routeOverride, config.onRetry);
1885
+ }
1886
+ switch (config.provider) {
1887
+ case "anthropic":
1888
+ return new AnthropicAgent(config.routeOverride, config.onRetry);
1889
+ case "google":
1890
+ return new GoogleAgent(config.routeOverride, config.onRetry);
1891
+ default: {
1892
+ const unreachable = config.provider;
1893
+ throw new Error(`createVisionAgent: provider '${String(unreachable)}' has no vision path (anthropic, google)`);
1894
+ }
1895
+ }
1896
+ }
1897
+ async function callLLM(config, systemPrompt, userPrompt, maxTokens) {
1898
+ const start = Date.now();
1899
+ const agent = createAgent(config);
1900
+ const result = await agent.callText(
1901
+ config.model,
1902
+ systemPrompt,
1903
+ userPrompt,
1904
+ maxTokens,
1905
+ config.temperature
1906
+ );
1907
+ console.log(
1908
+ `[llm-router] ${config.provider}/${config.model} | ${Date.now() - start}ms | in=${result.inputTokens} out=${result.outputTokens}`
1909
+ );
1910
+ return result;
1911
+ }
1912
+ async function preloadProviderSdks() {
1913
+ const start = Date.now();
1914
+ const attempts = [
1915
+ ["@anthropic-ai/claude-agent-sdk", () => import('@anthropic-ai/claude-agent-sdk')],
1916
+ ["openai", () => import('openai')],
1917
+ ["@google/genai", () => import('@google/genai')],
1918
+ ["@anthropic-ai/bedrock-sdk", () => import('@anthropic-ai/bedrock-sdk')]
1919
+ ];
1920
+ const settled = await Promise.allSettled(attempts.map(([, load]) => load()));
1921
+ const loaded = [];
1922
+ const missing = [];
1923
+ settled.forEach((r, i) => {
1924
+ const specifier = attempts[i]?.[0] ?? "";
1925
+ if (r.status === "fulfilled") loaded.push(specifier);
1926
+ else missing.push({ specifier, reason: r.reason instanceof Error ? r.reason.message : String(r.reason) });
1927
+ });
1928
+ return { ms: Date.now() - start, loaded, missing };
1929
+ }
1930
+
1931
+ export { applyRouteToEnv, callLLM, createAgent, createVisionAgent, preloadProviderSdks, resolveRoute };
1932
+ //# sourceMappingURL=chunk-O2YC6XDC.js.map
1933
+ //# sourceMappingURL=chunk-O2YC6XDC.js.map