@ggui-ai/ui-gen 0.19.0 → 0.21.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 (89) 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 +992 -3468
  6. package/dist/adapters/generation-dispatch.js.map +1 -1
  7. package/dist/adapters/index.js +70 -13
  8. package/dist/adapters/index.js.map +1 -1
  9. package/dist/adapters/types.d.ts +1 -1
  10. package/dist/adapters/types.js.map +1 -1
  11. package/dist/advanced/index.d.ts +4 -4
  12. package/dist/advanced/index.js +1060 -3520
  13. package/dist/advanced/index.js.map +1 -1
  14. package/dist/blueprint-validator.js +13 -3
  15. package/dist/blueprint-validator.js.map +1 -1
  16. package/dist/boilerplate.js +2 -2
  17. package/dist/boilerplate.js.map +1 -1
  18. package/dist/check/index.d.ts +1 -1
  19. package/dist/check/index.js.map +1 -1
  20. package/dist/{create-ui-generator-CxPvudbs.d.ts → create-ui-generator-D7kOXNjJ.d.ts} +2 -2
  21. package/dist/{dispatch-N8wdNOX-.d.ts → dispatch-DGvhmARp.d.ts} +1 -1
  22. package/dist/evaluation/axis-checks/index.d.ts +3 -3
  23. package/dist/evaluation/axis-checks/index.js +46 -0
  24. package/dist/evaluation/axis-checks/index.js.map +1 -1
  25. package/dist/evaluation/axis-checks/registry.d.ts +1 -1
  26. package/dist/evaluation/axis-checks/registry.js +46 -0
  27. package/dist/evaluation/axis-checks/registry.js.map +1 -1
  28. package/dist/evaluation/index.d.ts +38 -6
  29. package/dist/evaluation/index.js +682 -3357
  30. package/dist/evaluation/index.js.map +1 -1
  31. package/dist/harness/check/runtime-render/index.d.ts +2 -2
  32. package/dist/harness/check/runtime-render/index.js +13 -3
  33. package/dist/harness/check/runtime-render/index.js.map +1 -1
  34. package/dist/harness/check/runtime-render/render-check-worker.js +8 -3
  35. package/dist/harness/check/runtime-render/render-check-worker.js.map +1 -1
  36. package/dist/harness/index.d.ts +2 -2
  37. package/dist/harness/index.js +562 -30
  38. package/dist/harness/index.js.map +1 -1
  39. package/dist/harness/llm-trace-sink.d.ts +7 -1
  40. package/dist/harness/llm-trace-sink.js.map +1 -1
  41. package/dist/harness/result-types.d.ts +1 -1
  42. package/dist/harness/runtime.d.ts +11 -2
  43. package/dist/harness/runtime.js +3 -3
  44. package/dist/harness/runtime.js.map +1 -1
  45. package/dist/harness/types-public.d.ts +1 -1
  46. package/dist/harness/types-public.js +2 -2
  47. package/dist/harness/types-public.js.map +1 -1
  48. package/dist/harness/validator-trace-sink.d.ts +1 -1
  49. package/dist/index.d.ts +3 -3
  50. package/dist/index.js +1060 -3520
  51. package/dist/index.js.map +1 -1
  52. package/dist/{llm-router-D7fjBhtr.d.ts → llm-router-DwwUK1bz.d.ts} +16 -3
  53. package/dist/patch.d.ts +1 -1
  54. package/dist/providers/index.js +5 -4
  55. package/dist/providers/index.js.map +1 -1
  56. package/dist/{render-check-WfOVO9kU.d.ts → render-check-C0CqVceF.d.ts} +17 -0
  57. package/dist/{types-public-DKR_h70H.d.ts → types-public-CzYqZclY.d.ts} +44 -1
  58. package/dist/validation/index.d.ts +1 -1
  59. package/dist/validation/index.js +1 -1
  60. package/dist/validation/index.js.map +1 -1
  61. package/dist/workflows.d.ts +1 -1
  62. package/package.json +16 -12
  63. package/src/adapters/base.ts +8 -0
  64. package/src/adapters/claude/claude-code-login.ts +73 -0
  65. package/src/adapters/claude/sdk.ts +8 -1
  66. package/src/adapters/generation-dispatch.ts +19 -2
  67. package/src/adapters/openai/raw.ts +11 -5
  68. package/src/adapters/openai/sdk.ts +8 -5
  69. package/src/adapters/openai/tokens.ts +67 -0
  70. package/src/adapters/provider-router.ts +28 -0
  71. package/src/adapters/types.ts +1 -1
  72. package/src/boilerplate/generate.ts +19 -4
  73. package/src/boilerplate/hard-sections.ts +1 -1
  74. package/src/create-ui-generator.ts +19 -10
  75. package/src/evaluation/axis-checks/checks/universal.ts +92 -0
  76. package/src/evaluation/design-identity.ts +16 -5
  77. package/src/evaluation/types-public.ts +42 -0
  78. package/src/evaluation/visual-evaluator.ts +231 -24
  79. package/src/harness/check/runtime-render/adapter.ts +9 -0
  80. package/src/harness/check/runtime-render/render-check.ts +37 -1
  81. package/src/harness/claude-code-login-query.ts +528 -0
  82. package/src/harness/coding/assemble-result.ts +10 -1
  83. package/src/harness/coding/run-eval-round.ts +179 -34
  84. package/src/harness/enforced-coding.ts +20 -0
  85. package/src/harness/llm-router.ts +390 -55
  86. package/src/harness/llm-trace-sink.ts +7 -1
  87. package/src/harness/runtime.ts +6 -0
  88. package/src/providers/openai.ts +31 -19
  89. 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,25 @@ function createAnthropicClient(rawKey) {
3227
3227
  });
3228
3228
  }
3229
3229
 
3230
+ // src/adapters/claude/claude-code-login.ts
3231
+ var PROVIDER_KEY_ENV_NAMES = [
3232
+ "ANTHROPIC_API_KEY",
3233
+ "CLAUDE_API_KEY",
3234
+ "ANTHROPIC_AUTH_TOKEN",
3235
+ "ANTHROPIC_BASE_URL"
3236
+ ];
3237
+ function stripProviderKeyEnv(env) {
3238
+ const out = {};
3239
+ const drop = new Set(PROVIDER_KEY_ENV_NAMES);
3240
+ for (const [k, v] of Object.entries(env)) {
3241
+ if (v !== void 0 && !drop.has(k)) out[k] = v;
3242
+ }
3243
+ return out;
3244
+ }
3245
+ function claudeCodeLoginQueryOptions() {
3246
+ return { tools: [], settingSources: [] };
3247
+ }
3248
+
3230
3249
  // src/adapters/provider-router.ts
3231
3250
  function getBedrockModelId(model) {
3232
3251
  const BEDROCK_MAP = {
@@ -3277,6 +3296,26 @@ function toJsonValue(value, path) {
3277
3296
  );
3278
3297
  }
3279
3298
 
3299
+ // src/adapters/openai/tokens.ts
3300
+ function splitOpenAiUsage(inputTokensInclCached, outputTokens, cachedTokens) {
3301
+ return splitCacheInclusiveUsage(inputTokensInclCached, outputTokens, cachedTokens);
3302
+ }
3303
+ function splitCacheInclusiveUsage(inputTokensInclCached, outputTokens, cachedTokens) {
3304
+ const cacheRead = Math.min(
3305
+ Math.max(cachedTokens, 0),
3306
+ Math.max(inputTokensInclCached, 0)
3307
+ );
3308
+ const nonCachedInput = Math.max(inputTokensInclCached, 0) - cacheRead;
3309
+ return {
3310
+ tokens: {
3311
+ input: nonCachedInput,
3312
+ output: outputTokens,
3313
+ total: nonCachedInput + cacheRead + outputTokens
3314
+ },
3315
+ cacheReadTokens: cacheRead
3316
+ };
3317
+ }
3318
+
3280
3319
  // src/harness/llm-trace-sink.ts
3281
3320
  var activeSink2 = null;
3282
3321
  function setLlmTraceSink(sink) {
@@ -3300,6 +3339,271 @@ function newLlmTraceId() {
3300
3339
  return Date.now().toString(36) + Math.random().toString(36).slice(2, 10);
3301
3340
  }
3302
3341
 
3342
+ // src/adapters/claude/tool-bridge.ts
3343
+ var MCP_SERVER_NAME = "ggui";
3344
+
3345
+ // src/harness/claude-code-login-query.ts
3346
+ var MCP_TOOL_PREFIX = `mcp__${MCP_SERVER_NAME}__`;
3347
+ function stripMcpToolPrefix(name) {
3348
+ return name.startsWith(MCP_TOOL_PREFIX) ? name.slice(MCP_TOOL_PREFIX.length) : name;
3349
+ }
3350
+ var LOGIN_API_KEY_SOURCE = "none";
3351
+ var ClaudeCodeLoginAuthError = class extends Error {
3352
+ apiKeySource;
3353
+ constructor(apiKeySource) {
3354
+ super(
3355
+ `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`
3356
+ );
3357
+ this.name = "ClaudeCodeLoginAuthError";
3358
+ this.apiKeySource = apiKeySource;
3359
+ }
3360
+ };
3361
+ var ClaudeCodeLoginResultError = class extends Error {
3362
+ subtype;
3363
+ errors;
3364
+ status;
3365
+ constructor(subtype, errors, status) {
3366
+ super(
3367
+ `claude-code-login: query() ended with ${subtype}` + (status !== void 0 ? ` (api status ${status})` : "") + `: ${errors.length > 0 ? errors.join("; ") : "no detail from the binary"}`
3368
+ );
3369
+ this.name = "ClaudeCodeLoginResultError";
3370
+ this.subtype = subtype;
3371
+ this.errors = errors;
3372
+ this.status = status;
3373
+ }
3374
+ };
3375
+ function createDenyingIntercept(intercepted) {
3376
+ return async (toolName, input) => {
3377
+ intercepted.push({ name: toolName, input });
3378
+ return {
3379
+ behavior: "deny",
3380
+ message: "ggui executes tools in its harness; the binary only proposes them."
3381
+ };
3382
+ };
3383
+ }
3384
+ async function* imageUserMessage(text, image) {
3385
+ const message = {
3386
+ role: "user",
3387
+ content: [
3388
+ {
3389
+ type: "image",
3390
+ source: { type: "base64", media_type: image.mediaType, data: image.base64 }
3391
+ },
3392
+ { type: "text", text }
3393
+ ]
3394
+ };
3395
+ yield { type: "user", message, parent_tool_use_id: null };
3396
+ }
3397
+ function buildLoginQueryOptions(params) {
3398
+ return {
3399
+ model: params.model,
3400
+ systemPrompt: params.systemPrompt,
3401
+ maxTurns: 1,
3402
+ thinking: { type: "disabled" },
3403
+ env: stripProviderKeyEnv(params.env ?? process.env),
3404
+ ...claudeCodeLoginQueryOptions(),
3405
+ ...params.tools !== void 0 ? {
3406
+ mcpServers: { [MCP_SERVER_NAME]: params.tools.server },
3407
+ canUseTool: params.tools.canUseTool
3408
+ } : {}
3409
+ };
3410
+ }
3411
+ var JSON_SCHEMA_TYPES = [
3412
+ "object",
3413
+ "array",
3414
+ "string",
3415
+ "number",
3416
+ "boolean",
3417
+ "null",
3418
+ "integer"
3419
+ ];
3420
+ function isJsonSchemaType(value) {
3421
+ return typeof value === "string" && JSON_SCHEMA_TYPES.includes(value);
3422
+ }
3423
+ function isJsonObject(value) {
3424
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3425
+ }
3426
+ function schemaAt(value, path) {
3427
+ if (typeof value === "boolean") return value;
3428
+ if (!isJsonObject(value)) {
3429
+ throw new Error(
3430
+ `claude-code-login: ${path} must be a JSON-Schema object or boolean, got ${JSON.stringify(value)}`
3431
+ );
3432
+ }
3433
+ return toZodJsonSchema(value, path);
3434
+ }
3435
+ function toZodJsonSchema(parameters, path = "parameters") {
3436
+ const out = {};
3437
+ for (const [key, value] of Object.entries(parameters)) {
3438
+ if (value === void 0) continue;
3439
+ switch (key) {
3440
+ case "type": {
3441
+ if (!isJsonSchemaType(value)) {
3442
+ throw new Error(
3443
+ `claude-code-login: ${path}.type must be one of ${JSON_SCHEMA_TYPES.join("|")}, got ${JSON.stringify(value)}`
3444
+ );
3445
+ }
3446
+ out.type = value;
3447
+ break;
3448
+ }
3449
+ case "properties":
3450
+ case "patternProperties":
3451
+ case "$defs": {
3452
+ if (!isJsonObject(value))
3453
+ throw new Error(`claude-code-login: ${path}.${key} must be an object`);
3454
+ const record = {};
3455
+ for (const [name, sub] of Object.entries(value)) {
3456
+ if (sub === void 0) continue;
3457
+ record[name] = schemaAt(sub, `${path}.${key}.${name}`);
3458
+ }
3459
+ if (key === "$defs") {
3460
+ const defs = {};
3461
+ for (const [name, sub] of Object.entries(record)) {
3462
+ if (typeof sub === "boolean")
3463
+ throw new Error(`claude-code-login: ${path}.$defs.${name} must be an object`);
3464
+ defs[name] = sub;
3465
+ }
3466
+ out.$defs = defs;
3467
+ } else {
3468
+ out[key] = record;
3469
+ }
3470
+ break;
3471
+ }
3472
+ case "items": {
3473
+ out.items = Array.isArray(value) ? value.map((sub, i) => schemaAt(sub, `${path}.items[${i}]`)) : schemaAt(value, `${path}.items`);
3474
+ break;
3475
+ }
3476
+ case "additionalProperties":
3477
+ case "not": {
3478
+ out[key] = schemaAt(value, `${path}.${key}`);
3479
+ break;
3480
+ }
3481
+ case "anyOf":
3482
+ case "oneOf":
3483
+ case "allOf": {
3484
+ if (!Array.isArray(value))
3485
+ throw new Error(`claude-code-login: ${path}.${key} must be an array`);
3486
+ out[key] = value.map((sub, i) => {
3487
+ const s = schemaAt(sub, `${path}.${key}[${i}]`);
3488
+ if (typeof s === "boolean")
3489
+ throw new Error(`claude-code-login: ${path}.${key}[${i}] must be an object`);
3490
+ return s;
3491
+ });
3492
+ break;
3493
+ }
3494
+ case "required": {
3495
+ if (!Array.isArray(value) || !value.every((v) => typeof v === "string")) {
3496
+ throw new Error(`claude-code-login: ${path}.required must be an array of strings`);
3497
+ }
3498
+ out.required = value;
3499
+ break;
3500
+ }
3501
+ case "enum": {
3502
+ if (!Array.isArray(value) || !value.every(
3503
+ (v) => v === null || typeof v === "string" || typeof v === "number" || typeof v === "boolean"
3504
+ )) {
3505
+ throw new Error(`claude-code-login: ${path}.enum must be an array of primitives`);
3506
+ }
3507
+ out.enum = value;
3508
+ break;
3509
+ }
3510
+ default:
3511
+ out[key] = value;
3512
+ }
3513
+ }
3514
+ return out;
3515
+ }
3516
+ function createLoginToolServer(sdk, tools) {
3517
+ const sdkTools = tools.map((def) => {
3518
+ const schema = z.fromJSONSchema(toZodJsonSchema(def.parameters, `${def.name}.parameters`));
3519
+ if (!(schema instanceof z.ZodObject)) {
3520
+ throw new Error(
3521
+ `claude-code-login: tool ${def.name}'s parameters must be a JSON-Schema object`
3522
+ );
3523
+ }
3524
+ return sdk.tool(def.name, def.description, schema.shape, async () => {
3525
+ throw new Error(
3526
+ `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)`
3527
+ );
3528
+ });
3529
+ });
3530
+ return sdk.createSdkMcpServer({ name: MCP_SERVER_NAME, version: "1.0.0", tools: sdkTools });
3531
+ }
3532
+ 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.";
3533
+ function describeCalls(calls) {
3534
+ return JSON.stringify(calls.map((c) => ({ name: stripMcpToolPrefix(c.name), input: c.input })));
3535
+ }
3536
+ async function collectLoginTurn(stream, opts) {
3537
+ let init;
3538
+ const toolCalls = [];
3539
+ let text = "";
3540
+ let thinkingBlocks = 0;
3541
+ let result;
3542
+ try {
3543
+ for await (const msg of stream) {
3544
+ if (msg.type === "system" && msg.subtype === "init") {
3545
+ const measured = msg;
3546
+ init = measured;
3547
+ opts.log(
3548
+ `[claude-code-login] apiKeySource=${measured.apiKeySource} claude_code_version=${measured.claude_code_version} model=${measured.model}`
3549
+ );
3550
+ if (measured.apiKeySource !== LOGIN_API_KEY_SOURCE) {
3551
+ throw new ClaudeCodeLoginAuthError(measured.apiKeySource);
3552
+ }
3553
+ } else if (msg.type === "assistant") {
3554
+ for (const block of msg.message.content) {
3555
+ if (block.type === "tool_use") {
3556
+ toolCalls.push({
3557
+ id: block.id,
3558
+ name: stripMcpToolPrefix(block.name),
3559
+ input: isRecord(block.input) ? toolArgsToJsonObject(block.input) : {}
3560
+ });
3561
+ } else if (block.type === "text") {
3562
+ text += block.text;
3563
+ } else if (block.type === "thinking" || block.type === "redacted_thinking") {
3564
+ thinkingBlocks += 1;
3565
+ }
3566
+ }
3567
+ } else if (msg.type === "result") {
3568
+ result = msg;
3569
+ }
3570
+ }
3571
+ } catch (e) {
3572
+ if (result === void 0) throw e;
3573
+ opts.log(
3574
+ `[claude-code-login] query() threw after its result message \u2014 the result decides (${e instanceof Error ? e.message : String(e)})`
3575
+ );
3576
+ }
3577
+ if (result === void 0) {
3578
+ throw new Error("claude-code-login: query() ended without a result message");
3579
+ }
3580
+ const usage = result.usage;
3581
+ const thinkingTokens = usage.output_tokens_details?.thinking_tokens ?? 0;
3582
+ opts.log(
3583
+ `[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}`
3584
+ );
3585
+ const streamSaw = describeCalls(toolCalls);
3586
+ const interceptSaw = describeCalls(opts.intercepted);
3587
+ if (streamSaw !== interceptSaw) {
3588
+ opts.warn(
3589
+ `[claude-code-login] intercept/stream disagreement \u2014 the stream's blocks win. intercepted=${interceptSaw} stream=${streamSaw}`
3590
+ );
3591
+ }
3592
+ if (result.subtype === "success") {
3593
+ if (result.is_error) {
3594
+ throw new ClaudeCodeLoginResultError(
3595
+ "success",
3596
+ [result.result],
3597
+ result.api_error_status ?? void 0
3598
+ );
3599
+ }
3600
+ } else if (result.subtype !== "error_max_turns") {
3601
+ const failed = result;
3602
+ throw new ClaudeCodeLoginResultError(failed.subtype, failed.errors, void 0);
3603
+ }
3604
+ return { init, toolCalls, text, result };
3605
+ }
3606
+
3303
3607
  // src/harness/llm-router.ts
3304
3608
  function addStrictSchemaConstraints(schema) {
3305
3609
  const result = { ...schema };
@@ -3494,17 +3798,6 @@ var LLMAgent = class {
3494
3798
  }
3495
3799
  }
3496
3800
  };
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
3801
  function resolveAnthropicSampling(model, temperature) {
3509
3802
  if (temperature === void 0) {
3510
3803
  return { request: {}, applied: { temperature: "provider-default" } };
@@ -3880,6 +4173,200 @@ var AnthropicAgent = class extends LLMAgent {
3880
4173
  }
3881
4174
  }
3882
4175
  };
4176
+ var ClaudeCodeLoginAgent = class extends LLMAgent {
4177
+ provider = "anthropic";
4178
+ resolveModel(model) {
4179
+ return model.startsWith("anthropic/") ? model.slice("anthropic/".length) : model;
4180
+ }
4181
+ async createClient() {
4182
+ const { query, tool, createSdkMcpServer } = await import('@anthropic-ai/claude-agent-sdk');
4183
+ return { query, tool, createSdkMcpServer };
4184
+ }
4185
+ async callText(model, systemPrompt, userPrompt, _maxTokens, _temperature) {
4186
+ const sdk = await this.getClient();
4187
+ const traceId = newLlmTraceId();
4188
+ const startedAt = Date.now();
4189
+ const resolvedModel = this.resolveModel(model);
4190
+ try {
4191
+ const turn = await this.apiCall(
4192
+ () => collectLoginTurn(
4193
+ sdk.query({
4194
+ prompt: userPrompt,
4195
+ options: buildLoginQueryOptions({ model: resolvedModel, systemPrompt })
4196
+ }),
4197
+ { intercepted: [], kind: "callText", choice: "none", log: console.log, warn: console.warn }
4198
+ )
4199
+ );
4200
+ const usage = turn.result.usage;
4201
+ const endedAt = Date.now();
4202
+ emitLlmTraceEvent({
4203
+ id: traceId,
4204
+ at: startedAt,
4205
+ endedAt,
4206
+ durationMs: endedAt - startedAt,
4207
+ provider: "claude-code-login",
4208
+ model: resolvedModel,
4209
+ kind: "callText",
4210
+ systemPrompt,
4211
+ userPrompt,
4212
+ result: {
4213
+ inputTokens: usage.input_tokens,
4214
+ outputTokens: usage.output_tokens,
4215
+ cacheCreated: usage.cache_creation_input_tokens,
4216
+ cacheRead: usage.cache_read_input_tokens,
4217
+ text: turn.text
4218
+ }
4219
+ });
4220
+ return { text: turn.text, inputTokens: usage.input_tokens, outputTokens: usage.output_tokens };
4221
+ } catch (e) {
4222
+ const endedAt = Date.now();
4223
+ emitLlmTraceEvent({
4224
+ id: traceId,
4225
+ at: startedAt,
4226
+ endedAt,
4227
+ durationMs: endedAt - startedAt,
4228
+ provider: "claude-code-login",
4229
+ model: resolvedModel,
4230
+ kind: "callText",
4231
+ systemPrompt,
4232
+ userPrompt,
4233
+ error: { message: e instanceof Error ? e.message : String(e) }
4234
+ });
4235
+ throw e;
4236
+ }
4237
+ }
4238
+ async callVision(model, systemPrompt, userPrompt, image, _maxTokens) {
4239
+ const sdk = await this.getClient();
4240
+ const traceId = newLlmTraceId();
4241
+ const startedAt = Date.now();
4242
+ const resolvedModel = this.resolveModel(model);
4243
+ try {
4244
+ const turn = await this.apiCall(
4245
+ () => collectLoginTurn(
4246
+ sdk.query({
4247
+ prompt: imageUserMessage(userPrompt, image),
4248
+ options: buildLoginQueryOptions({ model: resolvedModel, systemPrompt })
4249
+ }),
4250
+ { intercepted: [], kind: "callVision", choice: "none", log: console.log, warn: console.warn }
4251
+ )
4252
+ );
4253
+ const usage = turn.result.usage;
4254
+ const endedAt = Date.now();
4255
+ emitLlmTraceEvent({
4256
+ id: traceId,
4257
+ at: startedAt,
4258
+ endedAt,
4259
+ durationMs: endedAt - startedAt,
4260
+ provider: "claude-code-login",
4261
+ model: resolvedModel,
4262
+ kind: "callVision",
4263
+ systemPrompt,
4264
+ userPrompt,
4265
+ result: { inputTokens: usage.input_tokens, outputTokens: usage.output_tokens, text: turn.text }
4266
+ });
4267
+ return { text: turn.text, inputTokens: usage.input_tokens, outputTokens: usage.output_tokens };
4268
+ } catch (e) {
4269
+ const endedAt = Date.now();
4270
+ emitLlmTraceEvent({
4271
+ id: traceId,
4272
+ at: startedAt,
4273
+ endedAt,
4274
+ durationMs: endedAt - startedAt,
4275
+ provider: "claude-code-login",
4276
+ model: resolvedModel,
4277
+ kind: "callVision",
4278
+ systemPrompt,
4279
+ userPrompt,
4280
+ error: { message: e instanceof Error ? e.message : String(e) }
4281
+ });
4282
+ throw e;
4283
+ }
4284
+ }
4285
+ async callTools(model, systemPrompt, userPrompt, tools, toolChoice = "required", _scopedTools) {
4286
+ const sdk = await this.getClient();
4287
+ const traceId = newLlmTraceId();
4288
+ const startedAt = Date.now();
4289
+ const resolvedModel = this.resolveModel(model);
4290
+ const sentUserPrompt = toolChoice === "required" ? `${userPrompt}
4291
+
4292
+ ${LOGIN_REQUIRED_TOOL_INSTRUCTION}` : userPrompt;
4293
+ try {
4294
+ const turn = await this.apiCall(() => {
4295
+ const intercepted = [];
4296
+ const server = createLoginToolServer(sdk, tools);
4297
+ return collectLoginTurn(
4298
+ sdk.query({
4299
+ prompt: sentUserPrompt,
4300
+ options: buildLoginQueryOptions({
4301
+ model: resolvedModel,
4302
+ systemPrompt,
4303
+ tools: { server, canUseTool: createDenyingIntercept(intercepted) }
4304
+ })
4305
+ }),
4306
+ { intercepted, kind: "callTools", choice: toolChoice === "required" ? "required:instructed" : "auto", log: console.log, warn: console.warn }
4307
+ );
4308
+ });
4309
+ const usage = turn.result.usage;
4310
+ const cacheCreated = usage.cache_creation_input_tokens;
4311
+ const cacheRead = usage.cache_read_input_tokens;
4312
+ if (cacheCreated || cacheRead) {
4313
+ console.log(
4314
+ `[claude-code-login] callTools cache: created=${cacheCreated} read=${cacheRead} input=${usage.input_tokens} output=${usage.output_tokens}`
4315
+ );
4316
+ }
4317
+ const toolCalls = turn.toolCalls.map((c) => ({ id: c.id, name: c.name, input: c.input }));
4318
+ const endedAt = Date.now();
4319
+ emitLlmTraceEvent({
4320
+ id: traceId,
4321
+ at: startedAt,
4322
+ endedAt,
4323
+ durationMs: endedAt - startedAt,
4324
+ provider: "claude-code-login",
4325
+ model: resolvedModel,
4326
+ kind: "callTools",
4327
+ systemPrompt,
4328
+ userPrompt: sentUserPrompt,
4329
+ tools: summarizeTools(tools),
4330
+ result: {
4331
+ inputTokens: usage.input_tokens,
4332
+ outputTokens: usage.output_tokens,
4333
+ cacheCreated,
4334
+ cacheRead,
4335
+ toolCalls: toolCalls.map((c) => ({ name: c.name, input: c.input }))
4336
+ }
4337
+ });
4338
+ return {
4339
+ toolCalls,
4340
+ inputTokens: usage.input_tokens,
4341
+ outputTokens: usage.output_tokens,
4342
+ cacheReadTokens: cacheRead,
4343
+ cacheCreationTokens: cacheCreated,
4344
+ appliedToolChoice: "auto"
4345
+ };
4346
+ } catch (e) {
4347
+ const endedAt = Date.now();
4348
+ emitLlmTraceEvent({
4349
+ id: traceId,
4350
+ at: startedAt,
4351
+ endedAt,
4352
+ durationMs: endedAt - startedAt,
4353
+ provider: "claude-code-login",
4354
+ model: resolvedModel,
4355
+ kind: "callTools",
4356
+ systemPrompt,
4357
+ userPrompt: sentUserPrompt,
4358
+ tools: summarizeTools(tools),
4359
+ error: { message: e instanceof Error ? e.message : String(e) }
4360
+ });
4361
+ throw e;
4362
+ }
4363
+ }
4364
+ async callWithTools() {
4365
+ throw new Error(
4366
+ "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"
4367
+ );
4368
+ }
4369
+ };
3883
4370
  var OpenAIAgent = class extends LLMAgent {
3884
4371
  provider = "openai";
3885
4372
  resolveModel(model) {
@@ -3908,10 +4395,15 @@ var OpenAIAgent = class extends LLMAgent {
3908
4395
  }
3909
4396
  }
3910
4397
  }
4398
+ const textUsage = splitOpenAiUsage(
4399
+ response.usage?.input_tokens ?? 0,
4400
+ response.usage?.output_tokens ?? 0,
4401
+ response.usage?.input_tokens_details?.cached_tokens ?? 0
4402
+ );
3911
4403
  return {
3912
4404
  text,
3913
- inputTokens: response.usage?.input_tokens ?? 0,
3914
- outputTokens: response.usage?.output_tokens ?? 0,
4405
+ inputTokens: textUsage.tokens.input,
4406
+ outputTokens: textUsage.tokens.output,
3915
4407
  sampling: { temperature: temperature ?? "provider-default" }
3916
4408
  };
3917
4409
  }
@@ -3950,14 +4442,20 @@ var OpenAIAgent = class extends LLMAgent {
3950
4442
  const functionCalls = response.output.filter(
3951
4443
  (o) => o.type === "function_call"
3952
4444
  );
4445
+ const toolsUsage = splitOpenAiUsage(
4446
+ response.usage?.input_tokens ?? 0,
4447
+ response.usage?.output_tokens ?? 0,
4448
+ response.usage?.input_tokens_details?.cached_tokens ?? 0
4449
+ );
3953
4450
  return {
3954
4451
  toolCalls: functionCalls.map((fc) => ({
3955
4452
  id: fc.call_id,
3956
4453
  name: fc.name,
3957
4454
  input: JSON.parse(fc.arguments ?? "{}")
3958
4455
  })),
3959
- inputTokens: response.usage?.input_tokens ?? 0,
3960
- outputTokens: response.usage?.output_tokens ?? 0
4456
+ inputTokens: toolsUsage.tokens.input,
4457
+ outputTokens: toolsUsage.tokens.output,
4458
+ cacheReadTokens: toolsUsage.cacheReadTokens
3961
4459
  };
3962
4460
  }
3963
4461
  // Pending tool results — stored by sendToolResult, consumed by next callTools
@@ -3984,6 +4482,7 @@ var OpenAIAgent = class extends LLMAgent {
3984
4482
  ];
3985
4483
  let totalIn = 0;
3986
4484
  let totalOut = 0;
4485
+ let totalCacheRead = 0;
3987
4486
  let allText = "";
3988
4487
  for (let turn = 0; turn < maxTurns; turn++) {
3989
4488
  const response = await this.apiCall(
@@ -3994,8 +4493,14 @@ var OpenAIAgent = class extends LLMAgent {
3994
4493
  tools: openaiTools
3995
4494
  })
3996
4495
  );
3997
- totalIn += response.usage?.input_tokens ?? 0;
3998
- totalOut += response.usage?.output_tokens ?? 0;
4496
+ const turnUsage = splitOpenAiUsage(
4497
+ response.usage?.input_tokens ?? 0,
4498
+ response.usage?.output_tokens ?? 0,
4499
+ response.usage?.input_tokens_details?.cached_tokens ?? 0
4500
+ );
4501
+ totalIn += turnUsage.tokens.input;
4502
+ totalOut += turnUsage.tokens.output;
4503
+ totalCacheRead += turnUsage.cacheReadTokens;
3999
4504
  const functionCalls = response.output.filter(
4000
4505
  (o) => o.type === "function_call"
4001
4506
  );
@@ -4026,6 +4531,7 @@ var OpenAIAgent = class extends LLMAgent {
4026
4531
  text: allText,
4027
4532
  inputTokens: totalIn,
4028
4533
  outputTokens: totalOut,
4534
+ cacheReadTokens: totalCacheRead,
4029
4535
  turnsUsed: input.length
4030
4536
  };
4031
4537
  }
@@ -4051,6 +4557,10 @@ function functionCallSteps(interaction) {
4051
4557
  function toUserInputStep(text) {
4052
4558
  return { type: "user_input", content: [{ type: "text", text }] };
4053
4559
  }
4560
+ function googleInteractionUsage(usage) {
4561
+ const split = splitCacheInclusiveUsage(usage?.total_input_tokens ?? 0, usage?.total_output_tokens ?? 0, usage?.total_cached_tokens ?? 0);
4562
+ return { inputTokens: split.tokens.input, outputTokens: split.tokens.output, cacheReadTokens: split.cacheReadTokens };
4563
+ }
4054
4564
  var GoogleAgent = class extends LLMAgent {
4055
4565
  provider = "google";
4056
4566
  resolveModel(model) {
@@ -4080,8 +4590,9 @@ var GoogleAgent = class extends LLMAgent {
4080
4590
  );
4081
4591
  return {
4082
4592
  text: interactionText(interaction),
4083
- inputTokens: interaction.usage?.total_input_tokens ?? 0,
4084
- outputTokens: interaction.usage?.total_output_tokens ?? 0,
4593
+ // ggui#1186 (third site): `total_input_tokens` INCLUDES the cached part of the prompt — report the non-cached input.
4594
+ inputTokens: googleInteractionUsage(interaction.usage).inputTokens,
4595
+ outputTokens: googleInteractionUsage(interaction.usage).outputTokens,
4085
4596
  sampling: { temperature: temperature ?? "provider-default" }
4086
4597
  };
4087
4598
  }
@@ -4113,7 +4624,8 @@ var GoogleAgent = class extends LLMAgent {
4113
4624
  const usage = response.usageMetadata;
4114
4625
  const result = {
4115
4626
  text,
4116
- inputTokens: usage?.promptTokenCount ?? 0,
4627
+ // ggui#1186 (third site): `promptTokenCount` includes `cachedContentTokenCount` — report the non-cached input.
4628
+ inputTokens: splitCacheInclusiveUsage(usage?.promptTokenCount ?? 0, usage?.candidatesTokenCount ?? 0, usage?.cachedContentTokenCount ?? 0).tokens.input,
4117
4629
  outputTokens: usage?.candidatesTokenCount ?? 0
4118
4630
  };
4119
4631
  const endedAt = Date.now();
@@ -4257,8 +4769,8 @@ var GoogleAgent = class extends LLMAgent {
4257
4769
  }));
4258
4770
  return {
4259
4771
  toolCalls,
4260
- inputTokens: interaction.usage?.total_input_tokens ?? 0,
4261
- outputTokens: interaction.usage?.total_output_tokens ?? 0
4772
+ // ggui#1186 (third site): the coding loop reads this per-turn usage — the non-cached prompt as input, the cached subset reported.
4773
+ ...googleInteractionUsage(interaction.usage)
4262
4774
  };
4263
4775
  }
4264
4776
  // Pending tool results — stored by sendToolResult, consumed by next callTools
@@ -4282,6 +4794,7 @@ var GoogleAgent = class extends LLMAgent {
4282
4794
  }));
4283
4795
  let totalIn = 0;
4284
4796
  let totalOut = 0;
4797
+ let totalCacheRead = 0;
4285
4798
  let allText = "";
4286
4799
  let turnsUsed = 0;
4287
4800
  let interaction = await this.apiCall(
@@ -4295,8 +4808,10 @@ var GoogleAgent = class extends LLMAgent {
4295
4808
  for (let turn = 0; turn < maxTurns; turn++) {
4296
4809
  turnsUsed = turn + 1;
4297
4810
  if (interaction.usage) {
4298
- totalIn += interaction.usage.total_input_tokens ?? 0;
4299
- totalOut += interaction.usage.total_output_tokens ?? 0;
4811
+ const turnUsage = googleInteractionUsage(interaction.usage);
4812
+ totalIn += turnUsage.inputTokens;
4813
+ totalOut += turnUsage.outputTokens;
4814
+ totalCacheRead += turnUsage.cacheReadTokens;
4300
4815
  }
4301
4816
  allText += interactionText(interaction);
4302
4817
  const fnCalls = functionCallSteps(interaction);
@@ -4334,6 +4849,7 @@ var GoogleAgent = class extends LLMAgent {
4334
4849
  text: allText,
4335
4850
  inputTokens: totalIn,
4336
4851
  outputTokens: totalOut,
4852
+ cacheReadTokens: totalCacheRead,
4337
4853
  turnsUsed
4338
4854
  };
4339
4855
  }
@@ -4389,12 +4905,19 @@ var OpenRouterAgent = class extends LLMAgent {
4389
4905
  this.sessionMessages.push({ role: "system", content: systemPrompt });
4390
4906
  }
4391
4907
  this.sessionMessages.push({ role: "user", content: userPrompt });
4908
+ const requested = toolChoice ?? "auto";
4909
+ const appliedToolChoice = requested === "required" && anthropicRejectsForcedToolChoice(resolved) ? "auto" : requested;
4910
+ if (appliedToolChoice !== requested) {
4911
+ console.warn(
4912
+ `[openrouter] callTools: ${resolved} rejects forced tool_choice (HTTP 400); caller's 'required' downgraded to 'auto'`
4913
+ );
4914
+ }
4392
4915
  const response = await this.apiCall(
4393
4916
  () => client.chatCompletion({
4394
4917
  model: resolved,
4395
4918
  messages: this.sessionMessages,
4396
4919
  tools: orTools,
4397
- tool_choice: toolChoice ?? "auto"
4920
+ tool_choice: appliedToolChoice
4398
4921
  })
4399
4922
  );
4400
4923
  const choice = response.choices[0];
@@ -4413,7 +4936,8 @@ var OpenRouterAgent = class extends LLMAgent {
4413
4936
  return {
4414
4937
  toolCalls,
4415
4938
  inputTokens: response.usage.prompt_tokens,
4416
- outputTokens: response.usage.completion_tokens
4939
+ outputTokens: response.usage.completion_tokens,
4940
+ appliedToolChoice
4417
4941
  };
4418
4942
  }
4419
4943
  async sendToolResult(results) {
@@ -4484,6 +5008,14 @@ function createAgent(providerOrConfig) {
4484
5008
  const provider = typeof providerOrConfig === "string" ? providerOrConfig : providerOrConfig.provider;
4485
5009
  const routeOverride = typeof providerOrConfig === "string" ? void 0 : providerOrConfig.routeOverride;
4486
5010
  const onRetry = typeof providerOrConfig === "string" ? void 0 : providerOrConfig.onRetry;
5011
+ if (routeOverride?.claudeCodeLogin === true) {
5012
+ if (provider !== "anthropic") {
5013
+ throw new Error(
5014
+ `${provider}: the claude-code-login credential authenticates the Claude Code binary only \u2014 pick an anthropic route or supply a ${provider} API key`
5015
+ );
5016
+ }
5017
+ return new ClaudeCodeLoginAgent(routeOverride, onRetry);
5018
+ }
4487
5019
  switch (provider) {
4488
5020
  case "anthropic":
4489
5021
  return new AnthropicAgent(routeOverride, onRetry);