@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
@@ -21,6 +21,20 @@ import type { DataContract, JsonObject, PropsSpec } from "@ggui-ai/protocol";
21
21
  import { HOOK_NAME_RE, listContractGadgets } from "@ggui-ai/protocol";
22
22
  import { createProbe, createProbeWireConfig, type ActionFiredEvent, type Probe } from "./probe.js";
23
23
  import { loadComponent } from "./load-component.js";
24
+ import { ASSET_SHIM_FOR_SPECIFIER } from "@ggui-ai/design/rendering";
25
+
26
+ /**
27
+ * ggui#1934 — the design specifiers production's import rewrite serves, each paired with the module
28
+ * that backs it here. `templates` is backed by the barrel: the renderer's templates shim serves every
29
+ * layer (compositions, then primitives and components). `app-components` is the operator's own registry, never a design-package module, so it is not
30
+ * pre-resolved; a card importing it fails with the sandbox's "not available" reason.
31
+ */
32
+ export function sandboxDesignModules(): ReadonlyArray<readonly [specifier: string, moduleId: string]> {
33
+ return Object.keys(ASSET_SHIM_FOR_SPECIFIER)
34
+ .filter((id) => id === "@ggui-ai/design" || id.startsWith("@ggui-ai/design/"))
35
+ .filter((id) => id !== "@ggui-ai/design/app-components")
36
+ .map((id) => [id, id === "@ggui-ai/design/templates" ? "@ggui-ai/design" : id] as const);
37
+ }
24
38
  import { findWiring, type WiringDetection } from "./find-wiring.js";
25
39
  import { installProductionActShim } from "./production-act-shim.js";
26
40
  // All happy-dom / RTL / user-event narrowing flows through the typed
@@ -290,27 +304,12 @@ export async function runRenderCheckInProcess(
290
304
  "@ggui-ai/wire": Wire,
291
305
  };
292
306
 
293
- // Pre-load the @ggui-ai/design specifiers the boilerplate may import.
294
- // Post-D1, generated code imports the single bare `@ggui-ai/design`
295
- // barrel — that MUST be pre-resolved here: the package is ESM-only
296
- // with no `require` export condition, so `loadComponent`'s
297
- // `createRequire` fallback cannot load it (it would crash:fail the
298
- // render check). The subpaths stay for any legacy/first-party code.
299
- // Per-specifier failures are swallowed — the sandbox throws its own
300
- // clear error if the component actually needs a missing one.
301
- const designSpecifiers = [
302
- "@ggui-ai/design",
303
- "@ggui-ai/design/primitives",
304
- "@ggui-ai/design/components",
305
- "@ggui-ai/design/compositions",
306
- "@ggui-ai/design/interact",
307
- ];
308
- for (const id of designSpecifiers) {
309
- try {
310
- moduleResolutions[id] = await import(id);
311
- } catch {
312
- /* skip — sandbox will report a clear error if the component imports it */
313
- }
307
+ // Pre-load every @ggui-ai/design specifier production serves (ggui#1934). The package is ESM-only
308
+ // with no `require` export condition, so `loadComponent`'s `createRequire` fallback can load none
309
+ // of them: a specifier missing here fails the probe while the served card renders. The list is
310
+ // the renderer's own map, so a subpath production starts serving is served here too.
311
+ for (const [id, moduleId] of sandboxDesignModules()) {
312
+ moduleResolutions[id] = await import(moduleId);
314
313
  }
315
314
 
316
315
  // Pre-load `@ggui-ai/gadgets` for components that declare
@@ -0,0 +1,93 @@
1
+ /**
2
+ * The one runtime load of `@anthropic-ai/claude-agent-sdk` (ggui#1951).
3
+ *
4
+ * The SDK is an OPTIONAL peer of this package, not a dependency. Its
5
+ * per-platform native binary is about 300 MB, and only the Claude Code
6
+ * login route (`routeOverride.claudeCodeLogin`, `ggui serve
7
+ * --local-cli-login`), the `sdk`-mode Claude adapter and the
8
+ * `./evaluation` loop run it. A plain install therefore does not carry
9
+ * it, and every one of those paths loads it through
10
+ * {@link loadClaudeAgentSdk}: a missing SDK fails with one
11
+ * {@link ClaudeAgentSdkMissingError} that names the install line for
12
+ * each way of installing ggui, and every other failure keeps its own
13
+ * shape.
14
+ *
15
+ * `preloadProviderSdks` (in `./llm-router.ts`) imports the SDK directly
16
+ * on purpose: it reports a missing SDK in its `missing` list and never
17
+ * throws.
18
+ */
19
+
20
+ /** The package specifier, as it is imported and installed. */
21
+ export const CLAUDE_AGENT_SDK_SPECIFIER = '@anthropic-ai/claude-agent-sdk';
22
+
23
+ /**
24
+ * The exact version this package's `peerDependencies` names. The install
25
+ * lines in {@link ClaudeAgentSdkMissingError} use it; a test pins it to
26
+ * `package.json`.
27
+ */
28
+ export const CLAUDE_AGENT_SDK_VERSION = '0.3.229';
29
+
30
+ export type ClaudeAgentSdk = typeof import('@anthropic-ai/claude-agent-sdk');
31
+
32
+ /** The SDK is not installed where this package can import it. */
33
+ export class ClaudeAgentSdkMissingError extends Error {
34
+ constructor(cause: Error) {
35
+ const pin = `${CLAUDE_AGENT_SDK_SPECIFIER}@${CLAUDE_AGENT_SDK_VERSION}`;
36
+ super(
37
+ `${CLAUDE_AGENT_SDK_SPECIFIER} is not installed. It is an optional peer of @ggui-ai/ui-gen, ` +
38
+ `needed only to generate with the Claude Code login (and by the Agent SDK adapter and evaluation loop). ` +
39
+ `Install it beside ggui:\n` +
40
+ ` npm install ${pin} (ggui installed in a project)\n` +
41
+ ` npm install -g ${pin} (ggui installed with -g)\n` +
42
+ ` npx -p @ggui-ai/cli -p ${pin} ggui serve --local-cli-login (ggui run with npx)`,
43
+ { cause },
44
+ );
45
+ this.name = 'ClaudeAgentSdkMissingError';
46
+ }
47
+ }
48
+
49
+ function isMissingSdk(err: unknown): err is Error {
50
+ if (!(err instanceof Error)) return false;
51
+ const code: unknown = Reflect.get(err, 'code');
52
+ if (code !== 'ERR_MODULE_NOT_FOUND' && code !== 'MODULE_NOT_FOUND') return false;
53
+ // A package the SDK itself imports can be missing too; that failure names
54
+ // its own package and is not this one.
55
+ return err.message.includes(`'${CLAUDE_AGENT_SDK_SPECIFIER}'`);
56
+ }
57
+
58
+ function missingOrRethrow(err: unknown): never {
59
+ if (isMissingSdk(err)) throw new ClaudeAgentSdkMissingError(err);
60
+ throw err;
61
+ }
62
+
63
+ /**
64
+ * Import the Claude Agent SDK. A missing SDK rejects with
65
+ * {@link ClaudeAgentSdkMissingError}; any other failure rejects unchanged.
66
+ * `importer` is for tests. Package-internal: its return type names the
67
+ * SDK's types, which a consumer without the SDK cannot resolve.
68
+ */
69
+ export async function loadClaudeAgentSdk(
70
+ importer: () => Promise<ClaudeAgentSdk> = () => import('@anthropic-ai/claude-agent-sdk'),
71
+ ): Promise<ClaudeAgentSdk> {
72
+ try {
73
+ return await importer();
74
+ } catch (err) {
75
+ return missingOrRethrow(err);
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Check that the Claude Agent SDK loads, with the same errors as
81
+ * {@link loadClaudeAgentSdk}, for a host that wants to say so at boot
82
+ * (`ggui serve --local-cli-login`). Its signature names none of the SDK's
83
+ * types, so the package's main entry can export it. `importer` is for tests.
84
+ */
85
+ export async function ensureClaudeAgentSdk(
86
+ importer: () => Promise<unknown> = () => import('@anthropic-ai/claude-agent-sdk'),
87
+ ): Promise<void> {
88
+ try {
89
+ await importer();
90
+ } catch (err) {
91
+ missingOrRethrow(err);
92
+ }
93
+ }
@@ -39,7 +39,7 @@ import type {
39
39
  } from "@anthropic-ai/claude-agent-sdk";
40
40
  import type { MessageParam } from "@anthropic-ai/sdk/resources";
41
41
  import { isRecord, type JsonObject, type JsonValue } from "@ggui-ai/protocol";
42
- import { z } from "zod";
42
+ import * as z from "zod";
43
43
  import type { LLMToolDef } from "../llm.js";
44
44
  import {
45
45
  claudeCodeLoginQueryOptions,
@@ -301,6 +301,7 @@ export function createGenerateTaskRunner(input: CreateGenerateRunnerInput): Task
301
301
  // place (fingerprint + cooldown + forceEscape +
302
302
  // scopedEscapeUsedCount).
303
303
  dupeBreak: session.dupeBreak,
304
+ ...(params.onCodingTurnPrompt !== undefined ? { onPrompt: params.onCodingTurnPrompt } : {}),
304
305
  },
305
306
  {
306
307
  turnsUsed: telemetry.turnsUsed,
@@ -189,6 +189,8 @@ export interface CodingTurnContext {
189
189
  * `breakDuplicatePatch` policy flag gates whether this turn reads it.
190
190
  */
191
191
  readonly dupeBreak?: import("./dupe-break.js").DupeBreakState;
192
+ /** Told the system prompt and user message this turn hands the coding agent, just before the call. */
193
+ readonly onPrompt?: (sent: import("../runtime.js").CodingTurnPrompt) => void;
192
194
  }
193
195
 
194
196
  /**
@@ -544,6 +546,7 @@ ${closingInstruction}`;
544
546
  planFirstTurn,
545
547
  codeFormat,
546
548
  );
549
+ ctx.onPrompt?.({ turn: turnsUsed, systemPrompt, userPrompt });
547
550
  const llmStart = Date.now();
548
551
  const response = await codingAgent.callTools(
549
552
  codingModel,
@@ -1462,6 +1462,15 @@ export async function runEvalRound(
1462
1462
  console.log(
1463
1463
  `[simple] eval round ${evalRoundsUsed}: ${allActionable.length} issues (${blocking.length} fail, ${warns.length} warn) → feeding back to coding loop`,
1464
1464
  );
1465
+ // ggui#1981 — one line per deterministic (tier-0) finding on a feedback round, and whether it took one of the
1466
+ // MAX_FEEDBACK_ISSUES slots, so a bench read can count a warn that displaced a repair instead of inferring it.
1467
+ for (const issue of evalResult.issues) {
1468
+ if (issue.tier !== 0 || (issue.result !== "fail" && issue.result !== "warn")) continue;
1469
+ console.log(
1470
+ `[simple] eval round ${evalRoundsUsed}: tier0 ${issue.result} ${issue.subcategory ?? issue.category} ` +
1471
+ `delivered=${allActionable.includes(issue)}`,
1472
+ );
1473
+ }
1465
1474
  // ggui#1542 — WHY this round continued, for a caller that set `actOn`: the manipulation check of an experiment on
1466
1475
  // the setting reads this line. `visual majors acted` counts the judge's majors only (fit's are never acted on).
1467
1476
  if (visualEvaluation?.actOn !== undefined) {
@@ -36,6 +36,7 @@ import { createAnthropicClient } from '../adapters/claude/client.js';
36
36
  import { getBedrockModelId } from '../adapters/provider-router.js';
37
37
  import { toolArgsToJsonObject } from '../adapters/tool-bridge.js';
38
38
  import { splitCacheInclusiveUsage, splitOpenAiUsage } from '../adapters/openai/tokens.js';
39
+ import { loadClaudeAgentSdk } from './claude-agent-sdk-loader.js';
39
40
  import {
40
41
  emitLlmTraceEvent,
41
42
  newLlmTraceId,
@@ -252,6 +253,8 @@ export interface LLMWithToolsResponse {
252
253
  turnsUsed: number;
253
254
  /** Prompt-cache reads summed over the loop's turns, when the provider reports them (absent = unreported, never zero). */
254
255
  cacheReadTokens?: number;
256
+ /** ggui#1957 — prompt-cache writes summed over the loop's turns, when the provider reports them (absent = unreported). */
257
+ cacheCreationTokens?: number;
255
258
  }
256
259
 
257
260
  export interface LLMToolCall {
@@ -1169,7 +1172,7 @@ export class ClaudeCodeLoginAgent extends LLMAgent implements VisionAgent {
1169
1172
  }
1170
1173
 
1171
1174
  protected async createClient(): Promise<LoginSdk> {
1172
- const { query, tool, createSdkMcpServer } = await import('@anthropic-ai/claude-agent-sdk');
1175
+ const { query, tool, createSdkMcpServer } = await loadClaudeAgentSdk();
1173
1176
  return { query, tool, createSdkMcpServer };
1174
1177
  }
1175
1178
 
@@ -1428,11 +1431,14 @@ export class OpenAIAgent extends LLMAgent {
1428
1431
  // ggui#1186 (second site): `input_tokens` INCLUDES the cached prefix on the
1429
1432
  // Responses API — report the NON-cached input like every other provider.
1430
1433
  // The text response carries no cache field, so the cached count is
1431
- // dropped here rather than double-counted.
1434
+ // dropped here rather than double-counted. Cache writes (ggui#1957) stay
1435
+ // inside `inputTokens` on this path for the same reason: with no
1436
+ // cache-write term to carry them, subtracting them would drop them.
1432
1437
  const textUsage = splitOpenAiUsage(
1433
1438
  response.usage?.input_tokens ?? 0,
1434
1439
  response.usage?.output_tokens ?? 0,
1435
1440
  response.usage?.input_tokens_details?.cached_tokens ?? 0,
1441
+ 0,
1436
1442
  );
1437
1443
  return {
1438
1444
  text,
@@ -1509,6 +1515,7 @@ export class OpenAIAgent extends LLMAgent {
1509
1515
  response.usage?.input_tokens ?? 0,
1510
1516
  response.usage?.output_tokens ?? 0,
1511
1517
  response.usage?.input_tokens_details?.cached_tokens ?? 0,
1518
+ response.usage?.input_tokens_details?.cache_write_tokens ?? 0,
1512
1519
  );
1513
1520
  return {
1514
1521
  toolCalls: functionCalls.map((fc) => ({
@@ -1519,6 +1526,7 @@ export class OpenAIAgent extends LLMAgent {
1519
1526
  inputTokens: toolsUsage.tokens.input,
1520
1527
  outputTokens: toolsUsage.tokens.output,
1521
1528
  cacheReadTokens: toolsUsage.cacheReadTokens,
1529
+ cacheCreationTokens: toolsUsage.cacheCreationTokens,
1522
1530
  };
1523
1531
  }
1524
1532
 
@@ -1558,6 +1566,7 @@ export class OpenAIAgent extends LLMAgent {
1558
1566
  let totalIn = 0;
1559
1567
  let totalOut = 0;
1560
1568
  let totalCacheRead = 0;
1569
+ let totalCacheWrite = 0;
1561
1570
  let allText = '';
1562
1571
 
1563
1572
  for (let turn = 0; turn < maxTurns; turn++) {
@@ -1574,10 +1583,12 @@ export class OpenAIAgent extends LLMAgent {
1574
1583
  response.usage?.input_tokens ?? 0,
1575
1584
  response.usage?.output_tokens ?? 0,
1576
1585
  response.usage?.input_tokens_details?.cached_tokens ?? 0,
1586
+ response.usage?.input_tokens_details?.cache_write_tokens ?? 0,
1577
1587
  );
1578
1588
  totalIn += turnUsage.tokens.input;
1579
1589
  totalOut += turnUsage.tokens.output;
1580
1590
  totalCacheRead += turnUsage.cacheReadTokens;
1591
+ totalCacheWrite += turnUsage.cacheCreationTokens;
1581
1592
 
1582
1593
  const functionCalls = response.output.filter(
1583
1594
  (o): o is ResponseFunctionToolCall => o.type === 'function_call',
@@ -1615,6 +1626,7 @@ export class OpenAIAgent extends LLMAgent {
1615
1626
  inputTokens: totalIn,
1616
1627
  outputTokens: totalOut,
1617
1628
  cacheReadTokens: totalCacheRead,
1629
+ cacheCreationTokens: totalCacheWrite,
1618
1630
  turnsUsed: input.length,
1619
1631
  };
1620
1632
  }
@@ -2435,3 +2447,39 @@ export async function callLLMWithTools(
2435
2447
  return result;
2436
2448
  }
2437
2449
 
2450
+
2451
+ /** What {@link preloadProviderSdks} loaded, and what it could not, by specifier. */
2452
+ export interface ProviderSdkPreload {
2453
+ readonly ms: number;
2454
+ readonly loaded: readonly string[];
2455
+ readonly missing: ReadonlyArray<{ readonly specifier: string; readonly reason: string }>;
2456
+ }
2457
+
2458
+ /**
2459
+ * ggui#1769 — load the provider SDKs this router imports lazily, once, before a long-lived host takes traffic.
2460
+ *
2461
+ * Each agent imports its SDK on first use. Cold, `@anthropic-ai/claude-agent-sdk` alone compiles for ~0.9 s
2462
+ * synchronously, so a host's first generation stalls the event loop for that long mid-request. A host awaits
2463
+ * this at boot instead; afterwards every `import()` below resolves from the module cache.
2464
+ *
2465
+ * Never rejects: an SDK that is not installed (an optional provider a deployment does not use) is reported in
2466
+ * `missing` with its reason, and its agent fails on first use exactly as it would without the preload.
2467
+ */
2468
+ export async function preloadProviderSdks(): Promise<ProviderSdkPreload> {
2469
+ const start = Date.now();
2470
+ const attempts: ReadonlyArray<readonly [string, () => Promise<object>]> = [
2471
+ ['@anthropic-ai/claude-agent-sdk', () => import('@anthropic-ai/claude-agent-sdk')],
2472
+ ['openai', () => import('openai')],
2473
+ ['@google/genai', () => import('@google/genai')],
2474
+ ['@anthropic-ai/bedrock-sdk', () => import('@anthropic-ai/bedrock-sdk')],
2475
+ ];
2476
+ const settled = await Promise.allSettled(attempts.map(([, load]) => load()));
2477
+ const loaded: string[] = [];
2478
+ const missing: { specifier: string; reason: string }[] = [];
2479
+ settled.forEach((r, i) => {
2480
+ const specifier = attempts[i]?.[0] ?? '';
2481
+ if (r.status === 'fulfilled') loaded.push(specifier);
2482
+ else missing.push({ specifier, reason: r.reason instanceof Error ? r.reason.message : String(r.reason) });
2483
+ });
2484
+ return { ms: Date.now() - start, loaded, missing };
2485
+ }
@@ -66,6 +66,18 @@ export interface AgentSpec {
66
66
  onRetry?: AgentConfig["onRetry"];
67
67
  }
68
68
 
69
+ /**
70
+ * One coding turn's prompt, exactly as handed to the coding agent's tool call: the system prompt (after every
71
+ * policy swap, e.g. the condensed component reference) and the turn's user message. Lets a caller audit the
72
+ * prompt a generation actually sent rather than the templates it was built from.
73
+ */
74
+ export interface CodingTurnPrompt {
75
+ /** 1-indexed coding turn. */
76
+ readonly turn: number;
77
+ readonly systemPrompt: string;
78
+ readonly userPrompt: string;
79
+ }
80
+
69
81
  export interface SingleComponentParams {
70
82
  userPrompt: string;
71
83
  contract?: DataContract;
@@ -119,6 +131,11 @@ export interface SingleComponentParams {
119
131
  actOn?: "critical" | "major";
120
132
  };
121
133
  onProgress?: (event: unknown) => void;
134
+ /**
135
+ * Observer of what each coding turn sends the model — see {@link CodingTurnPrompt}. Read-only: the generation is
136
+ * byte-identical with or without it. Absent = nothing reported.
137
+ */
138
+ onCodingTurnPrompt?: (sent: CodingTurnPrompt) => void;
122
139
  onInitialResult?: (result: {
123
140
  componentCode: string;
124
141
  sourceCode?: string;
package/src/index.ts CHANGED
@@ -22,6 +22,15 @@ export {
22
22
  export type { CreateUiGeneratorOptions } from './create-ui-generator.js';
23
23
  export type { RuntimeRenderProbeConfig } from './harness/check/runtime-render/adapter.js';
24
24
  export type { ProviderRetryInfo } from './harness/llm-router.js';
25
+ export { preloadProviderSdks, type ProviderSdkPreload } from './harness/llm-router.js';
26
+ // ggui#1951: the Agent SDK is an optional peer. Only what names none of
27
+ // its types is exported here, so this entry type-checks without it.
28
+ export {
29
+ CLAUDE_AGENT_SDK_SPECIFIER,
30
+ CLAUDE_AGENT_SDK_VERSION,
31
+ ClaudeAgentSdkMissingError,
32
+ ensureClaudeAgentSdk,
33
+ } from './harness/claude-agent-sdk-loader.js';
25
34
  export type { GenerationResult, SameExchangeBreak, SameFailureBreak } from './harness/result-types.js';
26
35
  // The build identity a stamp outside this package can name: the same
27
36
  // digests `createUiGenerator` puts on every result's `metadata.build`, for
@@ -0,0 +1,263 @@
1
+ /**
2
+ * The one Anthropic structured call: a single `/v1/messages` request
3
+ * that asks for one tool call and returns that tool's input.
4
+ *
5
+ * Every structured caller that speaks to Anthropic goes through
6
+ * {@link anthropicToolCall}, so the request shape and the failure
7
+ * names live in one place (ggui#1271): the negotiator's LLM seam in
8
+ * `@ggui-ai/mcp-server` (rerank judge + contract synthesizer), the
9
+ * judge the live match probes drive in `@ggui-ai/mcp-server-handlers`,
10
+ * and the negotiator's dev benches.
11
+ *
12
+ * It lives here because every production caller already depends on
13
+ * `@ggui-ai/ui-gen`. The day a production caller lives in a package that
14
+ * does not (`@ggui-ai/negotiator`, `@ggui-ai/protocol`), the home becomes
15
+ * `@ggui-ai/mcp-server-core`, which every one of them reaches.
16
+ *
17
+ * It is a standalone function rather than a `ProviderAdapter`
18
+ * method on purpose. Adapters return a result union and never throw;
19
+ * every structured caller here wants the throw, because its consumers
20
+ * (the rerank judge, the synthesizer) already collapse a throw to
21
+ * their null-decision fallback and carry the message into their
22
+ * reason. A transport failure keeps its own type: a network failure
23
+ * rejects with `fetch`'s `TypeError`, an abort with the signal's
24
+ * `AbortError`, and a 2xx body that is not JSON with `SyntaxError`.
25
+ *
26
+ * Two request shapes, chosen by the shared model rule
27
+ * (`@ggui-ai/protocol` `anthropicRejectsForcedToolChoice`, the same list
28
+ * the generation router reads):
29
+ *
30
+ * - A model that accepts a forced tool gets
31
+ * `tool_choice: {type: 'tool', name}` and the caller's budget.
32
+ * - A model that refuses one (the always-thinking family answers a
33
+ * forced tool with HTTP 400) gets `tool_choice: auto` with at most
34
+ * one call, an instruction naming the tool appended to the system
35
+ * prompt, and {@link ALWAYS_THINKING_HEADROOM_TOKENS} added to the
36
+ * caller's budget. `strict: true` is not sent: the callers' schemas
37
+ * carry keywords strict tool use rejects (`minimum`/`maximum`,
38
+ * map-shaped `additionalProperties`), and every caller validates
39
+ * the input it gets back.
40
+ *
41
+ * No `temperature` is sent: newer tool-use models reject it with
42
+ * HTTP 400, so callers tolerate paraphrase in field values instead.
43
+ */
44
+ import { anthropicRejectsForcedToolChoice, isRecord } from '@ggui-ai/protocol';
45
+
46
+ const DEFAULT_ENDPOINT = 'https://api.anthropic.com/v1/messages';
47
+ const DEFAULT_API_VERSION = '2023-06-01';
48
+ const DEFAULT_ANSWER_BUDGET = 1024;
49
+
50
+ /**
51
+ * Thinking budget added to the caller's ANSWER budget when the model
52
+ * refuses a forced tool. Those models always think, their thinking
53
+ * counts against `max_tokens`, and it comes before the tool call, so a
54
+ * small answer budget (the rerank judge asks for 512) would be spent
55
+ * before the `tool_use` block exists. `max_tokens` is a ceiling, not
56
+ * spend.
57
+ */
58
+ export const ALWAYS_THINKING_HEADROOM_TOKENS = 16_000;
59
+
60
+ /**
61
+ * The one tool the call asks for: the Messages API's own wire shape for a
62
+ * tool (`name`, `description`, `input_schema`), sent as-is under a raw
63
+ * `fetch`. It is not an SDK type and does not stand in for one; this
64
+ * package takes no Anthropic SDK dependency.
65
+ */
66
+ export interface AnthropicTool {
67
+ readonly name: string;
68
+ readonly description: string;
69
+ /** The tool's input JSON Schema, sent verbatim. */
70
+ readonly input_schema: Readonly<Record<string, unknown>>;
71
+ }
72
+
73
+ export interface AnthropicToolCallRequest {
74
+ readonly apiKey: string;
75
+ /** The wire model id, sent verbatim. */
76
+ readonly model: string;
77
+ readonly systemPrompt: string;
78
+ readonly userPrompt: string;
79
+ readonly tool: AnthropicTool;
80
+ /** The answer budget. Absent ⇒ 1024. Headroom is added on top where the model needs it. */
81
+ readonly maxTokens?: number;
82
+ /** Cancels the request. */
83
+ readonly signal?: AbortSignal;
84
+ /** Override for tests and instrumentation. Absent ⇒ the global `fetch` at call time. */
85
+ readonly fetch?: typeof globalThis.fetch;
86
+ /** Override for a proxy endpoint. */
87
+ readonly endpoint?: string;
88
+ }
89
+
90
+ /** The tool input, with the response's token usage when it reported one. */
91
+ export interface AnthropicToolCallResult {
92
+ readonly input: unknown;
93
+ /**
94
+ * `usage.input_tokens` / `usage.output_tokens` when both are numbers;
95
+ * absent otherwise. An unmetered response reports no usage, never
96
+ * zeros. No `cache_control` is sent, so there are no cache counts.
97
+ */
98
+ readonly usage?: { readonly input: number; readonly output: number };
99
+ }
100
+
101
+ /**
102
+ * How a structured call ended without the tool input it asked for:
103
+ *
104
+ * - `max_tokens`: the budget ran out (`stop_reason: max_tokens`) before
105
+ * the tool call.
106
+ * - `no_tool_call`: the model finished without calling the tool
107
+ * (possible under `auto`).
108
+ * - `refusal`: the model declined (`stop_reason: refusal`).
109
+ * - `http`: the API answered non-2xx.
110
+ * - `malformed`: the body was JSON but not a Messages response.
111
+ */
112
+ export const ANTHROPIC_STRUCTURED_CALL_FAILURE_KINDS = [
113
+ 'max_tokens',
114
+ 'no_tool_call',
115
+ 'refusal',
116
+ 'http',
117
+ 'malformed',
118
+ ] as const;
119
+
120
+ export type AnthropicStructuredCallFailureKind =
121
+ (typeof ANTHROPIC_STRUCTURED_CALL_FAILURE_KINDS)[number];
122
+
123
+ /**
124
+ * A structured call that returned no tool input, NAMED by why. The kind
125
+ * is in the message too (`[max_tokens]` …), so a consumer that only
126
+ * surfaces the message (the rerank judge's `reason`) still reports
127
+ * which failure it was.
128
+ */
129
+ export class AnthropicStructuredCallError extends Error {
130
+ readonly kind: AnthropicStructuredCallFailureKind;
131
+ readonly status: number | undefined;
132
+ readonly stopReason: string | undefined;
133
+ constructor(
134
+ kind: AnthropicStructuredCallFailureKind,
135
+ detail: string,
136
+ extra: { readonly status?: number; readonly stopReason?: string } = {},
137
+ ) {
138
+ super(`anthropic structured call [${kind}]: ${detail}`);
139
+ this.name = 'AnthropicStructuredCallError';
140
+ this.kind = kind;
141
+ this.status = extra.status;
142
+ this.stopReason = extra.stopReason;
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Whether `err` is an {@link AnthropicStructuredCallError}, by its shape:
148
+ * an `Error` named `AnthropicStructuredCallError` whose `kind` is one of
149
+ * {@link ANTHROPIC_STRUCTURED_CALL_FAILURE_KINDS}.
150
+ *
151
+ * Prefer this to `instanceof`. An `instanceof` check holds only while one
152
+ * copy of this package is installed: a consumer that resolves a second
153
+ * copy (its own `@ggui-ai/ui-gen` at a different version from the one
154
+ * `@ggui-ai/mcp-server` depends on) gets errors from a class it did not
155
+ * import, and every one of them fails the check.
156
+ */
157
+ export function isAnthropicStructuredCallError(
158
+ err: unknown,
159
+ ): err is AnthropicStructuredCallError {
160
+ if (!(err instanceof Error) || err.name !== 'AnthropicStructuredCallError') {
161
+ return false;
162
+ }
163
+ const kind: unknown = Reflect.get(err, 'kind');
164
+ return ANTHROPIC_STRUCTURED_CALL_FAILURE_KINDS.some((known) => known === kind);
165
+ }
166
+
167
+ /**
168
+ * Send one structured call and return the named tool's input. Every way
169
+ * the turn ends without that input throws an
170
+ * {@link AnthropicStructuredCallError} whose kind says which.
171
+ */
172
+ export async function anthropicToolCall(
173
+ request: AnthropicToolCallRequest,
174
+ ): Promise<AnthropicToolCallResult> {
175
+ const answerBudget = request.maxTokens ?? DEFAULT_ANSWER_BUDGET;
176
+ const refusesForcedTool = anthropicRejectsForcedToolChoice(request.model);
177
+ const maxTokens = refusesForcedTool
178
+ ? answerBudget + ALWAYS_THINKING_HEADROOM_TOKENS
179
+ : answerBudget;
180
+ const toolName = request.tool.name;
181
+ const body = {
182
+ model: request.model,
183
+ max_tokens: maxTokens,
184
+ system: refusesForcedTool
185
+ ? `${request.systemPrompt}\n\nAnswer by calling the \`${toolName}\` tool exactly once. Do not answer in text.`
186
+ : request.systemPrompt,
187
+ messages: [{ role: 'user', content: request.userPrompt }],
188
+ tools: [
189
+ {
190
+ name: toolName,
191
+ description: request.tool.description,
192
+ input_schema: request.tool.input_schema,
193
+ },
194
+ ],
195
+ tool_choice: refusesForcedTool
196
+ ? { type: 'auto', disable_parallel_tool_use: true }
197
+ : { type: 'tool', name: toolName },
198
+ };
199
+ const fetchImpl = request.fetch ?? globalThis.fetch;
200
+ const response = await fetchImpl(request.endpoint ?? DEFAULT_ENDPOINT, {
201
+ method: 'POST',
202
+ headers: {
203
+ 'x-api-key': request.apiKey,
204
+ 'anthropic-version': DEFAULT_API_VERSION,
205
+ 'content-type': 'application/json',
206
+ },
207
+ body: JSON.stringify(body),
208
+ ...(request.signal !== undefined ? { signal: request.signal } : {}),
209
+ });
210
+ if (!response.ok) {
211
+ const text = await response.text();
212
+ throw new AnthropicStructuredCallError(
213
+ 'http',
214
+ `HTTP ${response.status}: ${text.slice(0, 500)}`,
215
+ { status: response.status },
216
+ );
217
+ }
218
+ const json: unknown = await response.json();
219
+ if (!isRecord(json) || !Array.isArray(json['content'])) {
220
+ throw new AnthropicStructuredCallError(
221
+ 'malformed',
222
+ 'response body is not a Messages response',
223
+ );
224
+ }
225
+ const stopReason =
226
+ typeof json['stop_reason'] === 'string' ? json['stop_reason'] : undefined;
227
+ const toolUse = json['content'].find(
228
+ (block: unknown): block is Record<string, unknown> =>
229
+ isRecord(block) && block['type'] === 'tool_use' && block['name'] === toolName,
230
+ );
231
+ if (toolUse !== undefined && toolUse['input'] !== undefined) {
232
+ const usage = readUsage(json['usage']);
233
+ return { input: toolUse['input'], ...(usage !== undefined ? { usage } : {}) };
234
+ }
235
+ if (stopReason === 'max_tokens') {
236
+ throw new AnthropicStructuredCallError(
237
+ 'max_tokens',
238
+ `stop_reason=max_tokens before the "${toolName}" tool_use block (max_tokens=${maxTokens})`,
239
+ { stopReason },
240
+ );
241
+ }
242
+ if (stopReason === 'refusal') {
243
+ throw new AnthropicStructuredCallError(
244
+ 'refusal',
245
+ 'the model declined (stop_reason=refusal)',
246
+ { stopReason },
247
+ );
248
+ }
249
+ throw new AnthropicStructuredCallError(
250
+ 'no_tool_call',
251
+ `the turn ended (stop_reason=${stopReason ?? 'absent'}) without a "${toolName}" tool_use block`,
252
+ stopReason !== undefined ? { stopReason } : {},
253
+ );
254
+ }
255
+
256
+ function readUsage(usage: unknown): AnthropicToolCallResult['usage'] {
257
+ if (!isRecord(usage)) return undefined;
258
+ const input = usage['input_tokens'];
259
+ const output = usage['output_tokens'];
260
+ return typeof input === 'number' && typeof output === 'number'
261
+ ? { input, output }
262
+ : undefined;
263
+ }
@@ -62,6 +62,19 @@ export type { OpenRouterAdapterOptions } from './openrouter.js';
62
62
  export { createBedrockAdapter } from './bedrock.js';
63
63
  export type { BedrockAdapterOptions } from './bedrock.js';
64
64
 
65
+ export {
66
+ ANTHROPIC_STRUCTURED_CALL_FAILURE_KINDS,
67
+ AnthropicStructuredCallError,
68
+ anthropicToolCall,
69
+ isAnthropicStructuredCallError,
70
+ } from './anthropic-tool-call.js';
71
+ export type {
72
+ AnthropicStructuredCallFailureKind,
73
+ AnthropicTool,
74
+ AnthropicToolCallRequest,
75
+ AnthropicToolCallResult,
76
+ } from './anthropic-tool-call.js';
77
+
65
78
  import type { LlmProvider } from '@ggui-ai/mcp-server-core';
66
79
  import type { ProviderAdapter } from '../provider-adapter.js';
67
80
  import { createAnthropicAdapter } from './anthropic.js';