@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.
- package/dist/{adapter-h4_V69LI.d.ts → adapter-DLQ-5COm.d.ts} +1 -1
- package/dist/adapters/base.js +2 -27
- package/dist/adapters/base.js.map +1 -1
- package/dist/adapters/generation-dispatch.d.ts +14 -6
- package/dist/adapters/generation-dispatch.js +40 -19260
- package/dist/adapters/generation-dispatch.js.map +1 -1
- package/dist/adapters/index.d.ts +1 -1
- package/dist/adapters/index.js +25 -3561
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/types.js +2 -9
- package/dist/adapters/types.js.map +1 -1
- package/dist/advanced/index.d.ts +13 -12
- package/dist/advanced/index.js +63 -21954
- package/dist/advanced/index.js.map +1 -1
- package/dist/{bank-Ctdp3FUA.d.ts → bank-LWaiAtaA.d.ts} +1 -1
- package/dist/blueprint-validator.js +6 -2301
- package/dist/blueprint-validator.js.map +1 -1
- package/dist/boilerplate.d.ts +3 -2
- package/dist/boilerplate.js +6 -1867
- package/dist/boilerplate.js.map +1 -1
- package/dist/check/index.d.ts +3 -2
- package/dist/check/index.js +6 -1923
- package/dist/check/index.js.map +1 -1
- package/dist/chunk-33B3BAWS.js +24 -0
- package/dist/chunk-33B3BAWS.js.map +1 -0
- package/dist/chunk-3HSGA6NI.js +591 -0
- package/dist/chunk-3HSGA6NI.js.map +1 -0
- package/dist/chunk-3NNNGNTO.js +1707 -0
- package/dist/chunk-3NNNGNTO.js.map +1 -0
- package/dist/chunk-3T2HMTON.js +79 -0
- package/dist/chunk-3T2HMTON.js.map +1 -0
- package/dist/chunk-3ZSSDV3L.js +123 -0
- package/dist/chunk-3ZSSDV3L.js.map +1 -0
- package/dist/chunk-4MBR5GA3.js +99 -0
- package/dist/chunk-4MBR5GA3.js.map +1 -0
- package/dist/chunk-56NJFD55.js +337 -0
- package/dist/chunk-56NJFD55.js.map +1 -0
- package/dist/chunk-5AGTY555.js +26 -0
- package/dist/chunk-5AGTY555.js.map +1 -0
- package/dist/chunk-5EJM7EGY.js +115 -0
- package/dist/chunk-5EJM7EGY.js.map +1 -0
- package/dist/chunk-5KXCQLLR.js +529 -0
- package/dist/chunk-5KXCQLLR.js.map +1 -0
- package/dist/chunk-5VBXHVEH.js +171 -0
- package/dist/chunk-5VBXHVEH.js.map +1 -0
- package/dist/chunk-6KDDEING.js +515 -0
- package/dist/chunk-6KDDEING.js.map +1 -0
- package/dist/chunk-6VDRWAP6.js +123 -0
- package/dist/chunk-6VDRWAP6.js.map +1 -0
- package/dist/chunk-732IBFEJ.js +2400 -0
- package/dist/chunk-732IBFEJ.js.map +1 -0
- package/dist/chunk-73AURS3I.js +46 -0
- package/dist/chunk-73AURS3I.js.map +1 -0
- package/dist/chunk-77H5SKLX.js +8 -0
- package/dist/chunk-77H5SKLX.js.map +1 -0
- package/dist/chunk-7QE3AHLE.js +35 -0
- package/dist/chunk-7QE3AHLE.js.map +1 -0
- package/dist/chunk-A375NNWN.js +11 -0
- package/dist/chunk-A375NNWN.js.map +1 -0
- package/dist/chunk-AOU33EFS.js +140 -0
- package/dist/chunk-AOU33EFS.js.map +1 -0
- package/dist/chunk-BA4QYA6E.js +461 -0
- package/dist/chunk-BA4QYA6E.js.map +1 -0
- package/dist/chunk-DBP764GL.js +3 -0
- package/dist/chunk-DBP764GL.js.map +1 -0
- package/dist/chunk-DUU3ZJDQ.js +41 -0
- package/dist/chunk-DUU3ZJDQ.js.map +1 -0
- package/dist/chunk-DXKRBXNU.js +30 -0
- package/dist/chunk-DXKRBXNU.js.map +1 -0
- package/dist/chunk-FB7OEEAH.js +1467 -0
- package/dist/chunk-FB7OEEAH.js.map +1 -0
- package/dist/chunk-FLRNMZV2.js +18 -0
- package/dist/chunk-FLRNMZV2.js.map +1 -0
- package/dist/chunk-FWJIMKFZ.js +2032 -0
- package/dist/chunk-FWJIMKFZ.js.map +1 -0
- package/dist/chunk-FXRHWYIQ.js +163 -0
- package/dist/chunk-FXRHWYIQ.js.map +1 -0
- package/dist/chunk-G3PMV62Z.js +33 -0
- package/dist/chunk-G3PMV62Z.js.map +1 -0
- package/dist/chunk-GAJI7Y3P.js +13 -0
- package/dist/chunk-GAJI7Y3P.js.map +1 -0
- package/dist/chunk-GYINWMPR.js +2052 -0
- package/dist/chunk-GYINWMPR.js.map +1 -0
- package/dist/chunk-HD6ABIK7.js +484 -0
- package/dist/chunk-HD6ABIK7.js.map +1 -0
- package/dist/chunk-HDQVHYO6.js +100 -0
- package/dist/chunk-HDQVHYO6.js.map +1 -0
- package/dist/chunk-IHUT3SIW.js +7 -0
- package/dist/chunk-IHUT3SIW.js.map +1 -0
- package/dist/chunk-JDH5EGNZ.js +126 -0
- package/dist/chunk-JDH5EGNZ.js.map +1 -0
- package/dist/chunk-JMD4RGSV.js +37 -0
- package/dist/chunk-JMD4RGSV.js.map +1 -0
- package/dist/chunk-KJWGZL5B.js +33 -0
- package/dist/chunk-KJWGZL5B.js.map +1 -0
- package/dist/chunk-MKMDQFZ7.js +86 -0
- package/dist/chunk-MKMDQFZ7.js.map +1 -0
- package/dist/chunk-NF6JUZWF.js +85 -0
- package/dist/chunk-NF6JUZWF.js.map +1 -0
- package/dist/chunk-O2YC6XDC.js +1933 -0
- package/dist/chunk-O2YC6XDC.js.map +1 -0
- package/dist/chunk-OVLH5ICV.js +29 -0
- package/dist/chunk-OVLH5ICV.js.map +1 -0
- package/dist/chunk-PBOYHGSD.js +106 -0
- package/dist/chunk-PBOYHGSD.js.map +1 -0
- package/dist/chunk-PJFH7QWA.js +81 -0
- package/dist/chunk-PJFH7QWA.js.map +1 -0
- package/dist/chunk-PUH4CXGZ.js +284 -0
- package/dist/chunk-PUH4CXGZ.js.map +1 -0
- package/dist/chunk-QLXXPGTV.js +18 -0
- package/dist/chunk-QLXXPGTV.js.map +1 -0
- package/dist/chunk-R5GIGPW2.js +2845 -0
- package/dist/chunk-R5GIGPW2.js.map +1 -0
- package/dist/chunk-RH4IUSTO.js +83 -0
- package/dist/chunk-RH4IUSTO.js.map +1 -0
- package/dist/chunk-S2GVXKKY.js +153 -0
- package/dist/chunk-S2GVXKKY.js.map +1 -0
- package/dist/chunk-TVHGKFJS.js +58 -0
- package/dist/chunk-TVHGKFJS.js.map +1 -0
- package/dist/chunk-U7GALWLG.js +66 -0
- package/dist/chunk-U7GALWLG.js.map +1 -0
- package/dist/chunk-VTC263LT.js +256 -0
- package/dist/chunk-VTC263LT.js.map +1 -0
- package/dist/chunk-WEOXFEVE.js +739 -0
- package/dist/chunk-WEOXFEVE.js.map +1 -0
- package/dist/chunk-WYHP46LT.js +28 -0
- package/dist/chunk-WYHP46LT.js.map +1 -0
- package/dist/chunk-XGLO5LGG.js +274 -0
- package/dist/chunk-XGLO5LGG.js.map +1 -0
- package/dist/chunk-XRBCRKA2.js +29 -0
- package/dist/chunk-XRBCRKA2.js.map +1 -0
- package/dist/chunk-XW7ADFFV.js +340 -0
- package/dist/chunk-XW7ADFFV.js.map +1 -0
- package/dist/chunk-YIQ42IUX.js +34 -0
- package/dist/chunk-YIQ42IUX.js.map +1 -0
- package/dist/chunk-YJZTUY7B.js +101 -0
- package/dist/chunk-YJZTUY7B.js.map +1 -0
- package/dist/chunk-Z3CXME3K.js +107 -0
- package/dist/chunk-Z3CXME3K.js.map +1 -0
- package/dist/classifier/index.js +2 -527
- package/dist/classifier/index.js.map +1 -1
- package/dist/client-JBPCXU7W.js +4 -0
- package/dist/client-JBPCXU7W.js.map +1 -0
- package/dist/compose.js +3 -367
- package/dist/compose.js.map +1 -1
- package/dist/cost-tracker-HHGQ4FTW.js +67 -0
- package/dist/cost-tracker-HHGQ4FTW.js.map +1 -0
- package/dist/{create-ui-generator-BrZHF0cf.d.ts → create-ui-generator-BY-WeaPi.d.ts} +3 -3
- package/dist/{design-mode-DGWN_Gkm.d.ts → design-mode-XyOQmzE0.d.ts} +4 -3
- package/dist/{dispatch-B07TI30d.d.ts → dispatch-8LzTddKw.d.ts} +3 -2
- package/dist/dist-UALN6ZVB.js +543 -0
- package/dist/dist-UALN6ZVB.js.map +1 -0
- package/dist/dupe-break-X6K7QFIS.js +4 -0
- package/dist/dupe-break-X6K7QFIS.js.map +1 -0
- package/dist/evaluation/axis-checks/index.d.ts +5 -4
- package/dist/evaluation/axis-checks/index.js +6 -1914
- package/dist/evaluation/axis-checks/index.js.map +1 -1
- package/dist/evaluation/axis-checks/registry.d.ts +3 -2
- package/dist/evaluation/axis-checks/registry.js +3 -1844
- package/dist/evaluation/axis-checks/registry.js.map +1 -1
- package/dist/evaluation/index.d.ts +49 -132
- package/dist/evaluation/index.js +302 -6466
- package/dist/evaluation/index.js.map +1 -1
- package/dist/evaluation/types.d.ts +3 -2
- package/dist/evaluation/types.js +2 -5
- package/dist/evaluation/types.js.map +1 -1
- package/dist/fragments/index.js +2 -338
- package/dist/fragments/index.js.map +1 -1
- package/dist/{generate-tSO_shQ0.d.ts → generate-DQ4ci90s.d.ts} +1 -1
- package/dist/harness/check/runtime-render/index.d.ts +35 -15
- package/dist/harness/check/runtime-render/index.js +6 -2390
- package/dist/harness/check/runtime-render/index.js.map +1 -1
- package/dist/harness/check/runtime-render/render-check-worker.js +4 -1739
- package/dist/harness/check/runtime-render/render-check-worker.js.map +1 -1
- package/dist/harness/index.d.ts +3 -2
- package/dist/harness/index.js +23 -5126
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/llm-trace-sink.js +2 -24
- package/dist/harness/llm-trace-sink.js.map +1 -1
- package/dist/harness/prompts.d.ts +2 -2
- package/dist/harness/prompts.js +8 -324
- package/dist/harness/prompts.js.map +1 -1
- package/dist/harness/result-types.d.ts +6 -5
- package/dist/harness/result-types.js +2 -0
- package/dist/harness/result-types.js.map +1 -1
- package/dist/harness/runtime.d.ts +22 -5
- package/dist/harness/runtime.js +10 -2313
- package/dist/harness/runtime.js.map +1 -1
- package/dist/harness/types-public.d.ts +3 -2
- package/dist/harness/types-public.js +16 -2954
- package/dist/harness/types-public.js.map +1 -1
- package/dist/harness/validator-trace-sink.d.ts +3 -2
- package/dist/harness/validator-trace-sink.js +2 -26
- package/dist/harness/validator-trace-sink.js.map +1 -1
- package/dist/hash.js +2 -39
- package/dist/hash.js.map +1 -1
- package/dist/hashline-5OKREIRM.js +4 -0
- package/dist/hashline-5OKREIRM.js.map +1 -0
- package/dist/index.d.ts +50 -11
- package/dist/index.js +57 -22015
- package/dist/index.js.map +1 -1
- package/dist/llm-evaluator-PN3WFCS4.js +653 -0
- package/dist/llm-evaluator-PN3WFCS4.js.map +1 -0
- package/dist/magic-string.es-2NZTC4GZ.js +1307 -0
- package/dist/magic-string.es-2NZTC4GZ.js.map +1 -0
- package/dist/patch.d.ts +3 -2
- package/dist/patch.js +2 -56
- package/dist/patch.js.map +1 -1
- package/dist/policy-VCYKGSAC.js +5 -0
- package/dist/policy-VCYKGSAC.js.map +1 -0
- package/dist/policy.js +2 -35
- package/dist/policy.js.map +1 -1
- package/dist/primitive-index-2OF6F4TZ.js +131 -0
- package/dist/primitive-index-2OF6F4TZ.js.map +1 -0
- package/dist/primitive-slice-WTMBLYFJ.js +235 -0
- package/dist/primitive-slice-WTMBLYFJ.js.map +1 -0
- package/dist/{prompts-BhNUuRoZ.d.ts → prompts-CLZGywMP.d.ts} +1 -1
- package/dist/provider-adapter-contract.js +10407 -14204
- package/dist/provider-adapter-contract.js.map +1 -1
- package/dist/provider-adapter.js +2 -44
- package/dist/provider-adapter.js.map +1 -1
- package/dist/providers/index.d.ts +89 -1
- package/dist/providers/index.js +115 -44
- package/dist/providers/index.js.map +1 -1
- package/dist/raw-C6KBH5I3.js +141 -0
- package/dist/raw-C6KBH5I3.js.map +1 -0
- package/dist/raw-FXSBXQTZ.js +7 -0
- package/dist/raw-FXSBXQTZ.js.map +1 -0
- package/dist/raw-MURDUPGK.js +8 -0
- package/dist/raw-MURDUPGK.js.map +1 -0
- package/dist/raw-T3GW3UVX.js +8 -0
- package/dist/raw-T3GW3UVX.js.map +1 -0
- package/dist/render-check-host-ZNIS6BJ5.js +5 -0
- package/dist/render-check-host-ZNIS6BJ5.js.map +1 -0
- package/dist/sdk-4JNSDNSR.js +6 -0
- package/dist/sdk-4JNSDNSR.js.map +1 -0
- package/dist/sdk-DIVJMHCX.js +7 -0
- package/dist/sdk-DIVJMHCX.js.map +1 -0
- package/dist/sdk-QRJOCRTS.js +7 -0
- package/dist/sdk-QRJOCRTS.js.map +1 -0
- package/dist/tag-balance-CTHCTRJ7.js +123 -0
- package/dist/tag-balance-CTHCTRJ7.js.map +1 -0
- package/dist/tools/get-primitives-ts.d.ts +1 -1
- package/dist/tools/get-primitives-ts.js +3 -1
- package/dist/tools/get-primitives-ts.js.map +1 -1
- package/dist/tools/render-check-worker.js +3 -40
- package/dist/tools/render-check-worker.js.map +1 -1
- package/dist/{types-public-h8IMnYuh.d.ts → types-public-hYlrerZh.d.ts} +35 -2
- package/dist/validation/index.d.ts +2 -2
- package/dist/validation/index.js +3 -498
- package/dist/validation/index.js.map +1 -1
- package/dist/{visual-evaluator-C2strxqD.d.ts → visual-evaluator-KskajxD5.d.ts} +14 -4
- package/dist/visual-evaluator-TGEXYQTP.js +12 -0
- package/dist/visual-evaluator-TGEXYQTP.js.map +1 -0
- package/dist/workflows.d.ts +3 -2
- package/dist/workflows.js +2 -151
- package/dist/workflows.js.map +1 -1
- package/package.json +20 -16
- package/src/adapters/claude/sdk.ts +4 -2
- package/src/adapters/claude/tool-bridge.ts +2 -1
- package/src/adapters/generation-dispatch.ts +9 -0
- package/src/adapters/openai/raw.ts +4 -0
- package/src/adapters/openai/sdk.ts +6 -1
- package/src/adapters/openai/tokens.ts +21 -7
- package/src/adapters/tool-bridge.ts +1 -1
- package/src/adapters/tools.ts +1 -1
- package/src/advanced/feedback.ts +1 -2
- package/src/advanced/generator.ts +4 -3
- package/src/boilerplate/free-design-prompt.ts +7 -0
- package/src/boilerplate/generate.ts +18 -5
- package/src/boilerplate/hard-sections.ts +8 -3
- package/src/boilerplate/react-hooks.ts +56 -0
- package/src/boilerplate/system-prompt.ts +12 -3
- package/src/check/run-tier0.ts +233 -8
- package/src/check/type-checker.ts +128 -16
- package/src/coding-agent/tools.ts +26 -3
- package/src/design-mode.ts +4 -9
- package/src/evaluation/axis-checks/checks/state-merge.ts +2 -4
- package/src/evaluation/axis-checks/checks/state-payload.ts +1 -28
- package/src/evaluation/axis-checks/checks/tint.ts +197 -0
- package/src/evaluation/axis-checks/helpers.ts +121 -0
- package/src/evaluation/axis-checks/registry.ts +2 -0
- package/src/evaluation/criteria/__fixtures__/edge-v2-draw2.json +4629 -0
- package/src/evaluation/criteria/bank.ts +1 -1
- package/src/evaluation/criteria/edge-instruments.ts +14 -0
- package/src/evaluation/criteria/edge-probe-v2.ts +352 -0
- package/src/evaluation/criteria/edge-v2.ts +242 -0
- package/src/evaluation/criteria/resolve.ts +24 -5
- package/src/evaluation/index.ts +3 -5
- package/src/evaluation/mcp-server.ts +0 -50
- package/src/evaluation/types-public.ts +6 -1
- package/src/evaluation/visual-evaluator.ts +161 -30
- package/src/harness/check/runtime-render/index.ts +28 -4
- package/src/harness/check/runtime-render/render-check.ts +20 -21
- package/src/harness/claude-agent-sdk-loader.ts +93 -0
- package/src/harness/claude-code-login-query.ts +1 -1
- package/src/harness/coding/generate-task-runner.ts +1 -0
- package/src/harness/coding/run-coding-turn.ts +3 -0
- package/src/harness/coding/run-eval-round.ts +9 -0
- package/src/harness/llm-router.ts +50 -2
- package/src/harness/runtime.ts +17 -0
- package/src/index.ts +9 -0
- package/src/providers/anthropic-tool-call.ts +263 -0
- package/src/providers/index.ts +13 -0
- package/src/tools/get-primitives-ts.ts +1 -1
- package/src/tools/render-check.ts +4 -4
- package/src/validation/primitives.ts +2 -1
- 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
|
|
294
|
-
//
|
|
295
|
-
//
|
|
296
|
-
//
|
|
297
|
-
|
|
298
|
-
|
|
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
|
|
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
|
|
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
|
+
}
|
package/src/harness/runtime.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/providers/index.ts
CHANGED
|
@@ -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';
|