{"version":3,"file":"validation.mjs","names":[],"sources":["../../../../src/batteries/llm/litert_lm/validation.ts"],"sourcesContent":["/**\n * Runtime validation schema and wrapper for LiteRT-LM adapter options.\n *\n * @module @nhtio/adk/batteries/llm/litert_lm/validation\n */\n\nimport { isError } from '@nhtio/adk/guards'\nimport { E_INVALID_LITERT_LM_OPTIONS } from './exceptions'\nimport { validator, ValidationError } from '@nhtio/validation'\nimport { byteStoreSchema, TokenEncoding } from '@nhtio/adk/common'\nimport type { LiteRtLmAdapterOptions } from './types'\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 reasoningFieldPrecedenceSchema = validator\n  .array()\n  .items(validator.string().valid('reasoning', 'reasoning_content'))\n  .unique()\n  .min(1)\n  .default(['reasoning', 'reasoning_content'])\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 helperSchema = validator.function()\n\nconst helpersSchema = validator\n  .object({\n    descriptionToChatCompletionsJsonSchema: helperSchema.optional(),\n    renderUntrustedContent: helperSchema.optional(),\n    renderTrustedContent: helperSchema.optional(),\n    renderArtifactHandleBody: helperSchema.optional(),\n    renderRetrievableHandleBody: helperSchema.optional(),\n    renderStandingInstructions: helperSchema.optional(),\n    renderMemories: helperSchema.optional(),\n    renderRetrievables: helperSchema.optional(),\n    renderRetrievableSafetyDirective: helperSchema.optional(),\n    renderFirstPartyRetrievables: helperSchema.optional(),\n    renderThirdPartyPublicRetrievables: helperSchema.optional(),\n    renderThirdPartyPrivateRetrievables: helperSchema.optional(),\n    renderTimelineMessage: helperSchema.optional(),\n    renderThought: helperSchema.optional(),\n    filterThoughts: helperSchema.optional(),\n    toolsToChatCompletionsTools: helperSchema.optional(),\n    renderChatCompletionsSystemPrompt: helperSchema.optional(),\n    renderChatCompletionsToolCallResult: helperSchema.optional(),\n    buildChatCompletionsHistory: helperSchema.optional(),\n    createChatCompletionsToolCallDeltaAccumulator: helperSchema.optional(),\n  })\n  .unknown(false)\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\n// `model` accepts a URL string, a ReadableStream<Uint8Array>, or a Blob. The two object forms are\n// runtime instances Joi cannot introspect, so accept any non-string object and let `Engine.create`\n// reject a genuinely wrong shape — validating the *adapter* contract, not the provider's.\nconst modelSchema = validator\n  .alternatives(validator.string().min(1), validator.object().unknown(true))\n  .required()\n\n// NATIVE escape hatch — optional, no defaults (the shared generation resolver owns the defaults and\n// builds the effective samplerParams from the canonical `sampler`/`temperature`/`topK`/`topP`). When a\n// caller DOES pass samplerParams directly, the k<=1 invariant is enforced here so a bad combo fails at\n// validation with a clear message rather than exploding in the wasm runtime (`Top-K value N must be\n// <= 1`).\n//\n// WHY k<=1 for EVERY type (not just GREEDY): this battery runs on the WebGPU sampling path, where the\n// LiteRT runtime IGNORES `type` (it always combines top-k + top-p) and the WebGPU TopK sampler requires\n// `k <= 1` regardless of type (grounded in runtime/proto/sampler_params.proto — \"type … Ignored on the\n// GPU path\"). So TOP_K/TOP_P with k>1 throws at generate time just like GREEDY would. Diversity comes\n// from `p` + `temperature`, not `k`.\nconst samplerParamsSchema = validator\n  .object({\n    // 1=TOP_K, 2=TOP_P, 3=GREEDY (0=TYPE_UNSPECIFIED lets the runtime guess — disallowed here).\n    type: validator.number().integer().valid(1, 2, 3).optional(),\n    // The WebGPU sampling path requires k<=1 for ALL sampler types. Reject k>1 with a clear message.\n    k: validator\n      .number()\n      .integer()\n      .valid(0, 1)\n      .messages({\n        'any.only':\n          'LiteRT-LM runs on the WebGPU sampling path, which requires samplerParams.k <= 1 ' +\n          '(the runtime ignores the sampler type and combines top-k + top-p). Use k: 1 and tune ' +\n          'p/temperature for diversity.',\n      })\n      .optional(),\n    p: validator.number().min(0).max(1).optional(),\n    temperature: validator.number().min(0).optional(),\n    seed: validator.number().integer().optional(),\n  })\n  .unknown(false)\n  .optional()\n\n/**\n * Validator schema for {@link LiteRtLmAdapterOptions}. Rejects unknown keys (`.unknown(false)`) so\n * typos and removed fields fail loud, and fills in defaults.\n */\nexport const liteRtLmOptionsSchema = validator\n  .object<LiteRtLmAdapterOptions>({\n    // ── Engine ──\n    model: modelSchema,\n    engine: validator.object().unknown(true).optional(),\n    createEngine: validator.function().optional(),\n    onInitProgress: validator.function().optional(),\n    isWebGPUAvailable: validator.function().optional(),\n    forgeToolsFilter: validator.function().optional(),\n    inputPromptAsHint: validator.string().optional(),\n    // ── Generation: PORTABLE canonical contract (shared with transformers.js) ──\n    // Optional — the shared resolver fills deterministic defaults (greedy, temp 0.7, k 40, p 0.95, max\n    // 1024) and applies canonical-wins precedence over the LiteRT-native fields below.\n    maxTokens: validator.number().integer().min(1).optional(),\n    sampler: validator.string().valid('greedy', 'top-k', 'top-p').optional(),\n    temperature: validator.number().min(0).optional(),\n    // topK must be <= 1: this battery runs on the WebGPU sampling path, which requires k<=1 for ALL\n    // sampler types (the runtime ignores the type and combines top-k + top-p). A k>1 throws\n    // `Top-K value N must be <= 1` at generate time, so we reject it up front with guidance. Diversity\n    // comes from topP + temperature. (NOTE: the shared resolver's default topK of 40 is applied AFTER\n    // validation and is clamped to 1 in the adapter's #samplerParams — only an EXPLICIT topK>1 here is\n    // a caller error worth surfacing.)\n    topK: validator\n      .number()\n      .integer()\n      .valid(0, 1)\n      .messages({\n        'any.only':\n          'LiteRT-LM runs on the WebGPU sampling path, which requires topK <= 1 (the runtime ignores ' +\n          'the sampler type and combines top-k + top-p). Use topK: 1 and tune topP/temperature for ' +\n          'diversity.',\n      })\n      .optional(),\n    topP: validator.number().min(0).max(1).optional(),\n    seed: validator.number().integer().optional(),\n    multimodal: validator\n      .object({ image: validator.boolean().optional(), audio: validator.boolean().optional() })\n      .unknown(false)\n      .optional(),\n    // ── Generation (LiteRT-NATIVE escape hatches) ── consulted only when the canonical field is unset.\n    // `maxNumTokens` (the engine's total context budget) is LiteRT-only and pinned to a safe 4096.\n    samplerParams: samplerParamsSchema,\n    maxOutputTokens: validator.number().integer().min(1).optional(),\n    maxNumTokens: validator.number().integer().min(1).default(4096),\n    // Backend enum: 0..6 (UNSPECIFIED, CPU_ARTISAN, GPU_ARTISAN, CPU, GPU, GOOGLE_TENSOR_ARTISAN, NPU)\n    backend: validator.number().integer().min(0).max(6).optional(),\n    audioModalityEnabled: validator.boolean().optional(),\n    visionModalityEnabled: validator.boolean().optional(),\n    enableConstrainedDecoding: validator.boolean().optional(),\n    filterChannelContentFromKvCache: validator.boolean().optional(),\n    // ── ADK control ──\n    stream: validator.boolean().default(true),\n    bucketOrder: bucketOrderSchema,\n    contextWindow: validator.number().integer().min(1).optional(),\n    selfIdentity: validator.string().min(1).default('assistant'),\n    thoughtSurfacing: validator\n      .string()\n      .valid('all-self', 'latest-self', 'all')\n      .default('all-self'),\n    tokenEncoding: tokenEncodingSchema,\n    replayCompatibility: validator.array().items(validator.string().min(1)).default([]),\n    reasoningFieldPrecedence: reasoningFieldPrecedenceSchema,\n    helpers: helpersSchema.optional(),\n    spoolStore: byteStoreSchema.optional(),\n    unsupportedMediaPolicy: unsupportedMediaPolicySchema,\n    autoAck: validator.boolean().default(false),\n    // ── Lifecycle hooks (opt-in, normalized phase machine) ──\n    onLifecycle: validator.function().optional(),\n    onLoading: validator.function().optional(),\n    onCompiling: validator.function().optional(),\n    onReady: validator.function().optional(),\n    onGenerating: validator.function().optional(),\n    onComplete: validator.function().optional(),\n    onError: validator.function().optional(),\n    toolCallParser: validator\n      .alternatives(\n        validator\n          .string()\n          .valid(\n            'auto',\n            'hermes',\n            'gemma',\n            'gpt_oss',\n            'pythonic',\n            'llama3_json',\n            'mistral',\n            'qwen3_coder',\n            'phi',\n            'none'\n          ),\n        validator.function()\n      )\n      .default('auto'),\n    reasoningParser: validator\n      .alternatives(\n        validator.string().valid('auto', 'think_tag', 'harmony_analysis', 'gemma_channel', 'none'),\n        validator.function()\n      )\n      .default('auto'),\n    toolDelivery: validator.string().valid('prompt', 'native').default('prompt'),\n    enableThinking: validator.boolean().default(false),\n    reasoningOrphanRecovery: validator.boolean().default(true),\n    extractMediaOutputs: validator.function().optional(),\n    onRawGeneration: validator.function().optional(),\n    onPromptAssembled: validator.function().optional(),\n  })\n  .unknown(false)\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 raw adapter options against {@link liteRtLmOptionsSchema}, filling in defaults.\n *\n * @param input - The raw options object to validate.\n * @returns The resolved options object with defaults applied.\n * @throws {@link @nhtio/adk/batteries!E_INVALID_LITERT_LM_OPTIONS} when `input` is invalid.\n */\nexport const validateOptions = (input: unknown): LiteRtLmAdapterOptions => {\n  const { value, error } = liteRtLmOptionsSchema.validate(input, {\n    abortEarly: false,\n    convert: false,\n  })\n  if (error && isValidationError(error)) {\n    throw new E_INVALID_LITERT_LM_OPTIONS([formatValidationDetails(error)], {\n      cause: error,\n    })\n  }\n  return value as LiteRtLmAdapterOptions\n}\n"],"mappings":"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"}