@ggui-ai/ui-gen 0.20.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/dist/adapters/base.d.ts +7 -0
  2. package/dist/adapters/base.js +1 -0
  3. package/dist/adapters/base.js.map +1 -1
  4. package/dist/adapters/generation-dispatch.d.ts +17 -3
  5. package/dist/adapters/generation-dispatch.js +1078 -3464
  6. package/dist/adapters/generation-dispatch.js.map +1 -1
  7. package/dist/adapters/index.js +60 -3
  8. package/dist/adapters/index.js.map +1 -1
  9. package/dist/advanced/index.d.ts +4 -4
  10. package/dist/advanced/index.js +1346 -3557
  11. package/dist/advanced/index.js.map +1 -1
  12. package/dist/blueprint-validator.js +10 -3
  13. package/dist/blueprint-validator.js.map +1 -1
  14. package/dist/boilerplate.js +2 -2
  15. package/dist/boilerplate.js.map +1 -1
  16. package/dist/check/index.d.ts +1 -1
  17. package/dist/check/index.js.map +1 -1
  18. package/dist/{create-ui-generator-CxPvudbs.d.ts → create-ui-generator-BEkzEtNz.d.ts} +2 -2
  19. package/dist/{dispatch-N8wdNOX-.d.ts → dispatch-DJNpQ7DI.d.ts} +1 -1
  20. package/dist/evaluation/axis-checks/index.d.ts +3 -3
  21. package/dist/evaluation/axis-checks/index.js.map +1 -1
  22. package/dist/evaluation/axis-checks/registry.d.ts +1 -1
  23. package/dist/evaluation/index.d.ts +38 -6
  24. package/dist/evaluation/index.js +670 -3374
  25. package/dist/evaluation/index.js.map +1 -1
  26. package/dist/harness/check/runtime-render/index.d.ts +2 -2
  27. package/dist/harness/check/runtime-render/index.js +10 -3
  28. package/dist/harness/check/runtime-render/index.js.map +1 -1
  29. package/dist/harness/check/runtime-render/render-check-worker.js +5 -3
  30. package/dist/harness/check/runtime-render/render-check-worker.js.map +1 -1
  31. package/dist/harness/index.d.ts +2 -2
  32. package/dist/harness/index.js +587 -30
  33. package/dist/harness/index.js.map +1 -1
  34. package/dist/harness/llm-trace-sink.d.ts +7 -1
  35. package/dist/harness/llm-trace-sink.js.map +1 -1
  36. package/dist/harness/prompts.js +1 -1
  37. package/dist/harness/prompts.js.map +1 -1
  38. package/dist/harness/result-types.d.ts +15 -2
  39. package/dist/harness/result-types.js.map +1 -1
  40. package/dist/harness/runtime.d.ts +11 -2
  41. package/dist/harness/runtime.js +3 -3
  42. package/dist/harness/runtime.js.map +1 -1
  43. package/dist/harness/types-public.d.ts +1 -1
  44. package/dist/harness/types-public.js +2 -2
  45. package/dist/harness/types-public.js.map +1 -1
  46. package/dist/harness/validator-trace-sink.d.ts +1 -1
  47. package/dist/index.d.ts +23 -7
  48. package/dist/index.js +1346 -3557
  49. package/dist/index.js.map +1 -1
  50. package/dist/{llm-router-D7fjBhtr.d.ts → llm-router-DwwUK1bz.d.ts} +16 -3
  51. package/dist/patch.d.ts +1 -1
  52. package/dist/providers/index.js +5 -4
  53. package/dist/providers/index.js.map +1 -1
  54. package/dist/{render-check-WfOVO9kU.d.ts → render-check-C0CqVceF.d.ts} +17 -0
  55. package/dist/{types-public-DKR_h70H.d.ts → types-public-mM_-1Yk3.d.ts} +59 -1
  56. package/dist/validation/index.d.ts +1 -1
  57. package/dist/validation/index.js +1 -1
  58. package/dist/validation/index.js.map +1 -1
  59. package/dist/workflows.d.ts +1 -1
  60. package/package.json +16 -12
  61. package/src/adapters/base.ts +8 -0
  62. package/src/adapters/claude/claude-code-login.ts +128 -0
  63. package/src/adapters/claude/sdk.ts +9 -1
  64. package/src/adapters/generation-dispatch.ts +19 -2
  65. package/src/adapters/openai/tokens.ts +18 -0
  66. package/src/adapters/provider-router.ts +28 -0
  67. package/src/boilerplate/generate.ts +19 -4
  68. package/src/boilerplate/hard-sections.ts +1 -1
  69. package/src/boilerplate/templates/layouts/chat-mobile.tsx.tmpl +7 -3
  70. package/src/boilerplate/templates/layouts/chat-universal.tsx.tmpl +7 -3
  71. package/src/contract-context.ts +1 -1
  72. package/src/create-ui-generator.ts +49 -10
  73. package/src/evaluation/design-identity.ts +16 -5
  74. package/src/evaluation/index.ts +1 -0
  75. package/src/evaluation/llm-evaluator.ts +40 -13
  76. package/src/evaluation/types-public.ts +58 -0
  77. package/src/evaluation/visual-evaluator.ts +240 -30
  78. package/src/generator-build.ts +158 -0
  79. package/src/harness/check/runtime-render/adapter.ts +9 -0
  80. package/src/harness/check/runtime-render/render-check.ts +26 -5
  81. package/src/harness/claude-code-login-query.ts +529 -0
  82. package/src/harness/coding/assemble-result.ts +18 -2
  83. package/src/harness/coding/generate-task-runner.ts +75 -36
  84. package/src/harness/coding/run-coding-turn.ts +9 -0
  85. package/src/harness/coding/run-eval-round.ts +338 -38
  86. package/src/harness/enforced-coding.ts +20 -0
  87. package/src/harness/llm-router.ts +390 -55
  88. package/src/harness/llm-trace-sink.ts +7 -1
  89. package/src/harness/result-types.ts +14 -0
  90. package/src/harness/runtime.ts +6 -0
  91. package/src/index.ts +2 -1
  92. package/src/providers/openai.ts +31 -19
  93. package/src/validation/primitives.ts +1 -1
@@ -1,5 +1,5 @@
1
1
  import { LUCIDE_ICON_NAMES } from '@ggui-ai/design';
2
- import { resolveAppGadgets, listContractGadgets, HOOK_NAME_RE, APP_GENERATION_PROFILE_BOUNDS } from '@ggui-ai/protocol';
2
+ import { resolveAppGadgets, listContractGadgets, HOOK_NAME_RE, anthropicRejectsForcedToolChoice, normalizeAnthropicModelId, isRecord, anthropicRejectsSamplingParams, APP_GENERATION_PROFILE_BOUNDS } from '@ggui-ai/protocol';
3
3
  import { createHash } from 'crypto';
4
4
  import { readFileSync } from 'fs';
5
5
  import { dirname, resolve } from 'path';
@@ -7,7 +7,7 @@ import { fileURLToPath } from 'url';
7
7
  import { consumedTokenManifest } from '@ggui-ai/design/themes';
8
8
  import ts from 'typescript';
9
9
  import Anthropic from '@anthropic-ai/sdk';
10
- import 'zod';
10
+ import { z } from 'zod';
11
11
 
12
12
  var __defProp = Object.defineProperty;
13
13
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -1478,7 +1478,7 @@ function generateBoilerplate(_userPrompt, contract, shellType, screen, composedS
1478
1478
  }
1479
1479
  const toolNote = tool ? ` (label "${label}", nextStep hint \u2192 ${tool})` : "";
1480
1480
  actionTypeAliases.push(
1481
- `/** Action payload: ${desc || label}${toolNote} */
1481
+ `/** Action payload: ${desc || label}${toolNote} \u2014 control copy: "${label}" VERBATIM */
1482
1482
  type ${typeName} = ${tsType};`
1483
1483
  );
1484
1484
  const callSig = tsType === "void" ? "() => void \u2014 fire and forget" : `(data: ${tsType}) => void`;
@@ -1817,7 +1817,7 @@ Rules:
1817
1817
  1. All request-specific data \u2192 props with defaults. City names, tickers, user names, dates, counts.
1818
1818
  2. Layout and styling are universal. Colors, spacing, structure \u2014 these are the reusable part.
1819
1819
  3. Default values come from the current request \u2014 so the component renders correctly standalone.
1820
- 4. Copy comes from the props. NEVER invent display text the contract does not carry \u2014 no eyebrow / kicker / overline (a \`<Text caps>\` label), no helper line, no status text, no tagline. **Do NOT add a caps label above the heading** \u2014 not \`<Text caps>LET'S BEGIN</Text>\`, not \`WELCOME\`, not a section kicker like \`GETTING STARTED\` or \`OVERVIEW\` \u2014 UNLESS a prop supplies that exact text (\`<Text caps>{props.section}</Text>\`). When the request asks for a heading and a line, render the heading and the line and NOTHING above the heading. A heading's fallback is the request's own words, never a slogan of your own. Text the agent cannot change is chatter on every render, and a caps eyebrow you wrote is the most common form of it.
1820
+ 4. Copy comes from the props. NEVER invent display text the contract does not carry \u2014 no eyebrow / kicker / overline (a \`<Text caps>\` label), no helper line, no status text, no tagline. **Do NOT add a caps label above the heading** \u2014 not \`<Text caps>LET'S BEGIN</Text>\`, not \`WELCOME\`, not a section kicker like \`GETTING STARTED\` or \`OVERVIEW\` \u2014 UNLESS a prop supplies that exact text (\`<Text caps>{props.section}</Text>\`). When the request asks for a heading and a line, render the heading and the line and NOTHING above the heading. A heading's fallback is the request's own words, never a slogan of your own. Text the agent cannot change is chatter on every render, and a caps eyebrow you wrote is the most common form of it. An action's control shows the contract's \`label\` VERBATIM \u2014 every character, including \`&\`, \`/\`, \`\u2013\` and quotes \u2014 never paraphrased, shortened or re-cased (\`"Confirm & Schedule"\` is not \`"Confirm Schedule"\`).
1821
1821
  5. Props interface must be typed and exported.`;
1822
1822
  var TERMINAL_ACTIONS = `## Actions the user means once
1823
1823
 
@@ -3227,6 +3227,50 @@ function createAnthropicClient(rawKey) {
3227
3227
  });
3228
3228
  }
3229
3229
 
3230
+ // src/adapters/claude/claude-code-login.ts
3231
+ var LOGIN_CHILD_ENV_NAMES = [
3232
+ "PATH",
3233
+ "HOME",
3234
+ "USER",
3235
+ "LOGNAME",
3236
+ "SHELL",
3237
+ "TMPDIR",
3238
+ "TMP",
3239
+ "TEMP",
3240
+ "TERM",
3241
+ "TZ",
3242
+ "LANG",
3243
+ "CLAUDE_CONFIG_DIR",
3244
+ "CLAUDE_CODE_OAUTH_TOKEN",
3245
+ "XDG_CONFIG_HOME",
3246
+ "XDG_DATA_HOME",
3247
+ "XDG_CACHE_HOME",
3248
+ "XDG_STATE_HOME",
3249
+ "XDG_RUNTIME_DIR",
3250
+ "HTTPS_PROXY",
3251
+ "https_proxy",
3252
+ "HTTP_PROXY",
3253
+ "http_proxy",
3254
+ "NO_PROXY",
3255
+ "no_proxy",
3256
+ "ALL_PROXY",
3257
+ "all_proxy",
3258
+ "NODE_EXTRA_CA_CERTS",
3259
+ "SSL_CERT_FILE",
3260
+ "SSL_CERT_DIR"
3261
+ ];
3262
+ var LOGIN_CHILD_ENV = new Set(LOGIN_CHILD_ENV_NAMES);
3263
+ function loginChildEnv(env) {
3264
+ const out = {};
3265
+ for (const [k, v] of Object.entries(env)) {
3266
+ if (v !== void 0 && (LOGIN_CHILD_ENV.has(k) || k.startsWith("LC_"))) out[k] = v;
3267
+ }
3268
+ return out;
3269
+ }
3270
+ function claudeCodeLoginQueryOptions() {
3271
+ return { tools: [], settingSources: [] };
3272
+ }
3273
+
3230
3274
  // src/adapters/provider-router.ts
3231
3275
  function getBedrockModelId(model) {
3232
3276
  const BEDROCK_MAP = {
@@ -3277,6 +3321,26 @@ function toJsonValue(value, path) {
3277
3321
  );
3278
3322
  }
3279
3323
 
3324
+ // src/adapters/openai/tokens.ts
3325
+ function splitOpenAiUsage(inputTokensInclCached, outputTokens, cachedTokens) {
3326
+ return splitCacheInclusiveUsage(inputTokensInclCached, outputTokens, cachedTokens);
3327
+ }
3328
+ function splitCacheInclusiveUsage(inputTokensInclCached, outputTokens, cachedTokens) {
3329
+ const cacheRead = Math.min(
3330
+ Math.max(cachedTokens, 0),
3331
+ Math.max(inputTokensInclCached, 0)
3332
+ );
3333
+ const nonCachedInput = Math.max(inputTokensInclCached, 0) - cacheRead;
3334
+ return {
3335
+ tokens: {
3336
+ input: nonCachedInput,
3337
+ output: outputTokens,
3338
+ total: nonCachedInput + cacheRead + outputTokens
3339
+ },
3340
+ cacheReadTokens: cacheRead
3341
+ };
3342
+ }
3343
+
3280
3344
  // src/harness/llm-trace-sink.ts
3281
3345
  var activeSink2 = null;
3282
3346
  function setLlmTraceSink(sink) {
@@ -3300,6 +3364,271 @@ function newLlmTraceId() {
3300
3364
  return Date.now().toString(36) + Math.random().toString(36).slice(2, 10);
3301
3365
  }
3302
3366
 
3367
+ // src/adapters/claude/tool-bridge.ts
3368
+ var MCP_SERVER_NAME = "ggui";
3369
+
3370
+ // src/harness/claude-code-login-query.ts
3371
+ var MCP_TOOL_PREFIX = `mcp__${MCP_SERVER_NAME}__`;
3372
+ function stripMcpToolPrefix(name) {
3373
+ return name.startsWith(MCP_TOOL_PREFIX) ? name.slice(MCP_TOOL_PREFIX.length) : name;
3374
+ }
3375
+ var LOGIN_API_KEY_SOURCE = "none";
3376
+ var ClaudeCodeLoginAuthError = class extends Error {
3377
+ apiKeySource;
3378
+ constructor(apiKeySource) {
3379
+ super(
3380
+ `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`
3381
+ );
3382
+ this.name = "ClaudeCodeLoginAuthError";
3383
+ this.apiKeySource = apiKeySource;
3384
+ }
3385
+ };
3386
+ var ClaudeCodeLoginResultError = class extends Error {
3387
+ subtype;
3388
+ errors;
3389
+ status;
3390
+ constructor(subtype, errors, status) {
3391
+ super(
3392
+ `claude-code-login: query() ended with ${subtype}` + (status !== void 0 ? ` (api status ${status})` : "") + `: ${errors.length > 0 ? errors.join("; ") : "no detail from the binary"}`
3393
+ );
3394
+ this.name = "ClaudeCodeLoginResultError";
3395
+ this.subtype = subtype;
3396
+ this.errors = errors;
3397
+ this.status = status;
3398
+ }
3399
+ };
3400
+ function createDenyingIntercept(intercepted) {
3401
+ return async (toolName, input) => {
3402
+ intercepted.push({ name: toolName, input });
3403
+ return {
3404
+ behavior: "deny",
3405
+ message: "ggui executes tools in its harness; the binary only proposes them."
3406
+ };
3407
+ };
3408
+ }
3409
+ async function* imageUserMessage(text, image) {
3410
+ const message = {
3411
+ role: "user",
3412
+ content: [
3413
+ {
3414
+ type: "image",
3415
+ source: { type: "base64", media_type: image.mediaType, data: image.base64 }
3416
+ },
3417
+ { type: "text", text }
3418
+ ]
3419
+ };
3420
+ yield { type: "user", message, parent_tool_use_id: null };
3421
+ }
3422
+ function buildLoginQueryOptions(params) {
3423
+ return {
3424
+ model: params.model,
3425
+ systemPrompt: params.systemPrompt,
3426
+ maxTurns: 1,
3427
+ thinking: { type: "disabled" },
3428
+ env: loginChildEnv(params.env ?? process.env),
3429
+ ...claudeCodeLoginQueryOptions(),
3430
+ ...params.tools !== void 0 ? {
3431
+ mcpServers: { [MCP_SERVER_NAME]: params.tools.server },
3432
+ canUseTool: params.tools.canUseTool
3433
+ } : {}
3434
+ };
3435
+ }
3436
+ var JSON_SCHEMA_TYPES = [
3437
+ "object",
3438
+ "array",
3439
+ "string",
3440
+ "number",
3441
+ "boolean",
3442
+ "null",
3443
+ "integer"
3444
+ ];
3445
+ function isJsonSchemaType(value) {
3446
+ return typeof value === "string" && JSON_SCHEMA_TYPES.includes(value);
3447
+ }
3448
+ function isJsonObject(value) {
3449
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3450
+ }
3451
+ function schemaAt(value, path) {
3452
+ if (typeof value === "boolean") return value;
3453
+ if (!isJsonObject(value)) {
3454
+ throw new Error(
3455
+ `claude-code-login: ${path} must be a JSON-Schema object or boolean, got ${JSON.stringify(value)}`
3456
+ );
3457
+ }
3458
+ return toZodJsonSchema(value, path);
3459
+ }
3460
+ function toZodJsonSchema(parameters, path = "parameters") {
3461
+ const out = {};
3462
+ for (const [key, value] of Object.entries(parameters)) {
3463
+ if (value === void 0) continue;
3464
+ switch (key) {
3465
+ case "type": {
3466
+ if (!isJsonSchemaType(value)) {
3467
+ throw new Error(
3468
+ `claude-code-login: ${path}.type must be one of ${JSON_SCHEMA_TYPES.join("|")}, got ${JSON.stringify(value)}`
3469
+ );
3470
+ }
3471
+ out.type = value;
3472
+ break;
3473
+ }
3474
+ case "properties":
3475
+ case "patternProperties":
3476
+ case "$defs": {
3477
+ if (!isJsonObject(value))
3478
+ throw new Error(`claude-code-login: ${path}.${key} must be an object`);
3479
+ const record = {};
3480
+ for (const [name, sub] of Object.entries(value)) {
3481
+ if (sub === void 0) continue;
3482
+ record[name] = schemaAt(sub, `${path}.${key}.${name}`);
3483
+ }
3484
+ if (key === "$defs") {
3485
+ const defs = {};
3486
+ for (const [name, sub] of Object.entries(record)) {
3487
+ if (typeof sub === "boolean")
3488
+ throw new Error(`claude-code-login: ${path}.$defs.${name} must be an object`);
3489
+ defs[name] = sub;
3490
+ }
3491
+ out.$defs = defs;
3492
+ } else {
3493
+ out[key] = record;
3494
+ }
3495
+ break;
3496
+ }
3497
+ case "items": {
3498
+ out.items = Array.isArray(value) ? value.map((sub, i) => schemaAt(sub, `${path}.items[${i}]`)) : schemaAt(value, `${path}.items`);
3499
+ break;
3500
+ }
3501
+ case "additionalProperties":
3502
+ case "not": {
3503
+ out[key] = schemaAt(value, `${path}.${key}`);
3504
+ break;
3505
+ }
3506
+ case "anyOf":
3507
+ case "oneOf":
3508
+ case "allOf": {
3509
+ if (!Array.isArray(value))
3510
+ throw new Error(`claude-code-login: ${path}.${key} must be an array`);
3511
+ out[key] = value.map((sub, i) => {
3512
+ const s = schemaAt(sub, `${path}.${key}[${i}]`);
3513
+ if (typeof s === "boolean")
3514
+ throw new Error(`claude-code-login: ${path}.${key}[${i}] must be an object`);
3515
+ return s;
3516
+ });
3517
+ break;
3518
+ }
3519
+ case "required": {
3520
+ if (!Array.isArray(value) || !value.every((v) => typeof v === "string")) {
3521
+ throw new Error(`claude-code-login: ${path}.required must be an array of strings`);
3522
+ }
3523
+ out.required = value;
3524
+ break;
3525
+ }
3526
+ case "enum": {
3527
+ if (!Array.isArray(value) || !value.every(
3528
+ (v) => v === null || typeof v === "string" || typeof v === "number" || typeof v === "boolean"
3529
+ )) {
3530
+ throw new Error(`claude-code-login: ${path}.enum must be an array of primitives`);
3531
+ }
3532
+ out.enum = value;
3533
+ break;
3534
+ }
3535
+ default:
3536
+ out[key] = value;
3537
+ }
3538
+ }
3539
+ return out;
3540
+ }
3541
+ function createLoginToolServer(sdk, tools) {
3542
+ const sdkTools = tools.map((def) => {
3543
+ const schema = z.fromJSONSchema(toZodJsonSchema(def.parameters, `${def.name}.parameters`));
3544
+ if (!(schema instanceof z.ZodObject)) {
3545
+ throw new Error(
3546
+ `claude-code-login: tool ${def.name}'s parameters must be a JSON-Schema object`
3547
+ );
3548
+ }
3549
+ return sdk.tool(def.name, def.description, schema.shape, async () => {
3550
+ throw new Error(
3551
+ `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)`
3552
+ );
3553
+ });
3554
+ });
3555
+ return sdk.createSdkMcpServer({ name: MCP_SERVER_NAME, version: "1.0.0", tools: sdkTools });
3556
+ }
3557
+ 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.";
3558
+ function describeCalls(calls) {
3559
+ return JSON.stringify(calls.map((c) => ({ name: stripMcpToolPrefix(c.name), input: c.input })));
3560
+ }
3561
+ async function collectLoginTurn(stream, opts) {
3562
+ let init;
3563
+ const toolCalls = [];
3564
+ let text = "";
3565
+ let thinkingBlocks = 0;
3566
+ let result;
3567
+ try {
3568
+ for await (const msg of stream) {
3569
+ if (msg.type === "system" && msg.subtype === "init") {
3570
+ const measured = msg;
3571
+ init = measured;
3572
+ opts.log(
3573
+ `[claude-code-login] apiKeySource=${measured.apiKeySource} claude_code_version=${measured.claude_code_version} model=${measured.model}`
3574
+ );
3575
+ if (measured.apiKeySource !== LOGIN_API_KEY_SOURCE) {
3576
+ throw new ClaudeCodeLoginAuthError(measured.apiKeySource);
3577
+ }
3578
+ } else if (msg.type === "assistant") {
3579
+ for (const block of msg.message.content) {
3580
+ if (block.type === "tool_use") {
3581
+ toolCalls.push({
3582
+ id: block.id,
3583
+ name: stripMcpToolPrefix(block.name),
3584
+ input: isRecord(block.input) ? toolArgsToJsonObject(block.input) : {}
3585
+ });
3586
+ } else if (block.type === "text") {
3587
+ text += block.text;
3588
+ } else if (block.type === "thinking" || block.type === "redacted_thinking") {
3589
+ thinkingBlocks += 1;
3590
+ }
3591
+ }
3592
+ } else if (msg.type === "result") {
3593
+ result = msg;
3594
+ }
3595
+ }
3596
+ } catch (e) {
3597
+ if (result === void 0) throw e;
3598
+ opts.log(
3599
+ `[claude-code-login] query() threw after its result message \u2014 the result decides (${e instanceof Error ? e.message : String(e)})`
3600
+ );
3601
+ }
3602
+ if (result === void 0) {
3603
+ throw new Error("claude-code-login: query() ended without a result message");
3604
+ }
3605
+ const usage = result.usage;
3606
+ const thinkingTokens = usage.output_tokens_details?.thinking_tokens ?? 0;
3607
+ opts.log(
3608
+ `[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}`
3609
+ );
3610
+ const streamSaw = describeCalls(toolCalls);
3611
+ const interceptSaw = describeCalls(opts.intercepted);
3612
+ if (streamSaw !== interceptSaw) {
3613
+ opts.warn(
3614
+ `[claude-code-login] intercept/stream disagreement \u2014 the stream's blocks win. intercepted=${interceptSaw} stream=${streamSaw}`
3615
+ );
3616
+ }
3617
+ if (result.subtype === "success") {
3618
+ if (result.is_error) {
3619
+ throw new ClaudeCodeLoginResultError(
3620
+ "success",
3621
+ [result.result],
3622
+ result.api_error_status ?? void 0
3623
+ );
3624
+ }
3625
+ } else if (result.subtype !== "error_max_turns") {
3626
+ const failed = result;
3627
+ throw new ClaudeCodeLoginResultError(failed.subtype, failed.errors, void 0);
3628
+ }
3629
+ return { init, toolCalls, text, result };
3630
+ }
3631
+
3303
3632
  // src/harness/llm-router.ts
3304
3633
  function addStrictSchemaConstraints(schema) {
3305
3634
  const result = { ...schema };
@@ -3494,17 +3823,6 @@ var LLMAgent = class {
3494
3823
  }
3495
3824
  }
3496
3825
  };
3497
- function normalizeAnthropicModelId(model) {
3498
- return model.replace(/^anthropic\//, "").replace(/^(?:[a-z]{2}\.)?anthropic\./, "");
3499
- }
3500
- function anthropicRejectsSamplingParams(model) {
3501
- return /^claude-(?:opus-4-7|opus-5|sonnet-5|fable-5)(?:-|$)/.test(
3502
- normalizeAnthropicModelId(model)
3503
- );
3504
- }
3505
- function anthropicRejectsForcedToolChoice(model) {
3506
- return /^claude-fable-5-1(?:-|$)/.test(normalizeAnthropicModelId(model));
3507
- }
3508
3826
  function resolveAnthropicSampling(model, temperature) {
3509
3827
  if (temperature === void 0) {
3510
3828
  return { request: {}, applied: { temperature: "provider-default" } };
@@ -3880,6 +4198,200 @@ var AnthropicAgent = class extends LLMAgent {
3880
4198
  }
3881
4199
  }
3882
4200
  };
4201
+ var ClaudeCodeLoginAgent = class extends LLMAgent {
4202
+ provider = "anthropic";
4203
+ resolveModel(model) {
4204
+ return model.startsWith("anthropic/") ? model.slice("anthropic/".length) : model;
4205
+ }
4206
+ async createClient() {
4207
+ const { query, tool, createSdkMcpServer } = await import('@anthropic-ai/claude-agent-sdk');
4208
+ return { query, tool, createSdkMcpServer };
4209
+ }
4210
+ async callText(model, systemPrompt, userPrompt, _maxTokens, _temperature) {
4211
+ const sdk = await this.getClient();
4212
+ const traceId = newLlmTraceId();
4213
+ const startedAt = Date.now();
4214
+ const resolvedModel = this.resolveModel(model);
4215
+ try {
4216
+ const turn = await this.apiCall(
4217
+ () => collectLoginTurn(
4218
+ sdk.query({
4219
+ prompt: userPrompt,
4220
+ options: buildLoginQueryOptions({ model: resolvedModel, systemPrompt })
4221
+ }),
4222
+ { intercepted: [], kind: "callText", choice: "none", log: console.log, warn: console.warn }
4223
+ )
4224
+ );
4225
+ const usage = turn.result.usage;
4226
+ const endedAt = Date.now();
4227
+ emitLlmTraceEvent({
4228
+ id: traceId,
4229
+ at: startedAt,
4230
+ endedAt,
4231
+ durationMs: endedAt - startedAt,
4232
+ provider: "claude-code-login",
4233
+ model: resolvedModel,
4234
+ kind: "callText",
4235
+ systemPrompt,
4236
+ userPrompt,
4237
+ result: {
4238
+ inputTokens: usage.input_tokens,
4239
+ outputTokens: usage.output_tokens,
4240
+ cacheCreated: usage.cache_creation_input_tokens,
4241
+ cacheRead: usage.cache_read_input_tokens,
4242
+ text: turn.text
4243
+ }
4244
+ });
4245
+ return { text: turn.text, inputTokens: usage.input_tokens, outputTokens: usage.output_tokens };
4246
+ } catch (e) {
4247
+ const endedAt = Date.now();
4248
+ emitLlmTraceEvent({
4249
+ id: traceId,
4250
+ at: startedAt,
4251
+ endedAt,
4252
+ durationMs: endedAt - startedAt,
4253
+ provider: "claude-code-login",
4254
+ model: resolvedModel,
4255
+ kind: "callText",
4256
+ systemPrompt,
4257
+ userPrompt,
4258
+ error: { message: e instanceof Error ? e.message : String(e) }
4259
+ });
4260
+ throw e;
4261
+ }
4262
+ }
4263
+ async callVision(model, systemPrompt, userPrompt, image, _maxTokens) {
4264
+ const sdk = await this.getClient();
4265
+ const traceId = newLlmTraceId();
4266
+ const startedAt = Date.now();
4267
+ const resolvedModel = this.resolveModel(model);
4268
+ try {
4269
+ const turn = await this.apiCall(
4270
+ () => collectLoginTurn(
4271
+ sdk.query({
4272
+ prompt: imageUserMessage(userPrompt, image),
4273
+ options: buildLoginQueryOptions({ model: resolvedModel, systemPrompt })
4274
+ }),
4275
+ { intercepted: [], kind: "callVision", choice: "none", log: console.log, warn: console.warn }
4276
+ )
4277
+ );
4278
+ const usage = turn.result.usage;
4279
+ const endedAt = Date.now();
4280
+ emitLlmTraceEvent({
4281
+ id: traceId,
4282
+ at: startedAt,
4283
+ endedAt,
4284
+ durationMs: endedAt - startedAt,
4285
+ provider: "claude-code-login",
4286
+ model: resolvedModel,
4287
+ kind: "callVision",
4288
+ systemPrompt,
4289
+ userPrompt,
4290
+ result: { inputTokens: usage.input_tokens, outputTokens: usage.output_tokens, text: turn.text }
4291
+ });
4292
+ return { text: turn.text, inputTokens: usage.input_tokens, outputTokens: usage.output_tokens };
4293
+ } catch (e) {
4294
+ const endedAt = Date.now();
4295
+ emitLlmTraceEvent({
4296
+ id: traceId,
4297
+ at: startedAt,
4298
+ endedAt,
4299
+ durationMs: endedAt - startedAt,
4300
+ provider: "claude-code-login",
4301
+ model: resolvedModel,
4302
+ kind: "callVision",
4303
+ systemPrompt,
4304
+ userPrompt,
4305
+ error: { message: e instanceof Error ? e.message : String(e) }
4306
+ });
4307
+ throw e;
4308
+ }
4309
+ }
4310
+ async callTools(model, systemPrompt, userPrompt, tools, toolChoice = "required", _scopedTools) {
4311
+ const sdk = await this.getClient();
4312
+ const traceId = newLlmTraceId();
4313
+ const startedAt = Date.now();
4314
+ const resolvedModel = this.resolveModel(model);
4315
+ const sentUserPrompt = toolChoice === "required" ? `${userPrompt}
4316
+
4317
+ ${LOGIN_REQUIRED_TOOL_INSTRUCTION}` : userPrompt;
4318
+ try {
4319
+ const turn = await this.apiCall(() => {
4320
+ const intercepted = [];
4321
+ const server = createLoginToolServer(sdk, tools);
4322
+ return collectLoginTurn(
4323
+ sdk.query({
4324
+ prompt: sentUserPrompt,
4325
+ options: buildLoginQueryOptions({
4326
+ model: resolvedModel,
4327
+ systemPrompt,
4328
+ tools: { server, canUseTool: createDenyingIntercept(intercepted) }
4329
+ })
4330
+ }),
4331
+ { intercepted, kind: "callTools", choice: toolChoice === "required" ? "required:instructed" : "auto", log: console.log, warn: console.warn }
4332
+ );
4333
+ });
4334
+ const usage = turn.result.usage;
4335
+ const cacheCreated = usage.cache_creation_input_tokens;
4336
+ const cacheRead = usage.cache_read_input_tokens;
4337
+ if (cacheCreated || cacheRead) {
4338
+ console.log(
4339
+ `[claude-code-login] callTools cache: created=${cacheCreated} read=${cacheRead} input=${usage.input_tokens} output=${usage.output_tokens}`
4340
+ );
4341
+ }
4342
+ const toolCalls = turn.toolCalls.map((c) => ({ id: c.id, name: c.name, input: c.input }));
4343
+ const endedAt = Date.now();
4344
+ emitLlmTraceEvent({
4345
+ id: traceId,
4346
+ at: startedAt,
4347
+ endedAt,
4348
+ durationMs: endedAt - startedAt,
4349
+ provider: "claude-code-login",
4350
+ model: resolvedModel,
4351
+ kind: "callTools",
4352
+ systemPrompt,
4353
+ userPrompt: sentUserPrompt,
4354
+ tools: summarizeTools(tools),
4355
+ result: {
4356
+ inputTokens: usage.input_tokens,
4357
+ outputTokens: usage.output_tokens,
4358
+ cacheCreated,
4359
+ cacheRead,
4360
+ toolCalls: toolCalls.map((c) => ({ name: c.name, input: c.input }))
4361
+ }
4362
+ });
4363
+ return {
4364
+ toolCalls,
4365
+ inputTokens: usage.input_tokens,
4366
+ outputTokens: usage.output_tokens,
4367
+ cacheReadTokens: cacheRead,
4368
+ cacheCreationTokens: cacheCreated,
4369
+ appliedToolChoice: "auto"
4370
+ };
4371
+ } catch (e) {
4372
+ const endedAt = Date.now();
4373
+ emitLlmTraceEvent({
4374
+ id: traceId,
4375
+ at: startedAt,
4376
+ endedAt,
4377
+ durationMs: endedAt - startedAt,
4378
+ provider: "claude-code-login",
4379
+ model: resolvedModel,
4380
+ kind: "callTools",
4381
+ systemPrompt,
4382
+ userPrompt: sentUserPrompt,
4383
+ tools: summarizeTools(tools),
4384
+ error: { message: e instanceof Error ? e.message : String(e) }
4385
+ });
4386
+ throw e;
4387
+ }
4388
+ }
4389
+ async callWithTools() {
4390
+ throw new Error(
4391
+ "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"
4392
+ );
4393
+ }
4394
+ };
3883
4395
  var OpenAIAgent = class extends LLMAgent {
3884
4396
  provider = "openai";
3885
4397
  resolveModel(model) {
@@ -3908,10 +4420,15 @@ var OpenAIAgent = class extends LLMAgent {
3908
4420
  }
3909
4421
  }
3910
4422
  }
4423
+ const textUsage = splitOpenAiUsage(
4424
+ response.usage?.input_tokens ?? 0,
4425
+ response.usage?.output_tokens ?? 0,
4426
+ response.usage?.input_tokens_details?.cached_tokens ?? 0
4427
+ );
3911
4428
  return {
3912
4429
  text,
3913
- inputTokens: response.usage?.input_tokens ?? 0,
3914
- outputTokens: response.usage?.output_tokens ?? 0,
4430
+ inputTokens: textUsage.tokens.input,
4431
+ outputTokens: textUsage.tokens.output,
3915
4432
  sampling: { temperature: temperature ?? "provider-default" }
3916
4433
  };
3917
4434
  }
@@ -3950,14 +4467,20 @@ var OpenAIAgent = class extends LLMAgent {
3950
4467
  const functionCalls = response.output.filter(
3951
4468
  (o) => o.type === "function_call"
3952
4469
  );
4470
+ const toolsUsage = splitOpenAiUsage(
4471
+ response.usage?.input_tokens ?? 0,
4472
+ response.usage?.output_tokens ?? 0,
4473
+ response.usage?.input_tokens_details?.cached_tokens ?? 0
4474
+ );
3953
4475
  return {
3954
4476
  toolCalls: functionCalls.map((fc) => ({
3955
4477
  id: fc.call_id,
3956
4478
  name: fc.name,
3957
4479
  input: JSON.parse(fc.arguments ?? "{}")
3958
4480
  })),
3959
- inputTokens: response.usage?.input_tokens ?? 0,
3960
- outputTokens: response.usage?.output_tokens ?? 0
4481
+ inputTokens: toolsUsage.tokens.input,
4482
+ outputTokens: toolsUsage.tokens.output,
4483
+ cacheReadTokens: toolsUsage.cacheReadTokens
3961
4484
  };
3962
4485
  }
3963
4486
  // Pending tool results — stored by sendToolResult, consumed by next callTools
@@ -3984,6 +4507,7 @@ var OpenAIAgent = class extends LLMAgent {
3984
4507
  ];
3985
4508
  let totalIn = 0;
3986
4509
  let totalOut = 0;
4510
+ let totalCacheRead = 0;
3987
4511
  let allText = "";
3988
4512
  for (let turn = 0; turn < maxTurns; turn++) {
3989
4513
  const response = await this.apiCall(
@@ -3994,8 +4518,14 @@ var OpenAIAgent = class extends LLMAgent {
3994
4518
  tools: openaiTools
3995
4519
  })
3996
4520
  );
3997
- totalIn += response.usage?.input_tokens ?? 0;
3998
- totalOut += response.usage?.output_tokens ?? 0;
4521
+ const turnUsage = splitOpenAiUsage(
4522
+ response.usage?.input_tokens ?? 0,
4523
+ response.usage?.output_tokens ?? 0,
4524
+ response.usage?.input_tokens_details?.cached_tokens ?? 0
4525
+ );
4526
+ totalIn += turnUsage.tokens.input;
4527
+ totalOut += turnUsage.tokens.output;
4528
+ totalCacheRead += turnUsage.cacheReadTokens;
3999
4529
  const functionCalls = response.output.filter(
4000
4530
  (o) => o.type === "function_call"
4001
4531
  );
@@ -4026,6 +4556,7 @@ var OpenAIAgent = class extends LLMAgent {
4026
4556
  text: allText,
4027
4557
  inputTokens: totalIn,
4028
4558
  outputTokens: totalOut,
4559
+ cacheReadTokens: totalCacheRead,
4029
4560
  turnsUsed: input.length
4030
4561
  };
4031
4562
  }
@@ -4051,6 +4582,10 @@ function functionCallSteps(interaction) {
4051
4582
  function toUserInputStep(text) {
4052
4583
  return { type: "user_input", content: [{ type: "text", text }] };
4053
4584
  }
4585
+ function googleInteractionUsage(usage) {
4586
+ const split = splitCacheInclusiveUsage(usage?.total_input_tokens ?? 0, usage?.total_output_tokens ?? 0, usage?.total_cached_tokens ?? 0);
4587
+ return { inputTokens: split.tokens.input, outputTokens: split.tokens.output, cacheReadTokens: split.cacheReadTokens };
4588
+ }
4054
4589
  var GoogleAgent = class extends LLMAgent {
4055
4590
  provider = "google";
4056
4591
  resolveModel(model) {
@@ -4080,8 +4615,9 @@ var GoogleAgent = class extends LLMAgent {
4080
4615
  );
4081
4616
  return {
4082
4617
  text: interactionText(interaction),
4083
- inputTokens: interaction.usage?.total_input_tokens ?? 0,
4084
- outputTokens: interaction.usage?.total_output_tokens ?? 0,
4618
+ // ggui#1186 (third site): `total_input_tokens` INCLUDES the cached part of the prompt — report the non-cached input.
4619
+ inputTokens: googleInteractionUsage(interaction.usage).inputTokens,
4620
+ outputTokens: googleInteractionUsage(interaction.usage).outputTokens,
4085
4621
  sampling: { temperature: temperature ?? "provider-default" }
4086
4622
  };
4087
4623
  }
@@ -4113,7 +4649,8 @@ var GoogleAgent = class extends LLMAgent {
4113
4649
  const usage = response.usageMetadata;
4114
4650
  const result = {
4115
4651
  text,
4116
- inputTokens: usage?.promptTokenCount ?? 0,
4652
+ // ggui#1186 (third site): `promptTokenCount` includes `cachedContentTokenCount` — report the non-cached input.
4653
+ inputTokens: splitCacheInclusiveUsage(usage?.promptTokenCount ?? 0, usage?.candidatesTokenCount ?? 0, usage?.cachedContentTokenCount ?? 0).tokens.input,
4117
4654
  outputTokens: usage?.candidatesTokenCount ?? 0
4118
4655
  };
4119
4656
  const endedAt = Date.now();
@@ -4257,8 +4794,8 @@ var GoogleAgent = class extends LLMAgent {
4257
4794
  }));
4258
4795
  return {
4259
4796
  toolCalls,
4260
- inputTokens: interaction.usage?.total_input_tokens ?? 0,
4261
- outputTokens: interaction.usage?.total_output_tokens ?? 0
4797
+ // ggui#1186 (third site): the coding loop reads this per-turn usage — the non-cached prompt as input, the cached subset reported.
4798
+ ...googleInteractionUsage(interaction.usage)
4262
4799
  };
4263
4800
  }
4264
4801
  // Pending tool results — stored by sendToolResult, consumed by next callTools
@@ -4282,6 +4819,7 @@ var GoogleAgent = class extends LLMAgent {
4282
4819
  }));
4283
4820
  let totalIn = 0;
4284
4821
  let totalOut = 0;
4822
+ let totalCacheRead = 0;
4285
4823
  let allText = "";
4286
4824
  let turnsUsed = 0;
4287
4825
  let interaction = await this.apiCall(
@@ -4295,8 +4833,10 @@ var GoogleAgent = class extends LLMAgent {
4295
4833
  for (let turn = 0; turn < maxTurns; turn++) {
4296
4834
  turnsUsed = turn + 1;
4297
4835
  if (interaction.usage) {
4298
- totalIn += interaction.usage.total_input_tokens ?? 0;
4299
- totalOut += interaction.usage.total_output_tokens ?? 0;
4836
+ const turnUsage = googleInteractionUsage(interaction.usage);
4837
+ totalIn += turnUsage.inputTokens;
4838
+ totalOut += turnUsage.outputTokens;
4839
+ totalCacheRead += turnUsage.cacheReadTokens;
4300
4840
  }
4301
4841
  allText += interactionText(interaction);
4302
4842
  const fnCalls = functionCallSteps(interaction);
@@ -4334,6 +4874,7 @@ var GoogleAgent = class extends LLMAgent {
4334
4874
  text: allText,
4335
4875
  inputTokens: totalIn,
4336
4876
  outputTokens: totalOut,
4877
+ cacheReadTokens: totalCacheRead,
4337
4878
  turnsUsed
4338
4879
  };
4339
4880
  }
@@ -4389,12 +4930,19 @@ var OpenRouterAgent = class extends LLMAgent {
4389
4930
  this.sessionMessages.push({ role: "system", content: systemPrompt });
4390
4931
  }
4391
4932
  this.sessionMessages.push({ role: "user", content: userPrompt });
4933
+ const requested = toolChoice ?? "auto";
4934
+ const appliedToolChoice = requested === "required" && anthropicRejectsForcedToolChoice(resolved) ? "auto" : requested;
4935
+ if (appliedToolChoice !== requested) {
4936
+ console.warn(
4937
+ `[openrouter] callTools: ${resolved} rejects forced tool_choice (HTTP 400); caller's 'required' downgraded to 'auto'`
4938
+ );
4939
+ }
4392
4940
  const response = await this.apiCall(
4393
4941
  () => client.chatCompletion({
4394
4942
  model: resolved,
4395
4943
  messages: this.sessionMessages,
4396
4944
  tools: orTools,
4397
- tool_choice: toolChoice ?? "auto"
4945
+ tool_choice: appliedToolChoice
4398
4946
  })
4399
4947
  );
4400
4948
  const choice = response.choices[0];
@@ -4413,7 +4961,8 @@ var OpenRouterAgent = class extends LLMAgent {
4413
4961
  return {
4414
4962
  toolCalls,
4415
4963
  inputTokens: response.usage.prompt_tokens,
4416
- outputTokens: response.usage.completion_tokens
4964
+ outputTokens: response.usage.completion_tokens,
4965
+ appliedToolChoice
4417
4966
  };
4418
4967
  }
4419
4968
  async sendToolResult(results) {
@@ -4484,6 +5033,14 @@ function createAgent(providerOrConfig) {
4484
5033
  const provider = typeof providerOrConfig === "string" ? providerOrConfig : providerOrConfig.provider;
4485
5034
  const routeOverride = typeof providerOrConfig === "string" ? void 0 : providerOrConfig.routeOverride;
4486
5035
  const onRetry = typeof providerOrConfig === "string" ? void 0 : providerOrConfig.onRetry;
5036
+ if (routeOverride?.claudeCodeLogin === true) {
5037
+ if (provider !== "anthropic") {
5038
+ throw new Error(
5039
+ `${provider}: the claude-code-login credential authenticates the Claude Code binary only \u2014 pick an anthropic route or supply a ${provider} API key`
5040
+ );
5041
+ }
5042
+ return new ClaudeCodeLoginAgent(routeOverride, onRetry);
5043
+ }
4487
5044
  switch (provider) {
4488
5045
  case "anthropic":
4489
5046
  return new AnthropicAgent(routeOverride, onRetry);