{"version":3,"file":"validation.cjs","names":[],"sources":["../../../../src/batteries/llm/claude_code_cli/validation.ts"],"sourcesContent":["/**\n * Runtime validation schema and wrapper for Claude Code CLI adapter options.\n *\n * @module @nhtio/adk/batteries/llm/claude_code_cli/validation\n *\n * @remarks\n * Schema and call-site wrapper for validating `ClaudeCodeCliAdapterOptions`. Used at construction\n * time and at the start of every iteration against the merged options shape (stash > executor >\n * constructor). Throws `E_INVALID_CLAUDE_CODE_CLI_OPTIONS` on failure — same hard-fail policy as\n * every other ADK contract.\n *\n * Three invariants enforced here that have no equivalent in the other LLM batteries:\n * - `apiKey`/`authToken` are mutually exclusive AND at least one is required (`.xor`).\n * - `extraArgs` entries are validated against a strict six-flag allowlist, with per-flag value\n *   arity, and every value string is rejected if it starts with `-` — the fix for the `--betas`\n *   variadic-value injection hazard (a value string spelling another flag would otherwise reach\n *   the CLI's own argv parser as a distinct token).\n * - `process.platform` is checked at validation time: this battery is POSIX-only in v1 (reliable\n *   process-group control requires POSIX process groups), mirroring\n *   `src/batteries/sandbox/node/srt_enforcer.ts`'s own platform-boundary precedent.\n */\n\nimport { isError } from '@nhtio/adk/guards'\nimport { validator, ValidationError } from '@nhtio/validation'\nimport { E_INVALID_CLAUDE_CODE_CLI_OPTIONS } from './exceptions'\nimport { byteStoreSchema, TokenEncoding } from '@nhtio/adk/common'\nimport type { ClaudeCodeCliAdapterOptions } from './types'\n\n// ─── Sub-schemas ──────────────────────────────────────────────────────────────\n\nconst bucketLabelSchema = validator\n  .string()\n  .valid('standingInstructions', 'memories', 'retrievables', 'timeline')\n\nconst bucketOrderSchema = validator\n  .array()\n  .items(bucketLabelSchema)\n  .unique()\n  .default(['standingInstructions', 'memories', 'retrievables', 'timeline'])\n\nconst tokenEncodingSchema = validator\n  .alternatives(\n    // Known values are suggestions from the canonical list, not a whitelist: consumers may provide\n    // a custom or newer tokenizer name. The field accepts any non-empty string, explicit null, or\n    // absent (undefined = \"no token counting\"). `.optional()` preserves the null/undefined\n    // disposition required by adk/require-validator-any-required.\n    validator\n      .string()\n      .min(1)\n      .description(`Known encodings: ${TokenEncoding.join(', ')}`),\n    validator.any().valid(null).optional()\n  )\n  .default(null)\n\nconst unsupportedMediaPolicySchema = validator\n  .alternatives(\n    validator.string().valid('throw', 'fallback-stash', 'synthetic-description'),\n    validator\n      .object({\n        mode: validator.string().valid('fallback-stash').required(),\n        stashKeys: validator.array().items(validator.string().min(1)).required(),\n      })\n      .unknown(false)\n  )\n  .default('throw')\n\nconst helperSchema = validator.function()\n\nconst helpersSchema = validator\n  .object({\n    descriptionToChatCompletionsJsonSchema: helperSchema.optional(),\n    renderUntrustedContent: helperSchema.optional(),\n    renderTrustedContent: helperSchema.optional(),\n    renderStandingInstructions: helperSchema.optional(),\n    renderMemories: helperSchema.optional(),\n    renderRetrievables: helperSchema.optional(),\n    renderRetrievableHandleBody: helperSchema.optional(),\n    renderRetrievableSafetyDirective: helperSchema.optional(),\n    renderFirstPartyRetrievables: helperSchema.optional(),\n    renderThirdPartyPublicRetrievables: helperSchema.optional(),\n    renderThirdPartyPrivateRetrievables: helperSchema.optional(),\n    renderThought: helperSchema.optional(),\n    filterThoughts: helperSchema.optional(),\n    toolsToChatCompletionsTools: helperSchema.optional(),\n    renderChatCompletionsSystemPrompt: helperSchema.optional(),\n    renderClaudeCodeCliTimelineMessage: helperSchema.optional(),\n    renderClaudeCodeCliToolCallResult: helperSchema.optional(),\n    buildClaudeCodeCliPrompt: helperSchema.optional(),\n  })\n  .unknown(false)\n\n// `extraArgs`: a structured allowlist, not a flat string[]. Every value string, in every\n// position, is rejected if it starts with `-` — the fix for the --betas injection hazard.\nconst NOT_A_FLAG_RE = /^(?!-).+$/\n\nconst singleValueSchema = validator.string().min(1).pattern(NOT_A_FLAG_RE)\nconst multiValueSchema = validator\n  .array()\n  .items(validator.string().min(1).pattern(NOT_A_FLAG_RE))\n  .min(1)\n\nconst extraArgSchema = validator\n  .alternatives(\n    // --betas is the only flag accepting a string[] value.\n    validator\n      .object({\n        flag: validator.string().valid('--betas').required(),\n        value: multiValueSchema.required(),\n      })\n      .unknown(false),\n    // --prompt-suggestions is the only flag with an OPTIONAL value.\n    validator\n      .object({\n        flag: validator.string().valid('--prompt-suggestions').required(),\n        value: singleValueSchema.optional(),\n      })\n      .unknown(false),\n    // Every remaining allowlisted flag requires a single non-empty, non-flag-shaped string value.\n    validator\n      .object({\n        flag: validator.string().valid('--effort', '--agent', '--json-schema', '--name').required(),\n        value: singleValueSchema.required(),\n      })\n      .unknown(false)\n  )\n  .required()\n\nconst extraArgsSchema = validator.array().items(extraArgSchema).optional()\n\n// ─── Top-level schema ─────────────────────────────────────────────────────────\n\n/**\n * Validator schema for `ClaudeCodeCliAdapterOptions`. Used by `validateOptions` at construction\n * time and again at the start of every iteration after options have been merged (stash > executor\n * > constructor). Rejects unknown top-level keys so typos fail loud.\n */\nexport const claudeCodeCliOptionsSchema = validator\n  .object<ClaudeCodeCliAdapterOptions>({\n    // ADK control\n    execa: validator.function().optional(),\n    wrapperPath: validator.string().optional(),\n    claudeBin: validator.string().default('claude'),\n    appendSystemPrompt: validator.string().optional(),\n    apiKey: validator.string().optional(),\n    authToken: validator.string().optional(),\n    baseURL: validator.string().optional(),\n    cwd: validator.string().optional(),\n    addDir: validator.array().items(validator.string().min(1)).optional(),\n    disallowedTools: validator.array().items(validator.string().min(1)).default([]),\n    maxTurns: validator\n      .number()\n      .valid(1)\n      .messages({\n        'any.only':\n          'maxTurns is deprecated and inert: single-turn dispatch is the fixed battery contract; ' +\n          'only maxTurns: 1 is accepted, and the option is ignored because argv always carries --max-turns 1.',\n      })\n      .optional(),\n    contextWindow: validator.number().integer().min(1).optional(),\n    tokenEncoding: tokenEncodingSchema,\n    maxBudgetUsd: validator.number().min(0).optional(),\n    fallbackModel: validator.array().items(validator.string().min(1)).optional(),\n    selfIdentity: validator.string().min(1).default('assistant'),\n    autoAck: validator.boolean().default(false),\n    forwardSubagentText: validator.boolean().default(false),\n    bucketOrder: bucketOrderSchema,\n    thoughtSurfacing: validator\n      .string()\n      .valid('all-self', 'latest-self', 'all')\n      .default('all-self'),\n    replayCompatibility: validator.array().items(validator.string().min(1)).default([]),\n    helpers: helpersSchema.optional(),\n    spoolStore: byteStoreSchema.optional(),\n    unsupportedMediaPolicy: unsupportedMediaPolicySchema,\n    unsupportedResultMediaPolicy: unsupportedMediaPolicySchema,\n    extraArgs: extraArgsSchema,\n\n    // CLI-native safety caps / timeouts\n    streamIdleTimeoutMs: validator.number().integer().min(0).default(60_000),\n    startupTimeoutMs: validator.number().integer().min(0).default(45_000),\n    disposeGraceMs: validator.number().integer().min(0).default(2_000),\n    mcpToolIdleTimeoutMs: validator.number().integer().min(0).optional(),\n    disableTelemetry: validator.boolean().optional(),\n    disableErrorReporting: validator.boolean().optional(),\n    disableNonessentialTraffic: validator.boolean().optional(),\n\n    // Required\n    model: validator.string().required(),\n  })\n  .unknown(false)\n  // Exactly one of apiKey/authToken must be set.\n  .xor('apiKey', 'authToken')\n  // POSIX-only in v1: reliable process-group control requires POSIX process groups, mirroring\n  // src/batteries/sandbox/node/srt_enforcer.ts's own platform-boundary precedent — refuse rather\n  // than silently degrade.\n  .custom((value, helpers) => {\n    // POSIX-only in v1: reliable process-group control requires process.kill(-pid, signal),\n    // which has no Windows equivalent — refuse rather than silently degrade, mirroring\n    // src/batteries/sandbox/node/srt_enforcer.ts's own platform-boundary precedent.\n    if (process.platform !== 'darwin' && process.platform !== 'linux') {\n      return helpers.error('any.invalid')\n    }\n    return value\n  })\n\nconst isValidationError = (value: unknown): value is ValidationError =>\n  isError(value) && Array.isArray((value as ValidationError).details)\n\nconst formatValidationDetails = (err: ValidationError): string =>\n  err.details.map((d) => d.message).join(' and ')\n\n/**\n * Validates an arbitrary input against `claudeCodeCliOptionsSchema` and returns the resolved\n * options shape. Throws `E_INVALID_CLAUDE_CODE_CLI_OPTIONS` (carrying the validator's error\n * report on `cause`) on failure.\n *\n * @param input - The raw options object to validate.\n * @returns The resolved options object with defaults filled in.\n */\nexport const validateOptions = (input: unknown): ClaudeCodeCliAdapterOptions => {\n  const { value, error } = claudeCodeCliOptionsSchema.validate(input, {\n    abortEarly: false,\n    convert: false,\n  })\n  if (error) {\n    throw new E_INVALID_CLAUDE_CODE_CLI_OPTIONS([formatValidationDetails(error)], { cause: error })\n  }\n  return value as ClaudeCodeCliAdapterOptions\n}\n\n// suppress unused import warning when the alias isn't referenced\nvoid isValidationError\n"],"mappings":"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"}