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