@fgv/ts-extras 5.1.0-52 → 5.1.0-53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/packlets/ai-assist/completionClient.js +147 -19
- package/dist/packlets/ai-assist/completionClient.js.map +1 -1
- package/dist/packlets/ai-assist/index.js +2 -1
- package/dist/packlets/ai-assist/index.js.map +1 -1
- package/dist/packlets/ai-assist/jsonCompletion.js +20 -2
- package/dist/packlets/ai-assist/jsonCompletion.js.map +1 -1
- package/dist/packlets/ai-assist/model.js.map +1 -1
- package/dist/packlets/ai-assist/registry.js +39 -1
- package/dist/packlets/ai-assist/registry.js.map +1 -1
- package/dist/packlets/ai-assist/structuredOutput.js +315 -0
- package/dist/packlets/ai-assist/structuredOutput.js.map +1 -0
- package/dist/packlets/ai-assist/structuredOutputTypes.js +21 -0
- package/dist/packlets/ai-assist/structuredOutputTypes.js.map +1 -0
- package/dist/ts-extras.d.ts +229 -2
- package/lib/packlets/ai-assist/completionClient.d.ts +13 -0
- package/lib/packlets/ai-assist/completionClient.d.ts.map +1 -1
- package/lib/packlets/ai-assist/completionClient.js +146 -18
- package/lib/packlets/ai-assist/completionClient.js.map +1 -1
- package/lib/packlets/ai-assist/index.d.ts +3 -1
- package/lib/packlets/ai-assist/index.d.ts.map +1 -1
- package/lib/packlets/ai-assist/index.js +6 -2
- package/lib/packlets/ai-assist/index.js.map +1 -1
- package/lib/packlets/ai-assist/jsonCompletion.d.ts.map +1 -1
- package/lib/packlets/ai-assist/jsonCompletion.js +20 -2
- package/lib/packlets/ai-assist/jsonCompletion.js.map +1 -1
- package/lib/packlets/ai-assist/model.d.ts +38 -2
- package/lib/packlets/ai-assist/model.d.ts.map +1 -1
- package/lib/packlets/ai-assist/model.js.map +1 -1
- package/lib/packlets/ai-assist/registry.d.ts +20 -0
- package/lib/packlets/ai-assist/registry.d.ts.map +1 -1
- package/lib/packlets/ai-assist/registry.js +41 -1
- package/lib/packlets/ai-assist/registry.js.map +1 -1
- package/lib/packlets/ai-assist/structuredOutput.d.ts +88 -0
- package/lib/packlets/ai-assist/structuredOutput.d.ts.map +1 -0
- package/lib/packlets/ai-assist/structuredOutput.js +321 -0
- package/lib/packlets/ai-assist/structuredOutput.js.map +1 -0
- package/lib/packlets/ai-assist/structuredOutputTypes.d.ts +142 -0
- package/lib/packlets/ai-assist/structuredOutputTypes.d.ts.map +1 -0
- package/lib/packlets/ai-assist/structuredOutputTypes.js +22 -0
- package/lib/packlets/ai-assist/structuredOutputTypes.js.map +1 -0
- package/package.json +7 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/packlets/ai-assist/registry.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2026 Erik Fortune\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n/**\n * Centralized provider registry — single source of truth for all AI provider metadata.\n * @packageDocumentation\n */\n\nimport { fail, Result, succeed } from '@fgv/ts-utils';\n\nimport {\n type AiProviderId,\n type IAiEmbeddingModelCapability,\n type IAiImageModelCapability,\n type IAiModelCapabilityConfig,\n type IAiProviderDescriptor,\n resolveModelAlias\n} from './model';\n\n// ============================================================================\n// Built-in providers\n// ============================================================================\n\n/**\n * All known AI provider descriptors. Copy-paste first, then alphabetical.\n * @internal\n */\nconst BUILTIN_PROVIDERS: ReadonlyArray<IAiProviderDescriptor> = [\n {\n id: 'copy-paste',\n label: 'Copy / Paste',\n buttonLabel: 'AI Assist | Copy',\n needsSecret: false,\n apiFormat: 'openai',\n baseUrl: '',\n defaultModel: '',\n supportedTools: [],\n corsRestricted: false,\n streamingCorsRestricted: false,\n acceptsImageInput: false,\n thinkingMode: 'unsupported'\n },\n {\n id: 'anthropic',\n label: 'Anthropic Claude',\n buttonLabel: 'AI Assist | Claude',\n needsSecret: true,\n apiFormat: 'anthropic',\n baseUrl: 'https://api.anthropic.com/v1',\n defaultModel: {\n base: '@anthropic:sonnet', // claude-sonnet-5 (was 'claude-sonnet-4-5-20250929')\n advanced: '@anthropic:opus' // claude-opus-5\n // no frontier key → a frontier request cascades advanced → opus (see resolveModel)\n },\n aliases: {\n '@anthropic:sonnet': 'claude-sonnet-5', // base tier\n '@anthropic:opus': 'claude-opus-5', // advanced tier (was claude-opus-4-8; opus-5 is the drop-in successor at the same price)\n '@anthropic:haiku': 'claude-haiku-4-5-20251001', // NON-tier alias; modelOverride only\n '@anthropic:fable': 'claude-fable-5' // NON-tier alias; modelOverride only\n // NOTE: no thinking/image/embedding keys — Anthropic completions are all text; base\n // (sonnet-5) and advanced (opus-5) are both thinking-capable, so a thinking-context\n // call flat-falls to base safely.\n },\n supportedTools: ['web_search'],\n corsRestricted: false,\n streamingCorsRestricted: false,\n acceptsImageInput: true,\n thinkingMode: 'optional',\n // Claude 5 family requires the adaptive thinking wire shape (thinking.type: 'adaptive' +\n // output_config.effort) and 400s on the legacy thinking.type: 'enabled' + budget_tokens\n // shape; see AiAssist.isAdaptiveThinkingModel.\n adaptiveThinkingModelPrefixes: ['claude-sonnet-5', 'claude-opus-5', 'claude-fable-5']\n },\n {\n id: 'google-gemini',\n label: 'Google Gemini',\n buttonLabel: 'AI Assist | Gemini',\n needsSecret: true,\n apiFormat: 'gemini',\n baseUrl: 'https://generativelanguage.googleapis.com/v1beta',\n defaultModel: {\n base: '@google-gemini:flash',\n advanced: '@google-gemini:pro', // reuses the existing @google-gemini:pro alias (no new alias entry)\n image: '@google-gemini:flash-image',\n embedding: '@google-gemini:embedding'\n // no frontier key → a frontier request cascades advanced → pro (see resolveModel)\n },\n aliases: {\n // NOTE: the base flash line is at 3.5 while pro / flash-lite / flash-image are at 3.1 — this is\n // NOT a typo. The targets come verbatim from Google's official deprecation table: the flash base\n // line advanced to 3.5 while the other roles are on the 3.1 generation. The per-role version\n // split is exactly why the alias layer exists — consumers never see these numbers.\n '@google-gemini:flash': 'gemini-3.5-flash', // base (was gemini-2.5-flash, shutdown 2026-10-16)\n '@google-gemini:pro': 'gemini-3.1-pro-preview', // advanced-tier role (wired to the 'advanced' defaultModel key); also the frontier cascade target (was gemini-2.5-pro, 2026-10-16)\n '@google-gemini:flash-lite': 'gemini-3.1-flash-lite', // cheaper thinking-capable line; available via modelOverride only (was gemini-2.5-flash-lite, 2026-10-16)\n '@google-gemini:flash-image': 'gemini-3.1-flash-image', // image (GA id; was gemini-3.1-flash-image-preview, retired 2026-06-25)\n '@google-gemini:embedding': 'gemini-embedding-001' // NOT deprecated — aliased for uniformity only\n },\n supportedTools: ['web_search'],\n corsRestricted: false,\n streamingCorsRestricted: false,\n acceptsImageInput: true,\n thinkingMode: 'optional',\n embedding: [\n {\n modelPrefix: '',\n format: 'gemini-embeddings',\n supportsDimensions: true,\n supportsTaskType: true,\n defaultDimensions: 3072\n }\n ],\n imageGeneration: [\n {\n // Gemini Flash Image: chat-style generateContent\n modelPrefix: '',\n format: 'gemini-image-out',\n acceptsImageReferenceInput: true,\n supportsQualityParam: false,\n maxCount: 1,\n outputParamStyle: 'none',\n defaultOutputMimeType: 'image/jpeg'\n }\n ]\n },\n {\n id: 'groq',\n label: 'Groq',\n buttonLabel: 'AI Assist | Groq',\n needsSecret: true,\n apiFormat: 'openai',\n baseUrl: 'https://api.groq.com/openai/v1',\n defaultModel: 'llama-3.3-70b-versatile',\n supportedTools: [],\n corsRestricted: false,\n streamingCorsRestricted: false,\n acceptsImageInput: false,\n thinkingMode: 'unsupported'\n },\n {\n id: 'mistral',\n label: 'Mistral',\n buttonLabel: 'AI Assist | Mistral',\n needsSecret: true,\n apiFormat: 'openai',\n baseUrl: 'https://api.mistral.ai/v1',\n defaultModel: { base: 'mistral-large-latest', embedding: 'mistral-embed' },\n supportedTools: [],\n corsRestricted: false,\n streamingCorsRestricted: false,\n acceptsImageInput: false,\n thinkingMode: 'unsupported',\n embedding: [{ modelPrefix: '', format: 'openai-embeddings' }]\n },\n {\n id: 'ollama',\n label: 'Ollama (self-hosted)',\n buttonLabel: 'AI Assist | Ollama',\n needsSecret: false,\n apiFormat: 'openai',\n baseUrl: 'http://localhost:11434/v1',\n defaultModel: '',\n supportedTools: [],\n corsRestricted: false,\n streamingCorsRestricted: false,\n acceptsImageInput: false,\n thinkingMode: 'unsupported',\n embedding: [{ modelPrefix: '', format: 'openai-embeddings' }]\n },\n {\n id: 'openai',\n label: 'OpenAI',\n buttonLabel: 'AI Assist | OpenAI',\n needsSecret: true,\n apiFormat: 'openai',\n baseUrl: 'https://api.openai.com/v1',\n defaultModel: {\n base: '@openai:mini', // gpt-5.6-luna (was gpt-5.4-mini; before that 'gpt-4o' — EOL-behind)\n advanced: '@openai:flagship', // gpt-5.6-terra\n // The gpt-5.6 family works on BOTH Chat Completions and the Responses API, so the\n // frontier tier no longer needs Responses-only routing. gpt-5.5-pro (the previous\n // frontier target) remains Responses-API-only and reachable via modelOverride; the\n // `responsesOnlyModelPrefixes` marker below still routes it correctly.\n frontier: '@openai:pro', // gpt-5.6-sol\n image: '@openai:image', // gpt-image-2 (was gpt-image-1.5; before that 'dall-e-3' — EOL 2026-05-12)\n embedding: '@openai:embedding' // text-embedding-3-small (unchanged, aliased for uniformity)\n },\n aliases: {\n '@openai:mini': 'gpt-5.6-luna', // base tier (was gpt-5.4-mini)\n '@openai:flagship': 'gpt-5.6-terra', // advanced tier (was gpt-5.5)\n '@openai:pro': 'gpt-5.6-sol', // frontier tier (was gpt-5.5-pro, which was Responses-API-only; 5.6 works on chat completions)\n '@openai:nano': 'gpt-5.4-nano', // NON-tier alias; modelOverride only\n '@openai:image': 'gpt-image-2', // image (matches the gpt-image- capability prefix; was gpt-image-1.5)\n '@openai:embedding': 'text-embedding-3-small' // NOT deprecated — aliased for uniformity\n // NOTE: gpt-5.1 deliberately absent — retired March 2026.\n },\n supportedTools: ['web_search'],\n corsRestricted: false,\n streamingCorsRestricted: false,\n acceptsImageInput: true,\n thinkingMode: 'optional',\n responsesOnlyModelPrefixes: ['gpt-5.5-pro'],\n embedding: [\n {\n modelPrefix: 'text-embedding-3',\n format: 'openai-embeddings',\n supportsDimensions: true,\n maxBatchSize: 2048\n },\n {\n modelPrefix: '',\n format: 'openai-embeddings'\n }\n ],\n imageGeneration: [\n {\n modelPrefix: 'gpt-image-',\n format: 'openai-images',\n acceptsImageReferenceInput: true,\n acceptedSizes: ['1024x1024', '1536x1024', '1024x1536', 'auto'],\n supportsQualityParam: true,\n acceptedQualities: ['low', 'medium', 'high', 'auto'],\n maxCount: 10,\n outputParamStyle: 'output-format',\n defaultOutputMimeType: 'image/png'\n },\n {\n modelPrefix: '',\n format: 'openai-images',\n outputParamStyle: 'response-format',\n defaultOutputMimeType: 'image/png'\n }\n ]\n },\n {\n id: 'openai-compat',\n label: 'OpenAI-compatible (self-hosted)',\n buttonLabel: 'AI Assist | OpenAI-compat',\n needsSecret: false,\n apiFormat: 'openai',\n baseUrl: '',\n defaultModel: '',\n supportedTools: [],\n corsRestricted: false,\n streamingCorsRestricted: false,\n acceptsImageInput: false,\n thinkingMode: 'unsupported',\n embedding: [{ modelPrefix: '', format: 'openai-embeddings' }]\n },\n {\n id: 'xai-grok',\n label: 'xAI Grok',\n buttonLabel: 'AI Assist | Grok',\n needsSecret: true,\n apiFormat: 'openai',\n baseUrl: 'https://api.x.ai/v1',\n defaultModel: {\n base: '@xai-grok:standard', // grok-4.3 (was the raw id; the cheap line + retirement-wave redirect target)\n advanced: '@xai-grok:flagship', // grok-4.5 (flagship since 2026-07-08; $2/$6 sits in the advanced band)\n image: '@xai-grok:imagine' // grok-imagine-image-quality (was the raw id)\n // no frontier key → a frontier request cascades advanced → grok-4.5 (see resolveModel)\n },\n aliases: {\n '@xai-grok:standard': 'grok-4.3', // base tier; NOT deprecated — superseded as flagship by grok-4.5\n '@xai-grok:flagship': 'grok-4.5', // advanced tier; configurable reasoning effort (default high)\n '@xai-grok:imagine': 'grok-imagine-image-quality' // image tier; current (redirect target for the retired -pro)\n },\n supportedTools: ['web_search'],\n corsRestricted: true,\n streamingCorsRestricted: true,\n acceptsImageInput: true,\n thinkingMode: 'optional',\n imageGeneration: [\n {\n // grok-imagine models use JSON edits with image_url objects (different wire format)\n modelPrefix: 'grok-imagine-',\n format: 'xai-images-edits',\n acceptsImageReferenceInput: true,\n supportsQualityParam: false,\n maxCount: 10,\n outputParamStyle: 'response-format',\n defaultOutputMimeType: 'image/jpeg'\n },\n {\n // catch-all for other xai image models\n modelPrefix: '',\n format: 'xai-images',\n acceptsImageReferenceInput: false,\n supportsQualityParam: false,\n maxCount: 10,\n outputParamStyle: 'response-format',\n defaultOutputMimeType: 'image/jpeg'\n }\n ]\n }\n];\n\n/**\n * Index for O(1) lookup by id.\n * @internal\n */\nconst PROVIDER_BY_ID: ReadonlyMap<string, IAiProviderDescriptor> = new Map(\n BUILTIN_PROVIDERS.map((d) => [d.id, d])\n);\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * All valid provider ID values, in the same order as the registry.\n * @public\n */\nexport const allProviderIds: ReadonlyArray<AiProviderId> = BUILTIN_PROVIDERS.map((d) => d.id);\n\n/**\n * Get all known provider descriptors. Copy-paste first, then alphabetical.\n * @returns All built-in provider descriptors\n * @public\n */\nexport function getProviderDescriptors(): ReadonlyArray<IAiProviderDescriptor> {\n return BUILTIN_PROVIDERS;\n}\n\n/**\n * Get a provider descriptor by id.\n * @param id - The provider identifier\n * @returns The descriptor, or a failure if the provider is unknown\n * @public\n */\nexport function getProviderDescriptor(id: string): Result<IAiProviderDescriptor> {\n const descriptor = PROVIDER_BY_ID.get(id);\n if (!descriptor) {\n return fail(`unknown AI provider: ${id}`);\n }\n return succeed(descriptor);\n}\n\n/**\n * Whether a provider declares any image-generation capability at all.\n *\n * @param descriptor - The provider descriptor\n * @returns `true` when {@link IAiProviderDescriptor.imageGeneration} has at\n * least one entry; `false` otherwise.\n * @public\n */\nexport function supportsImageGeneration(descriptor: IAiProviderDescriptor): boolean {\n return (descriptor.imageGeneration?.length ?? 0) > 0;\n}\n\n/**\n * Shared longest-prefix capability match, alias-guarded.\n *\n * @remarks\n * `modelId` is resolved through `resolveModelAlias` **before** any prefix\n * matching, so an fgv alias (`@<provider>:<role>`) and the concrete id it names\n * select the same capability. Without this step an alias would fall through to\n * the `modelPrefix: ''` catch-all every provider declares and yield a\n * confidently wrong capability rather than no capability.\n *\n * An unresolvable alias (sigil-prefixed but unregistered, or cyclic) names no\n * model, so no capability applies and the match is skipped entirely — it must\n * never be prefix-matched verbatim. Callers already treat `undefined` as \"this\n * provider has no capability for this model\" and fail with that message.\n *\n * A concrete (non-sigil) id passes through `resolveModelAlias` verbatim, so\n * behavior for every raw provider id is unchanged.\n * @internal\n */\nfunction resolveCapabilityForModel<TCapability extends { readonly modelPrefix: string }>(\n descriptor: IAiProviderDescriptor,\n modelId: string,\n capabilities: ReadonlyArray<TCapability> | undefined\n): TCapability | undefined {\n const concreteModelId = resolveModelAlias(descriptor, modelId).orDefault();\n if (concreteModelId === undefined) {\n return undefined;\n }\n return (capabilities ?? [])\n .filter((cap) => concreteModelId.startsWith(cap.modelPrefix))\n .reduce<TCapability | undefined>(\n (best, cap) => (best && best.modelPrefix.length >= cap.modelPrefix.length ? best : cap),\n undefined\n );\n}\n\n/**\n * Resolve the image-generation capability that applies to a given model id\n * for a provider. Returns the entry from\n * {@link IAiProviderDescriptor.imageGeneration} whose `modelPrefix` is the\n * longest prefix of `modelId`. Ties are broken by first-encountered, so rule\n * order does not matter for correctness — only for tie-breaking among rules\n * with identical-length prefixes (an unusual case).\n *\n * @remarks\n * `modelId` may be either a concrete provider model id or an fgv model alias\n * (`@<provider>:<role>`, see `MODEL_ALIAS_SIGIL`) — it is resolved via\n * `resolveModelAlias` against `descriptor.aliases` before prefix matching,\n * so both forms select the same capability. A raw provider id passes through\n * unchanged. An alias that is not registered on `descriptor` (or is cyclic)\n * names no model and yields `undefined` rather than falling through to the\n * `modelPrefix: ''` catch-all.\n *\n * @param descriptor - The provider descriptor\n * @param modelId - The image model id — concrete or an fgv alias\n * @returns The matching capability, or `undefined` when no rule matches, the\n * provider declares no image-generation capabilities, or `modelId` is an\n * unresolvable alias.\n * @public\n */\nexport function resolveImageCapability(\n descriptor: IAiProviderDescriptor,\n modelId: string\n): IAiImageModelCapability | undefined {\n return resolveCapabilityForModel(descriptor, modelId, descriptor.imageGeneration);\n}\n\n/**\n * Whether a provider declares any embedding capability at all.\n *\n * @param descriptor - The provider descriptor\n * @returns `true` when {@link IAiProviderDescriptor.embedding} has at least one\n * entry; `false` otherwise.\n * @public\n */\nexport function supportsEmbedding(descriptor: IAiProviderDescriptor): boolean {\n return (descriptor.embedding?.length ?? 0) > 0;\n}\n\n/**\n * Resolve the embedding capability that applies to a given model id for a\n * provider. Returns the entry from {@link IAiProviderDescriptor.embedding} whose\n * `modelPrefix` is the longest prefix of `modelId`. Ties are broken by\n * first-encountered.\n *\n * @remarks\n * `modelId` may be either a concrete provider model id or an fgv model alias\n * (`@<provider>:<role>`, see `MODEL_ALIAS_SIGIL`) — it is resolved via\n * `resolveModelAlias` against `descriptor.aliases` before prefix matching,\n * so both forms select the same capability. A raw provider id passes through\n * unchanged. An alias that is not registered on `descriptor` (or is cyclic)\n * names no model and yields `undefined` rather than falling through to the\n * `modelPrefix: ''` catch-all.\n *\n * @param descriptor - The provider descriptor\n * @param modelId - The embedding model id — concrete or an fgv alias\n * @returns The matching capability, or `undefined` when no rule matches, the\n * provider declares no embedding capabilities, or `modelId` is an\n * unresolvable alias.\n * @public\n */\nexport function resolveEmbeddingCapability(\n descriptor: IAiProviderDescriptor,\n modelId: string\n): IAiEmbeddingModelCapability | undefined {\n return resolveCapabilityForModel(descriptor, modelId, descriptor.embedding);\n}\n\n// ============================================================================\n// Default model capability config\n// ============================================================================\n\n/**\n * Default capability config used by `callProviderListModels` when callers\n * don't supply their own. Patterns are intentionally narrow — false\n * positives are worse than missing a model. Caller can override per call\n * via {@link IProviderListModelsParams.capabilityConfig}.\n *\n * @public\n */\nexport const DEFAULT_MODEL_CAPABILITY_CONFIG: IAiModelCapabilityConfig = {\n perProvider: {\n openai: [\n { idPattern: /^gpt-image/, capabilities: ['image-generation'] },\n { idPattern: /^text-embedding/, capabilities: ['embedding'] },\n { idPattern: /^gpt-5/, capabilities: ['chat', 'tools', 'vision', 'thinking'] },\n { idPattern: /^gpt-4/, capabilities: ['chat', 'tools', 'vision'] },\n { idPattern: /^gpt-3\\.5/, capabilities: ['chat'] },\n { idPattern: /^o\\d/, capabilities: ['chat', 'tools', 'thinking'] }\n ],\n 'xai-grok': [\n { idPattern: /-image/, capabilities: ['image-generation'] },\n // grok-4.5 needs its own rule: it would otherwise hit only /^grok-4/ and lose\n // thinking, yet it has configurable reasoning effort. Detection accumulates across\n // matching rules, so /^grok-4/ still contributes vision (same as grok-4.3).\n { idPattern: /^grok-4\\.5/, capabilities: ['chat', 'tools', 'thinking'] },\n { idPattern: /^grok-4\\.3/, capabilities: ['chat', 'tools', 'thinking'] },\n { idPattern: /^grok-4$/, capabilities: ['chat', 'tools', 'thinking'] },\n { idPattern: /^grok-4/, capabilities: ['chat', 'tools', 'vision'] },\n { idPattern: /^grok-3-mini/, capabilities: ['chat', 'tools', 'thinking'] },\n { idPattern: /^grok-3/, capabilities: ['chat', 'tools'] },\n { idPattern: /^grok-2/, capabilities: ['chat', 'vision'] }\n ],\n 'google-gemini': [\n { idPattern: /^imagen/, capabilities: ['image-generation'] },\n { idPattern: /^gemini-.*-image/, capabilities: ['image-generation'] },\n { idPattern: /embedding/, capabilities: ['embedding'] },\n { idPattern: /^gemini-3/, capabilities: ['chat', 'tools', 'vision', 'thinking'] },\n { idPattern: /^gemini-2\\.5/, capabilities: ['chat', 'tools', 'vision', 'thinking'] },\n { idPattern: /^gemini-/, capabilities: ['chat', 'tools', 'vision'] }\n ],\n anthropic: [\n // Broadened from /^claude-opus-4/ and /^claude-sonnet-4/ so the sonnet-5+ / opus-5+ lines\n // are detected as thinking-capable. Detection accumulates across matching rules, so this is\n // purely additive: every existing opus-4 / sonnet-4 id still matches. Both broadenings are\n // now load-bearing: claude-sonnet-5 and claude-opus-5 (the advanced-tier target) would\n // otherwise hit only /^claude-/ and lose thinking. The fable rule keeps the\n // AnthropicThinkingModelNames union honest — claude-fable-5 (modelOverride-only) would\n // otherwise fall to the /^claude-/ catch-all and be detected without thinking.\n { idPattern: /^claude-opus-/, capabilities: ['chat', 'tools', 'vision', 'thinking'] },\n { idPattern: /^claude-sonnet-/, capabilities: ['chat', 'tools', 'vision', 'thinking'] },\n { idPattern: /^claude-fable-/, capabilities: ['chat', 'tools', 'vision', 'thinking'] },\n { idPattern: /^claude-/, capabilities: ['chat', 'tools', 'vision'] }\n ],\n groq: [{ idPattern: /./, capabilities: ['chat'] }],\n mistral: [\n { idPattern: /embed/, capabilities: ['embedding'] },\n { idPattern: /./, capabilities: ['chat'] }\n ],\n // Self-hosted OpenAI-compatible servers (Ollama, LM Studio, llama.cpp) serve\n // arbitrary, caller-chosen models whose ids we can't enumerate ahead of time.\n // The catch-all `/./` intentionally departs from the \"narrow patterns\" rule\n // above: assume `chat` for everything and let the caller override via\n // `capabilityConfig` when they know their deployment serves image/embedding models.\n ollama: [{ idPattern: /./, capabilities: ['chat'] }],\n 'openai-compat': [{ idPattern: /./, capabilities: ['chat'] }]\n }\n};\n"]}
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/packlets/ai-assist/registry.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2026 Erik Fortune\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n/**\n * Centralized provider registry — single source of truth for all AI provider metadata.\n * @packageDocumentation\n */\n\nimport { fail, Result, succeed } from '@fgv/ts-utils';\n\nimport {\n type AiProviderId,\n type IAiEmbeddingModelCapability,\n type IAiImageModelCapability,\n type IAiModelCapabilityConfig,\n type IAiProviderDescriptor,\n resolveModelAlias\n} from './model';\nimport type { IAiStructuredOutputCapability } from './structuredOutputTypes';\n\n// ============================================================================\n// Built-in providers\n// ============================================================================\n\n/**\n * All known AI provider descriptors. Copy-paste first, then alphabetical.\n * @internal\n */\nconst BUILTIN_PROVIDERS: ReadonlyArray<IAiProviderDescriptor> = [\n {\n id: 'copy-paste',\n label: 'Copy / Paste',\n buttonLabel: 'AI Assist | Copy',\n needsSecret: false,\n apiFormat: 'openai',\n baseUrl: '',\n defaultModel: '',\n supportedTools: [],\n corsRestricted: false,\n streamingCorsRestricted: false,\n acceptsImageInput: false,\n thinkingMode: 'unsupported'\n },\n {\n id: 'anthropic',\n label: 'Anthropic Claude',\n buttonLabel: 'AI Assist | Claude',\n needsSecret: true,\n apiFormat: 'anthropic',\n baseUrl: 'https://api.anthropic.com/v1',\n defaultModel: {\n base: '@anthropic:sonnet', // claude-sonnet-5 (was 'claude-sonnet-4-5-20250929')\n advanced: '@anthropic:opus' // claude-opus-5\n // no frontier key → a frontier request cascades advanced → opus (see resolveModel)\n },\n aliases: {\n '@anthropic:sonnet': 'claude-sonnet-5', // base tier\n '@anthropic:opus': 'claude-opus-5', // advanced tier (was claude-opus-4-8; opus-5 is the drop-in successor at the same price)\n '@anthropic:haiku': 'claude-haiku-4-5-20251001', // NON-tier alias; modelOverride only\n '@anthropic:fable': 'claude-fable-5' // NON-tier alias; modelOverride only\n // NOTE: no thinking/image/embedding keys — Anthropic completions are all text; base\n // (sonnet-5) and advanced (opus-5) are both thinking-capable, so a thinking-context\n // call flat-falls to base safely.\n },\n supportedTools: ['web_search'],\n corsRestricted: false,\n streamingCorsRestricted: false,\n acceptsImageInput: true,\n thinkingMode: 'optional',\n // Claude 5 family requires the adaptive thinking wire shape (thinking.type: 'adaptive' +\n // output_config.effort) and 400s on the legacy thinking.type: 'enabled' + budget_tokens\n // shape; see AiAssist.isAdaptiveThinkingModel.\n adaptiveThinkingModelPrefixes: ['claude-sonnet-5', 'claude-opus-5', 'claude-fable-5'],\n // Anthropic has no response-format field; forced tool use is the mechanism, and\n // it is uniform across the family — hence one catch-all entry.\n structuredOutput: [{ modelPrefix: '', format: 'anthropic-tool-forced' }]\n },\n {\n id: 'google-gemini',\n label: 'Google Gemini',\n buttonLabel: 'AI Assist | Gemini',\n needsSecret: true,\n apiFormat: 'gemini',\n baseUrl: 'https://generativelanguage.googleapis.com/v1beta',\n defaultModel: {\n base: '@google-gemini:flash',\n advanced: '@google-gemini:pro', // reuses the existing @google-gemini:pro alias (no new alias entry)\n image: '@google-gemini:flash-image',\n embedding: '@google-gemini:embedding'\n // no frontier key → a frontier request cascades advanced → pro (see resolveModel)\n },\n aliases: {\n // NOTE: the base flash line is at 3.5 while pro / flash-lite / flash-image are at 3.1 — this is\n // NOT a typo. The targets come verbatim from Google's official deprecation table: the flash base\n // line advanced to 3.5 while the other roles are on the 3.1 generation. The per-role version\n // split is exactly why the alias layer exists — consumers never see these numbers.\n '@google-gemini:flash': 'gemini-3.5-flash', // base (was gemini-2.5-flash, shutdown 2026-10-16)\n '@google-gemini:pro': 'gemini-3.1-pro-preview', // advanced-tier role (wired to the 'advanced' defaultModel key); also the frontier cascade target (was gemini-2.5-pro, 2026-10-16)\n '@google-gemini:flash-lite': 'gemini-3.1-flash-lite', // cheaper thinking-capable line; available via modelOverride only (was gemini-2.5-flash-lite, 2026-10-16)\n '@google-gemini:flash-image': 'gemini-3.1-flash-image', // image (GA id; was gemini-3.1-flash-image-preview, retired 2026-06-25)\n '@google-gemini:embedding': 'gemini-embedding-001' // NOT deprecated — aliased for uniformity only\n },\n supportedTools: ['web_search'],\n corsRestricted: false,\n streamingCorsRestricted: false,\n acceptsImageInput: true,\n thinkingMode: 'optional',\n // Gemini carries the constraint inside `generationConfig`\n // (responseMimeType + responseSchema), uniform across the family.\n structuredOutput: [{ modelPrefix: '', format: 'gemini-response-schema' }],\n embedding: [\n {\n modelPrefix: '',\n format: 'gemini-embeddings',\n supportsDimensions: true,\n supportsTaskType: true,\n defaultDimensions: 3072\n }\n ],\n imageGeneration: [\n {\n // Gemini Flash Image: chat-style generateContent\n modelPrefix: '',\n format: 'gemini-image-out',\n acceptsImageReferenceInput: true,\n supportsQualityParam: false,\n maxCount: 1,\n outputParamStyle: 'none',\n defaultOutputMimeType: 'image/jpeg'\n }\n ]\n },\n {\n id: 'groq',\n label: 'Groq',\n buttonLabel: 'AI Assist | Groq',\n needsSecret: true,\n apiFormat: 'openai',\n baseUrl: 'https://api.groq.com/openai/v1',\n defaultModel: 'llama-3.3-70b-versatile',\n supportedTools: [],\n corsRestricted: false,\n streamingCorsRestricted: false,\n acceptsImageInput: false,\n thinkingMode: 'unsupported'\n },\n {\n id: 'mistral',\n label: 'Mistral',\n buttonLabel: 'AI Assist | Mistral',\n needsSecret: true,\n apiFormat: 'openai',\n baseUrl: 'https://api.mistral.ai/v1',\n defaultModel: { base: 'mistral-large-latest', embedding: 'mistral-embed' },\n supportedTools: [],\n corsRestricted: false,\n streamingCorsRestricted: false,\n acceptsImageInput: false,\n thinkingMode: 'unsupported',\n structuredOutput: [{ modelPrefix: '', format: 'openai-json-schema' }],\n embedding: [{ modelPrefix: '', format: 'openai-embeddings' }]\n },\n {\n id: 'ollama',\n label: 'Ollama (self-hosted)',\n buttonLabel: 'AI Assist | Ollama',\n needsSecret: false,\n apiFormat: 'openai',\n baseUrl: 'http://localhost:11434/v1',\n defaultModel: '',\n supportedTools: [],\n corsRestricted: false,\n streamingCorsRestricted: false,\n acceptsImageInput: false,\n thinkingMode: 'unsupported',\n embedding: [{ modelPrefix: '', format: 'openai-embeddings' }]\n },\n {\n id: 'openai',\n label: 'OpenAI',\n buttonLabel: 'AI Assist | OpenAI',\n needsSecret: true,\n apiFormat: 'openai',\n baseUrl: 'https://api.openai.com/v1',\n defaultModel: {\n base: '@openai:mini', // gpt-5.6-luna (was gpt-5.4-mini; before that 'gpt-4o' — EOL-behind)\n advanced: '@openai:flagship', // gpt-5.6-terra\n // The gpt-5.6 family works on BOTH Chat Completions and the Responses API, so the\n // frontier tier no longer needs Responses-only routing. gpt-5.5-pro (the previous\n // frontier target) remains Responses-API-only and reachable via modelOverride; the\n // `responsesOnlyModelPrefixes` marker below still routes it correctly.\n frontier: '@openai:pro', // gpt-5.6-sol\n image: '@openai:image', // gpt-image-2 (was gpt-image-1.5; before that 'dall-e-3' — EOL 2026-05-12)\n embedding: '@openai:embedding' // text-embedding-3-small (unchanged, aliased for uniformity)\n },\n aliases: {\n '@openai:mini': 'gpt-5.6-luna', // base tier (was gpt-5.4-mini)\n '@openai:flagship': 'gpt-5.6-terra', // advanced tier (was gpt-5.5)\n '@openai:pro': 'gpt-5.6-sol', // frontier tier (was gpt-5.5-pro, which was Responses-API-only; 5.6 works on chat completions)\n '@openai:nano': 'gpt-5.4-nano', // NON-tier alias; modelOverride only\n '@openai:image': 'gpt-image-2', // image (matches the gpt-image- capability prefix; was gpt-image-1.5)\n '@openai:embedding': 'text-embedding-3-small' // NOT deprecated — aliased for uniformity\n // NOTE: gpt-5.1 deliberately absent — retired March 2026.\n },\n supportedTools: ['web_search'],\n corsRestricted: false,\n streamingCorsRestricted: false,\n acceptsImageInput: true,\n thinkingMode: 'optional',\n responsesOnlyModelPrefixes: ['gpt-5.5-pro'],\n // Declared once for the whole line. The Chat-Completions-vs-Responses split is\n // NOT declared here on purpose: the route depends on whether the call carries\n // server tools as well as on the model, so the same model takes both endpoints\n // on different calls. The dispatcher supplies that axis; a second declaration\n // of it here could only ever disagree.\n structuredOutput: [{ modelPrefix: '', format: 'openai-json-schema' }],\n embedding: [\n {\n modelPrefix: 'text-embedding-3',\n format: 'openai-embeddings',\n supportsDimensions: true,\n maxBatchSize: 2048\n },\n {\n modelPrefix: '',\n format: 'openai-embeddings'\n }\n ],\n imageGeneration: [\n {\n modelPrefix: 'gpt-image-',\n format: 'openai-images',\n acceptsImageReferenceInput: true,\n acceptedSizes: ['1024x1024', '1536x1024', '1024x1536', 'auto'],\n supportsQualityParam: true,\n acceptedQualities: ['low', 'medium', 'high', 'auto'],\n maxCount: 10,\n outputParamStyle: 'output-format',\n defaultOutputMimeType: 'image/png'\n },\n {\n modelPrefix: '',\n format: 'openai-images',\n outputParamStyle: 'response-format',\n defaultOutputMimeType: 'image/png'\n }\n ]\n },\n {\n id: 'openai-compat',\n label: 'OpenAI-compatible (self-hosted)',\n buttonLabel: 'AI Assist | OpenAI-compat',\n needsSecret: false,\n apiFormat: 'openai',\n baseUrl: '',\n defaultModel: '',\n supportedTools: [],\n corsRestricted: false,\n streamingCorsRestricted: false,\n acceptsImageInput: false,\n thinkingMode: 'unsupported',\n embedding: [{ modelPrefix: '', format: 'openai-embeddings' }]\n },\n {\n id: 'xai-grok',\n label: 'xAI Grok',\n buttonLabel: 'AI Assist | Grok',\n needsSecret: true,\n apiFormat: 'openai',\n baseUrl: 'https://api.x.ai/v1',\n defaultModel: {\n base: '@xai-grok:standard', // grok-4.3 (was the raw id; the cheap line + retirement-wave redirect target)\n advanced: '@xai-grok:flagship', // grok-4.5 (flagship since 2026-07-08; $2/$6 sits in the advanced band)\n image: '@xai-grok:imagine' // grok-imagine-image-quality (was the raw id)\n // no frontier key → a frontier request cascades advanced → grok-4.5 (see resolveModel)\n },\n aliases: {\n '@xai-grok:standard': 'grok-4.3', // base tier; NOT deprecated — superseded as flagship by grok-4.5\n '@xai-grok:flagship': 'grok-4.5', // advanced tier; configurable reasoning effort (default high)\n '@xai-grok:imagine': 'grok-imagine-image-quality' // image tier; current (redirect target for the retired -pro)\n },\n supportedTools: ['web_search'],\n corsRestricted: true,\n streamingCorsRestricted: true,\n acceptsImageInput: true,\n thinkingMode: 'optional',\n structuredOutput: [{ modelPrefix: '', format: 'openai-json-schema' }],\n imageGeneration: [\n {\n // grok-imagine models use JSON edits with image_url objects (different wire format)\n modelPrefix: 'grok-imagine-',\n format: 'xai-images-edits',\n acceptsImageReferenceInput: true,\n supportsQualityParam: false,\n maxCount: 10,\n outputParamStyle: 'response-format',\n defaultOutputMimeType: 'image/jpeg'\n },\n {\n // catch-all for other xai image models\n modelPrefix: '',\n format: 'xai-images',\n acceptsImageReferenceInput: false,\n supportsQualityParam: false,\n maxCount: 10,\n outputParamStyle: 'response-format',\n defaultOutputMimeType: 'image/jpeg'\n }\n ]\n }\n];\n\n/**\n * Index for O(1) lookup by id.\n * @internal\n */\nconst PROVIDER_BY_ID: ReadonlyMap<string, IAiProviderDescriptor> = new Map(\n BUILTIN_PROVIDERS.map((d) => [d.id, d])\n);\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * All valid provider ID values, in the same order as the registry.\n * @public\n */\nexport const allProviderIds: ReadonlyArray<AiProviderId> = BUILTIN_PROVIDERS.map((d) => d.id);\n\n/**\n * Get all known provider descriptors. Copy-paste first, then alphabetical.\n * @returns All built-in provider descriptors\n * @public\n */\nexport function getProviderDescriptors(): ReadonlyArray<IAiProviderDescriptor> {\n return BUILTIN_PROVIDERS;\n}\n\n/**\n * Get a provider descriptor by id.\n * @param id - The provider identifier\n * @returns The descriptor, or a failure if the provider is unknown\n * @public\n */\nexport function getProviderDescriptor(id: string): Result<IAiProviderDescriptor> {\n const descriptor = PROVIDER_BY_ID.get(id);\n if (!descriptor) {\n return fail(`unknown AI provider: ${id}`);\n }\n return succeed(descriptor);\n}\n\n/**\n * Whether a provider declares any image-generation capability at all.\n *\n * @param descriptor - The provider descriptor\n * @returns `true` when {@link IAiProviderDescriptor.imageGeneration} has at\n * least one entry; `false` otherwise.\n * @public\n */\nexport function supportsImageGeneration(descriptor: IAiProviderDescriptor): boolean {\n return (descriptor.imageGeneration?.length ?? 0) > 0;\n}\n\n/**\n * Shared longest-prefix capability match, alias-guarded.\n *\n * @remarks\n * `modelId` is resolved through `resolveModelAlias` **before** any prefix\n * matching, so an fgv alias (`@<provider>:<role>`) and the concrete id it names\n * select the same capability. Without this step an alias would fall through to\n * the `modelPrefix: ''` catch-all every provider declares and yield a\n * confidently wrong capability rather than no capability.\n *\n * An unresolvable alias (sigil-prefixed but unregistered, or cyclic) names no\n * model, so no capability applies and the match is skipped entirely — it must\n * never be prefix-matched verbatim. Callers already treat `undefined` as \"this\n * provider has no capability for this model\" and fail with that message.\n *\n * A concrete (non-sigil) id passes through `resolveModelAlias` verbatim, so\n * behavior for every raw provider id is unchanged.\n * @internal\n */\nfunction resolveCapabilityForModel<TCapability extends { readonly modelPrefix: string }>(\n descriptor: IAiProviderDescriptor,\n modelId: string,\n capabilities: ReadonlyArray<TCapability> | undefined\n): TCapability | undefined {\n const concreteModelId = resolveModelAlias(descriptor, modelId).orDefault();\n if (concreteModelId === undefined) {\n return undefined;\n }\n return (capabilities ?? [])\n .filter((cap) => concreteModelId.startsWith(cap.modelPrefix))\n .reduce<TCapability | undefined>(\n (best, cap) => (best && best.modelPrefix.length >= cap.modelPrefix.length ? best : cap),\n undefined\n );\n}\n\n/**\n * Resolve the image-generation capability that applies to a given model id\n * for a provider. Returns the entry from\n * {@link IAiProviderDescriptor.imageGeneration} whose `modelPrefix` is the\n * longest prefix of `modelId`. Ties are broken by first-encountered, so rule\n * order does not matter for correctness — only for tie-breaking among rules\n * with identical-length prefixes (an unusual case).\n *\n * @remarks\n * `modelId` may be either a concrete provider model id or an fgv model alias\n * (`@<provider>:<role>`, see `MODEL_ALIAS_SIGIL`) — it is resolved via\n * `resolveModelAlias` against `descriptor.aliases` before prefix matching,\n * so both forms select the same capability. A raw provider id passes through\n * unchanged. An alias that is not registered on `descriptor` (or is cyclic)\n * names no model and yields `undefined` rather than falling through to the\n * `modelPrefix: ''` catch-all.\n *\n * @param descriptor - The provider descriptor\n * @param modelId - The image model id — concrete or an fgv alias\n * @returns The matching capability, or `undefined` when no rule matches, the\n * provider declares no image-generation capabilities, or `modelId` is an\n * unresolvable alias.\n * @public\n */\nexport function resolveImageCapability(\n descriptor: IAiProviderDescriptor,\n modelId: string\n): IAiImageModelCapability | undefined {\n return resolveCapabilityForModel(descriptor, modelId, descriptor.imageGeneration);\n}\n\n/**\n * Whether a provider declares any embedding capability at all.\n *\n * @param descriptor - The provider descriptor\n * @returns `true` when {@link IAiProviderDescriptor.embedding} has at least one\n * entry; `false` otherwise.\n * @public\n */\nexport function supportsEmbedding(descriptor: IAiProviderDescriptor): boolean {\n return (descriptor.embedding?.length ?? 0) > 0;\n}\n\n/**\n * The structured-output capability for `modelId` under `descriptor`, or\n * `undefined` when the model can enforce nothing.\n *\n * @remarks\n * Alias-first, exactly like its `imageGeneration` / `embedding` siblings — an\n * unresolved alias returns `undefined` rather than prefix-matching a catch-all\n * `modelPrefix: ''`, which is the defect this helper was written to prevent.\n *\n * @param descriptor - The provider descriptor.\n * @param modelId - A concrete model id or an `@provider:role` alias.\n * @public\n */\nexport function resolveStructuredOutputCapability(\n descriptor: IAiProviderDescriptor,\n modelId: string\n): IAiStructuredOutputCapability | undefined {\n return resolveCapabilityForModel(descriptor, modelId, descriptor.structuredOutput);\n}\n\n/**\n * Whether `descriptor` declares any structured-output capability at all.\n * @public\n */\nexport function supportsStructuredOutput(descriptor: IAiProviderDescriptor): boolean {\n return (descriptor.structuredOutput?.length ?? 0) > 0;\n}\n\n/**\n * Resolve the embedding capability that applies to a given model id for a\n * provider. Returns the entry from {@link IAiProviderDescriptor.embedding} whose\n * `modelPrefix` is the longest prefix of `modelId`. Ties are broken by\n * first-encountered.\n *\n * @remarks\n * `modelId` may be either a concrete provider model id or an fgv model alias\n * (`@<provider>:<role>`, see `MODEL_ALIAS_SIGIL`) — it is resolved via\n * `resolveModelAlias` against `descriptor.aliases` before prefix matching,\n * so both forms select the same capability. A raw provider id passes through\n * unchanged. An alias that is not registered on `descriptor` (or is cyclic)\n * names no model and yields `undefined` rather than falling through to the\n * `modelPrefix: ''` catch-all.\n *\n * @param descriptor - The provider descriptor\n * @param modelId - The embedding model id — concrete or an fgv alias\n * @returns The matching capability, or `undefined` when no rule matches, the\n * provider declares no embedding capabilities, or `modelId` is an\n * unresolvable alias.\n * @public\n */\nexport function resolveEmbeddingCapability(\n descriptor: IAiProviderDescriptor,\n modelId: string\n): IAiEmbeddingModelCapability | undefined {\n return resolveCapabilityForModel(descriptor, modelId, descriptor.embedding);\n}\n\n// ============================================================================\n// Default model capability config\n// ============================================================================\n\n/**\n * Default capability config used by `callProviderListModels` when callers\n * don't supply their own. Patterns are intentionally narrow — false\n * positives are worse than missing a model. Caller can override per call\n * via {@link IProviderListModelsParams.capabilityConfig}.\n *\n * @public\n */\nexport const DEFAULT_MODEL_CAPABILITY_CONFIG: IAiModelCapabilityConfig = {\n perProvider: {\n openai: [\n { idPattern: /^gpt-image/, capabilities: ['image-generation'] },\n { idPattern: /^text-embedding/, capabilities: ['embedding'] },\n { idPattern: /^gpt-5/, capabilities: ['chat', 'tools', 'vision', 'thinking'] },\n { idPattern: /^gpt-4/, capabilities: ['chat', 'tools', 'vision'] },\n { idPattern: /^gpt-3\\.5/, capabilities: ['chat'] },\n { idPattern: /^o\\d/, capabilities: ['chat', 'tools', 'thinking'] }\n ],\n 'xai-grok': [\n { idPattern: /-image/, capabilities: ['image-generation'] },\n // grok-4.5 needs its own rule: it would otherwise hit only /^grok-4/ and lose\n // thinking, yet it has configurable reasoning effort. Detection accumulates across\n // matching rules, so /^grok-4/ still contributes vision (same as grok-4.3).\n { idPattern: /^grok-4\\.5/, capabilities: ['chat', 'tools', 'thinking'] },\n { idPattern: /^grok-4\\.3/, capabilities: ['chat', 'tools', 'thinking'] },\n { idPattern: /^grok-4$/, capabilities: ['chat', 'tools', 'thinking'] },\n { idPattern: /^grok-4/, capabilities: ['chat', 'tools', 'vision'] },\n { idPattern: /^grok-3-mini/, capabilities: ['chat', 'tools', 'thinking'] },\n { idPattern: /^grok-3/, capabilities: ['chat', 'tools'] },\n { idPattern: /^grok-2/, capabilities: ['chat', 'vision'] }\n ],\n 'google-gemini': [\n { idPattern: /^imagen/, capabilities: ['image-generation'] },\n { idPattern: /^gemini-.*-image/, capabilities: ['image-generation'] },\n { idPattern: /embedding/, capabilities: ['embedding'] },\n { idPattern: /^gemini-3/, capabilities: ['chat', 'tools', 'vision', 'thinking'] },\n { idPattern: /^gemini-2\\.5/, capabilities: ['chat', 'tools', 'vision', 'thinking'] },\n { idPattern: /^gemini-/, capabilities: ['chat', 'tools', 'vision'] }\n ],\n anthropic: [\n // Broadened from /^claude-opus-4/ and /^claude-sonnet-4/ so the sonnet-5+ / opus-5+ lines\n // are detected as thinking-capable. Detection accumulates across matching rules, so this is\n // purely additive: every existing opus-4 / sonnet-4 id still matches. Both broadenings are\n // now load-bearing: claude-sonnet-5 and claude-opus-5 (the advanced-tier target) would\n // otherwise hit only /^claude-/ and lose thinking. The fable rule keeps the\n // AnthropicThinkingModelNames union honest — claude-fable-5 (modelOverride-only) would\n // otherwise fall to the /^claude-/ catch-all and be detected without thinking.\n { idPattern: /^claude-opus-/, capabilities: ['chat', 'tools', 'vision', 'thinking'] },\n { idPattern: /^claude-sonnet-/, capabilities: ['chat', 'tools', 'vision', 'thinking'] },\n { idPattern: /^claude-fable-/, capabilities: ['chat', 'tools', 'vision', 'thinking'] },\n { idPattern: /^claude-/, capabilities: ['chat', 'tools', 'vision'] }\n ],\n groq: [{ idPattern: /./, capabilities: ['chat'] }],\n mistral: [\n { idPattern: /embed/, capabilities: ['embedding'] },\n { idPattern: /./, capabilities: ['chat'] }\n ],\n // Self-hosted OpenAI-compatible servers (Ollama, LM Studio, llama.cpp) serve\n // arbitrary, caller-chosen models whose ids we can't enumerate ahead of time.\n // The catch-all `/./` intentionally departs from the \"narrow patterns\" rule\n // above: assume `chat` for everything and let the caller override via\n // `capabilityConfig` when they know their deployment serves image/embedding models.\n ollama: [{ idPattern: /./, capabilities: ['chat'] }],\n 'openai-compat': [{ idPattern: /./, capabilities: ['chat'] }]\n }\n};\n"]}
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2026 Erik Fortune
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
import { fail, succeed } from '@fgv/ts-utils';
|
|
6
|
+
import { toGeminiParameterSchema } from './toolFormats';
|
|
7
|
+
/**
|
|
8
|
+
* The name the Anthropic forced-tool path gives its synthetic tool.
|
|
9
|
+
*
|
|
10
|
+
* @remarks
|
|
11
|
+
* Anthropic has no `response_format`; its structured-output mechanism is forced
|
|
12
|
+
* tool use, so a tool must exist to be forced. The name is fgv-owned and never
|
|
13
|
+
* reaches the caller — the structured-output resolver re-serializes the tool's
|
|
14
|
+
* `input` back into `IAiCompletionResponse.content`, so a caller's converter sees
|
|
15
|
+
* a JSON string exactly as it does on every other provider.
|
|
16
|
+
* @public
|
|
17
|
+
*/
|
|
18
|
+
export const ANTHROPIC_STRUCTURED_OUTPUT_TOOL_NAME = 'fgv_structured_output';
|
|
19
|
+
/** The `'none'` decision: nothing sent, nothing enforced. @internal */
|
|
20
|
+
export const NO_STRUCTURED_OUTPUT = { enforcement: 'none', wire: {} };
|
|
21
|
+
/**
|
|
22
|
+
* Wire fields for a schema-constrained request. Every format can express this —
|
|
23
|
+
* a structured-output capability that could not carry a schema would have nothing
|
|
24
|
+
* to declare.
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
function schemaWire(format, raw) {
|
|
28
|
+
switch (format) {
|
|
29
|
+
case 'openai-json-schema':
|
|
30
|
+
return {
|
|
31
|
+
enforcement: 'schema',
|
|
32
|
+
wire: {
|
|
33
|
+
response_format: {
|
|
34
|
+
type: 'json_schema',
|
|
35
|
+
json_schema: { name: 'response', strict: true, schema: raw }
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
case 'openai-responses-format':
|
|
40
|
+
// The Responses API nests the same choice under `text.format` and flattens the
|
|
41
|
+
// schema onto the format object rather than a `json_schema` sub-object.
|
|
42
|
+
return {
|
|
43
|
+
enforcement: 'schema',
|
|
44
|
+
wire: { text: { format: { type: 'json_schema', name: 'response', strict: true, schema: raw } } }
|
|
45
|
+
};
|
|
46
|
+
case 'gemini-response-schema':
|
|
47
|
+
// Merged into `generationConfig`, not the body. Gemini's schema is an
|
|
48
|
+
// OpenAPI-3.0 subset that REJECTS draft-07 keywords rather than ignoring them,
|
|
49
|
+
// and `JsonSchema` is strict-by-default so `.toJson()` emits
|
|
50
|
+
// `additionalProperties: false` on every object node — hence the same sanitizer
|
|
51
|
+
// the tool path uses.
|
|
52
|
+
return {
|
|
53
|
+
enforcement: 'schema',
|
|
54
|
+
wire: { responseMimeType: 'application/json', responseSchema: toGeminiParameterSchema(raw) }
|
|
55
|
+
};
|
|
56
|
+
case 'anthropic-tool-forced':
|
|
57
|
+
// Anthropic has no response-format field. The schema becomes a synthetic
|
|
58
|
+
// tool's `input_schema` and `tool_choice` forces it, which is why this is a
|
|
59
|
+
// distinct enforcement value rather than a spelling of `'schema'`: the reply
|
|
60
|
+
// arrives in a `tool_use` block, not as text.
|
|
61
|
+
return {
|
|
62
|
+
enforcement: 'tool-forced',
|
|
63
|
+
wire: {
|
|
64
|
+
tools: [
|
|
65
|
+
{
|
|
66
|
+
name: ANTHROPIC_STRUCTURED_OUTPUT_TOOL_NAME,
|
|
67
|
+
description: 'Return the response as structured data matching the supplied schema.',
|
|
68
|
+
input_schema: raw
|
|
69
|
+
}
|
|
70
|
+
],
|
|
71
|
+
tool_choice: { type: 'tool', name: ANTHROPIC_STRUCTURED_OUTPUT_TOOL_NAME }
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
/* c8 ignore next 4 - defensive: exhaustive switch guaranteed by TypeScript */
|
|
75
|
+
default: {
|
|
76
|
+
const _exhaustive = format;
|
|
77
|
+
throw new Error(`unsupported structured-output format: ${String(_exhaustive)}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Wire fields for a bare JSON-object request, or `undefined` when the format
|
|
83
|
+
* cannot express one.
|
|
84
|
+
*
|
|
85
|
+
* @remarks
|
|
86
|
+
* The `undefined` return **is** the capability table — there is deliberately no
|
|
87
|
+
* separate `supportsJsonObject` flag anywhere, because a second declaration of
|
|
88
|
+
* what a format can do could only ever disagree with this function.
|
|
89
|
+
* @internal
|
|
90
|
+
*/
|
|
91
|
+
function jsonObjectWire(format) {
|
|
92
|
+
switch (format) {
|
|
93
|
+
case 'openai-json-schema':
|
|
94
|
+
return { enforcement: 'json-mode', wire: { response_format: { type: 'json_object' } } };
|
|
95
|
+
case 'openai-responses-format':
|
|
96
|
+
return { enforcement: 'json-mode', wire: { text: { format: { type: 'json_object' } } } };
|
|
97
|
+
case 'gemini-response-schema':
|
|
98
|
+
return { enforcement: 'json-mode', wire: { responseMimeType: 'application/json' } };
|
|
99
|
+
case 'anthropic-tool-forced':
|
|
100
|
+
// A forced tool needs an input schema to be forced *to*, so there is no
|
|
101
|
+
// schema-less form of this mechanism.
|
|
102
|
+
return undefined;
|
|
103
|
+
/* c8 ignore next 4 - defensive: exhaustive switch guaranteed by TypeScript */
|
|
104
|
+
default: {
|
|
105
|
+
const _exhaustive = format;
|
|
106
|
+
throw new Error(`unsupported structured-output format: ${String(_exhaustive)}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Whether `raw` declares any object property that is absent from that object's
|
|
112
|
+
* `required` list — at any depth.
|
|
113
|
+
*
|
|
114
|
+
* @remarks
|
|
115
|
+
* **This is a hard constraint of OpenAI's strict structured output, not a style
|
|
116
|
+
* preference.** `response_format: { type: 'json_schema', json_schema: { strict: true } }`
|
|
117
|
+
* requires *every* key in `properties` to appear in `required`; a schema that omits
|
|
118
|
+
* one is rejected with a 400 before the model ever runs. `JsonSchema.optional(...)`
|
|
119
|
+
* produces exactly that shape, so an authored schema with one optional field is
|
|
120
|
+
* unsendable to the two OpenAI strict formats.
|
|
121
|
+
*
|
|
122
|
+
* The three obvious repairs are all worse than refusing. Rewriting optional to
|
|
123
|
+
* required-and-nullable changes what the model must emit (`null` rather than
|
|
124
|
+
* omission), so the reply would no longer satisfy the caller's own validator —
|
|
125
|
+
* breaking the one-object-cannot-drift property this whole surface exists for.
|
|
126
|
+
* Dropping `strict` silently downgrades the guarantee while still reporting
|
|
127
|
+
* `'schema'`, which is the lie the required report exists to prevent. And sending
|
|
128
|
+
* it anyway just relocates the failure to an opaque provider 400.
|
|
129
|
+
*
|
|
130
|
+
* So this is treated as a **capability mismatch** and routed through the caller's
|
|
131
|
+
* existing `onUnsupported` choice — degrade to unconstrained by default, fail loudly
|
|
132
|
+
* on request. Gemini and Anthropic have no such rule and are unaffected.
|
|
133
|
+
* @internal
|
|
134
|
+
*/
|
|
135
|
+
export function hasOptionalProperties(raw) {
|
|
136
|
+
if (Array.isArray(raw)) {
|
|
137
|
+
return raw.some(hasOptionalProperties);
|
|
138
|
+
}
|
|
139
|
+
if (raw === null || typeof raw !== 'object') {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
const node = raw;
|
|
143
|
+
const properties = node.properties;
|
|
144
|
+
if (properties !== null && typeof properties === 'object' && !Array.isArray(properties)) {
|
|
145
|
+
const required = Array.isArray(node.required) ? node.required : [];
|
|
146
|
+
for (const name of Object.keys(properties)) {
|
|
147
|
+
if (!required.includes(name)) {
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return Object.values(node).some((v) => v !== undefined && hasOptionalProperties(v));
|
|
153
|
+
}
|
|
154
|
+
/** The two formats that carry OpenAI's all-properties-required strict rule. @internal */
|
|
155
|
+
function isOpenAiStrictFormat(format) {
|
|
156
|
+
return format === 'openai-json-schema' || format === 'openai-responses-format';
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Whether a resolved wire claims the provider's tools channel, and therefore
|
|
160
|
+
* genuinely conflicts with server-side tools.
|
|
161
|
+
*
|
|
162
|
+
* @remarks
|
|
163
|
+
* Asked of the **resolved wire** rather than the declared format, because a format
|
|
164
|
+
* that *would* claim the channel does not claim it when the request degraded to
|
|
165
|
+
* sending nothing. Anthropic + `json-object` is exactly that case: the mode has no
|
|
166
|
+
* expression there, so the wire is empty and there is nothing to conflict with —
|
|
167
|
+
* rejecting it would refuse a request that was about to become harmless.
|
|
168
|
+
* @internal
|
|
169
|
+
*/
|
|
170
|
+
function conflictsWithServerTools(format, resolved) {
|
|
171
|
+
return (resolved.enforcement !== 'none' &&
|
|
172
|
+
(format === 'anthropic-tool-forced' || format === 'gemini-response-schema'));
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* The wire format actually in force, given which OpenAI endpoint the dispatcher
|
|
176
|
+
* will use.
|
|
177
|
+
*
|
|
178
|
+
* @remarks
|
|
179
|
+
* **The OpenAI route is not a function of the model alone.** `callProviderCompletion`
|
|
180
|
+
* sends a request to `/responses` when it carries server tools **or** when the model
|
|
181
|
+
* is Responses-only, and to `/chat/completions` otherwise — so the same model takes
|
|
182
|
+
* different endpoints on different calls, and those endpoints spell structured output
|
|
183
|
+
* differently (`response_format` vs `text.format`). A capability declaration keyed on
|
|
184
|
+
* the model therefore cannot name the right one by itself, and emitting
|
|
185
|
+
* `response_format` into a `/responses` body would be silently ignored by the
|
|
186
|
+
* provider: the request would look constrained and the reply would not be, with the
|
|
187
|
+
* report confidently saying `'schema'`.
|
|
188
|
+
*
|
|
189
|
+
* The declaration still names each family's *support*; this is the one axis it cannot
|
|
190
|
+
* carry, so it is supplied by the dispatcher that makes the routing decision.
|
|
191
|
+
* @internal
|
|
192
|
+
*/
|
|
193
|
+
function effectiveFormat(declared, usesResponsesApi) {
|
|
194
|
+
if (usesResponsesApi && declared === 'openai-json-schema') {
|
|
195
|
+
return 'openai-responses-format';
|
|
196
|
+
}
|
|
197
|
+
return declared;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Resolve a caller's structured-output request against the concrete model that
|
|
201
|
+
* will serve it.
|
|
202
|
+
*
|
|
203
|
+
* @param descriptor - The provider descriptor.
|
|
204
|
+
* @param model - The **concrete** model id, already through `resolveProviderModel`.
|
|
205
|
+
* Passing an alias here would be a bug of the class `resolveImageCapability` once
|
|
206
|
+
* had, where an unresolved alias fell through to a catch-all `modelPrefix: ''` and
|
|
207
|
+
* returned a confidently wrong capability.
|
|
208
|
+
* @param request - The caller's intent, or `undefined` for no request at all.
|
|
209
|
+
* @param serverTools - Server-side tools on the same request, which conflict with
|
|
210
|
+
* structured output on two of the four formats.
|
|
211
|
+
* @param usesResponsesApi - Whether the dispatcher will send this request to the
|
|
212
|
+
* OpenAI Responses API rather than Chat Completions. See {@link effectiveFormat} —
|
|
213
|
+
* the route is not a function of the model alone, so the capability declaration
|
|
214
|
+
* cannot carry it.
|
|
215
|
+
* @returns The decision, or `Failure` when the caller asked to fail rather than
|
|
216
|
+
* degrade — or when the request conflicts with server tools, which is never
|
|
217
|
+
* degradable because the caller asked for two things the provider cannot both do.
|
|
218
|
+
* @internal
|
|
219
|
+
*/
|
|
220
|
+
export function resolveStructuredOutput(descriptor, model, request, serverTools, usesResponsesApi, resolveCapability) {
|
|
221
|
+
var _a;
|
|
222
|
+
if (request === undefined) {
|
|
223
|
+
return succeed(NO_STRUCTURED_OUTPUT);
|
|
224
|
+
}
|
|
225
|
+
const fallback = (_a = request.onUnsupported) !== null && _a !== void 0 ? _a : 'degrade';
|
|
226
|
+
const capability = resolveCapability(descriptor, model);
|
|
227
|
+
if (capability === undefined) {
|
|
228
|
+
return fallback === 'fail'
|
|
229
|
+
? fail(`provider '${descriptor.id}' model '${model}' declares no structured-output capability; ` +
|
|
230
|
+
`pass onUnsupported: 'degrade' to send the request unconstrained`)
|
|
231
|
+
: succeed(NO_STRUCTURED_OUTPUT);
|
|
232
|
+
}
|
|
233
|
+
const format = effectiveFormat(capability.format, usesResponsesApi);
|
|
234
|
+
// Resolve the wire FIRST, then judge conflicts against what it actually is.
|
|
235
|
+
// Ordering matters: a format that would claim the tools channel does not claim
|
|
236
|
+
// it when the request degraded to sending nothing.
|
|
237
|
+
let resolved;
|
|
238
|
+
let unsupported;
|
|
239
|
+
if (request.mode === 'schema') {
|
|
240
|
+
const raw = request.schema.toJson();
|
|
241
|
+
if (isOpenAiStrictFormat(format) && hasOptionalProperties(raw)) {
|
|
242
|
+
// See `hasOptionalProperties` — a hard provider constraint, treated as a
|
|
243
|
+
// capability mismatch rather than relocated into an opaque 400.
|
|
244
|
+
unsupported =
|
|
245
|
+
`the supplied schema declares optional properties, and OpenAI strict structured output ` +
|
|
246
|
+
`requires every property to be required; author them as required, or pass ` +
|
|
247
|
+
`onUnsupported: 'degrade' to send the request unconstrained`;
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
resolved = schemaWire(format, raw);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
resolved = jsonObjectWire(format);
|
|
255
|
+
if (resolved === undefined) {
|
|
256
|
+
// Today this is only `'json-object'` on Anthropic, whose mechanism needs a
|
|
257
|
+
// schema to force a tool to.
|
|
258
|
+
unsupported = `provider '${descriptor.id}' model '${model}' cannot enforce '${request.mode}' structured output`;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (resolved === undefined) {
|
|
262
|
+
return fallback === 'fail' ? fail(`${unsupported}`) : succeed(NO_STRUCTURED_OUTPUT);
|
|
263
|
+
}
|
|
264
|
+
// Two formats cannot carry structured output and server-side tools at once, for
|
|
265
|
+
// DIFFERENT reasons — worth separating, because a reader who assumes one
|
|
266
|
+
// mechanism will reason wrongly about the other.
|
|
267
|
+
//
|
|
268
|
+
// anthropic-tool-forced: a wire-level clash. The constraint IS `tools` +
|
|
269
|
+
// `tool_choice`, so server tools would be overwritten (and `tool_choice`
|
|
270
|
+
// forces ours, which disables theirs anyway).
|
|
271
|
+
// gemini-response-schema: NOT a wire clash — `responseMimeType` /
|
|
272
|
+
// `responseSchema` live in `generationConfig`, nowhere near `tools`. It is
|
|
273
|
+
// an API-level mutual exclusivity Gemini enforces, the same restriction the
|
|
274
|
+
// client-tool path already pre-empts.
|
|
275
|
+
//
|
|
276
|
+
// Neither is degradable: silently dropping either half would give the caller
|
|
277
|
+
// something they did not ask for, and `onUnsupported` speaks to what a model can
|
|
278
|
+
// enforce, not to a caller asking for two incompatible things.
|
|
279
|
+
if (serverTools !== undefined && serverTools.length > 0 && conflictsWithServerTools(format, resolved)) {
|
|
280
|
+
const why = format === 'anthropic-tool-forced'
|
|
281
|
+
? 'Anthropic enforces structured output by forcing a tool, so it cannot be combined with'
|
|
282
|
+
: 'Gemini cannot combine a response schema with';
|
|
283
|
+
return fail(`${why} server-side tools (${serverTools.map((t) => t.type).join(', ')}) in the same request; ` +
|
|
284
|
+
`send one or the other`);
|
|
285
|
+
}
|
|
286
|
+
return succeed(resolved);
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Every valid `StructuredOutputEnforcement`, for the wire-shape guard below.
|
|
290
|
+
*
|
|
291
|
+
* @remarks
|
|
292
|
+
* A **total** `Record`, not a `Set` built from an array literal — the same reasoning
|
|
293
|
+
* as `SCHEMA_NODE_TYPES` in `@fgv/ts-json-base`. A `Set` catches a removed or
|
|
294
|
+
* misspelled member but not an *added* one, so a new enforcement value would compile
|
|
295
|
+
* fine here while this guard silently began rejecting it off a proxy response. The
|
|
296
|
+
* `Record` makes that addition a compile error at this line.
|
|
297
|
+
* @internal
|
|
298
|
+
*/
|
|
299
|
+
const ENFORCEMENTS = {
|
|
300
|
+
none: true,
|
|
301
|
+
'json-mode': true,
|
|
302
|
+
schema: true,
|
|
303
|
+
'tool-forced': true
|
|
304
|
+
};
|
|
305
|
+
/**
|
|
306
|
+
* Whether an untyped value off a proxy response is a valid
|
|
307
|
+
* `StructuredOutputEnforcement`.
|
|
308
|
+
* @internal
|
|
309
|
+
*/
|
|
310
|
+
export function isStructuredOutputEnforcement(value) {
|
|
311
|
+
// Indexed read compared to `true`, NOT `in` — `in` walks the prototype chain, so a
|
|
312
|
+
// proxy answering `structuredOutput: 'constructor'` would pass it.
|
|
313
|
+
return typeof value === 'string' && ENFORCEMENTS[value] === true;
|
|
314
|
+
}
|
|
315
|
+
//# sourceMappingURL=structuredOutput.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"structuredOutput.js","sourceRoot":"","sources":["../../../src/packlets/ai-assist/structuredOutput.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport type { JsonObject, JsonValue } from '@fgv/ts-json-base';\nimport { Result, fail, succeed } from '@fgv/ts-utils';\nimport type { AiServerToolConfig, IAiProviderDescriptor } from './model';\nimport type {\n IAiStructuredOutputCapability,\n StructuredOutputEnforcement,\n StructuredOutputFallback,\n StructuredOutputRequest\n} from './structuredOutputTypes';\nimport { toGeminiParameterSchema } from './toolFormats';\n\n/**\n * The name the Anthropic forced-tool path gives its synthetic tool.\n *\n * @remarks\n * Anthropic has no `response_format`; its structured-output mechanism is forced\n * tool use, so a tool must exist to be forced. The name is fgv-owned and never\n * reaches the caller — the structured-output resolver re-serializes the tool's\n * `input` back into `IAiCompletionResponse.content`, so a caller's converter sees\n * a JSON string exactly as it does on every other provider.\n * @public\n */\nexport const ANTHROPIC_STRUCTURED_OUTPUT_TOOL_NAME: string = 'fgv_structured_output';\n\n/**\n * A resolved structured-output decision: what will be enforced, and the wire\n * fields that enforce it.\n * @internal\n */\nexport interface IResolvedStructuredOutput {\n /** What to report on the response. */\n readonly enforcement: StructuredOutputEnforcement;\n /**\n * Fields to merge into the request, **at the location the format dictates** —\n * the request body for the OpenAI and Anthropic formats, `generationConfig` for\n * Gemini. Empty when `enforcement` is `'none'`.\n */\n readonly wire: JsonObject;\n}\n\n/** The `'none'` decision: nothing sent, nothing enforced. @internal */\nexport const NO_STRUCTURED_OUTPUT: IResolvedStructuredOutput = { enforcement: 'none', wire: {} };\n\n/**\n * Wire fields for a schema-constrained request. Every format can express this —\n * a structured-output capability that could not carry a schema would have nothing\n * to declare.\n * @internal\n */\nfunction schemaWire(\n format: IAiStructuredOutputCapability['format'],\n raw: JsonValue\n): IResolvedStructuredOutput {\n switch (format) {\n case 'openai-json-schema':\n return {\n enforcement: 'schema',\n wire: {\n response_format: {\n type: 'json_schema',\n json_schema: { name: 'response', strict: true, schema: raw }\n }\n }\n };\n case 'openai-responses-format':\n // The Responses API nests the same choice under `text.format` and flattens the\n // schema onto the format object rather than a `json_schema` sub-object.\n return {\n enforcement: 'schema',\n wire: { text: { format: { type: 'json_schema', name: 'response', strict: true, schema: raw } } }\n };\n case 'gemini-response-schema':\n // Merged into `generationConfig`, not the body. Gemini's schema is an\n // OpenAPI-3.0 subset that REJECTS draft-07 keywords rather than ignoring them,\n // and `JsonSchema` is strict-by-default so `.toJson()` emits\n // `additionalProperties: false` on every object node — hence the same sanitizer\n // the tool path uses.\n return {\n enforcement: 'schema',\n wire: { responseMimeType: 'application/json', responseSchema: toGeminiParameterSchema(raw) }\n };\n case 'anthropic-tool-forced':\n // Anthropic has no response-format field. The schema becomes a synthetic\n // tool's `input_schema` and `tool_choice` forces it, which is why this is a\n // distinct enforcement value rather than a spelling of `'schema'`: the reply\n // arrives in a `tool_use` block, not as text.\n return {\n enforcement: 'tool-forced',\n wire: {\n tools: [\n {\n name: ANTHROPIC_STRUCTURED_OUTPUT_TOOL_NAME,\n description: 'Return the response as structured data matching the supplied schema.',\n input_schema: raw\n }\n ],\n tool_choice: { type: 'tool', name: ANTHROPIC_STRUCTURED_OUTPUT_TOOL_NAME }\n }\n };\n /* c8 ignore next 4 - defensive: exhaustive switch guaranteed by TypeScript */\n default: {\n const _exhaustive: never = format;\n throw new Error(`unsupported structured-output format: ${String(_exhaustive)}`);\n }\n }\n}\n\n/**\n * Wire fields for a bare JSON-object request, or `undefined` when the format\n * cannot express one.\n *\n * @remarks\n * The `undefined` return **is** the capability table — there is deliberately no\n * separate `supportsJsonObject` flag anywhere, because a second declaration of\n * what a format can do could only ever disagree with this function.\n * @internal\n */\nfunction jsonObjectWire(\n format: IAiStructuredOutputCapability['format']\n): IResolvedStructuredOutput | undefined {\n switch (format) {\n case 'openai-json-schema':\n return { enforcement: 'json-mode', wire: { response_format: { type: 'json_object' } } };\n case 'openai-responses-format':\n return { enforcement: 'json-mode', wire: { text: { format: { type: 'json_object' } } } };\n case 'gemini-response-schema':\n return { enforcement: 'json-mode', wire: { responseMimeType: 'application/json' } };\n case 'anthropic-tool-forced':\n // A forced tool needs an input schema to be forced *to*, so there is no\n // schema-less form of this mechanism.\n return undefined;\n /* c8 ignore next 4 - defensive: exhaustive switch guaranteed by TypeScript */\n default: {\n const _exhaustive: never = format;\n throw new Error(`unsupported structured-output format: ${String(_exhaustive)}`);\n }\n }\n}\n\n/**\n * Whether `raw` declares any object property that is absent from that object's\n * `required` list — at any depth.\n *\n * @remarks\n * **This is a hard constraint of OpenAI's strict structured output, not a style\n * preference.** `response_format: { type: 'json_schema', json_schema: { strict: true } }`\n * requires *every* key in `properties` to appear in `required`; a schema that omits\n * one is rejected with a 400 before the model ever runs. `JsonSchema.optional(...)`\n * produces exactly that shape, so an authored schema with one optional field is\n * unsendable to the two OpenAI strict formats.\n *\n * The three obvious repairs are all worse than refusing. Rewriting optional to\n * required-and-nullable changes what the model must emit (`null` rather than\n * omission), so the reply would no longer satisfy the caller's own validator —\n * breaking the one-object-cannot-drift property this whole surface exists for.\n * Dropping `strict` silently downgrades the guarantee while still reporting\n * `'schema'`, which is the lie the required report exists to prevent. And sending\n * it anyway just relocates the failure to an opaque provider 400.\n *\n * So this is treated as a **capability mismatch** and routed through the caller's\n * existing `onUnsupported` choice — degrade to unconstrained by default, fail loudly\n * on request. Gemini and Anthropic have no such rule and are unaffected.\n * @internal\n */\nexport function hasOptionalProperties(raw: JsonValue): boolean {\n if (Array.isArray(raw)) {\n return raw.some(hasOptionalProperties);\n }\n if (raw === null || typeof raw !== 'object') {\n return false;\n }\n const node: Record<string, JsonValue | undefined> = raw as Record<string, JsonValue | undefined>;\n const properties = node.properties;\n if (properties !== null && typeof properties === 'object' && !Array.isArray(properties)) {\n const required: ReadonlyArray<JsonValue> = Array.isArray(node.required) ? node.required : [];\n for (const name of Object.keys(properties)) {\n if (!required.includes(name)) {\n return true;\n }\n }\n }\n return Object.values(node).some((v) => v !== undefined && hasOptionalProperties(v));\n}\n\n/** The two formats that carry OpenAI's all-properties-required strict rule. @internal */\nfunction isOpenAiStrictFormat(format: IAiStructuredOutputCapability['format']): boolean {\n return format === 'openai-json-schema' || format === 'openai-responses-format';\n}\n\n/**\n * Whether a resolved wire claims the provider's tools channel, and therefore\n * genuinely conflicts with server-side tools.\n *\n * @remarks\n * Asked of the **resolved wire** rather than the declared format, because a format\n * that *would* claim the channel does not claim it when the request degraded to\n * sending nothing. Anthropic + `json-object` is exactly that case: the mode has no\n * expression there, so the wire is empty and there is nothing to conflict with —\n * rejecting it would refuse a request that was about to become harmless.\n * @internal\n */\nfunction conflictsWithServerTools(\n format: IAiStructuredOutputCapability['format'],\n resolved: IResolvedStructuredOutput\n): boolean {\n return (\n resolved.enforcement !== 'none' &&\n (format === 'anthropic-tool-forced' || format === 'gemini-response-schema')\n );\n}\n\n/**\n * The wire format actually in force, given which OpenAI endpoint the dispatcher\n * will use.\n *\n * @remarks\n * **The OpenAI route is not a function of the model alone.** `callProviderCompletion`\n * sends a request to `/responses` when it carries server tools **or** when the model\n * is Responses-only, and to `/chat/completions` otherwise — so the same model takes\n * different endpoints on different calls, and those endpoints spell structured output\n * differently (`response_format` vs `text.format`). A capability declaration keyed on\n * the model therefore cannot name the right one by itself, and emitting\n * `response_format` into a `/responses` body would be silently ignored by the\n * provider: the request would look constrained and the reply would not be, with the\n * report confidently saying `'schema'`.\n *\n * The declaration still names each family's *support*; this is the one axis it cannot\n * carry, so it is supplied by the dispatcher that makes the routing decision.\n * @internal\n */\nfunction effectiveFormat(\n declared: IAiStructuredOutputCapability['format'],\n usesResponsesApi: boolean\n): IAiStructuredOutputCapability['format'] {\n if (usesResponsesApi && declared === 'openai-json-schema') {\n return 'openai-responses-format';\n }\n return declared;\n}\n\n/**\n * Resolve a caller's structured-output request against the concrete model that\n * will serve it.\n *\n * @param descriptor - The provider descriptor.\n * @param model - The **concrete** model id, already through `resolveProviderModel`.\n * Passing an alias here would be a bug of the class `resolveImageCapability` once\n * had, where an unresolved alias fell through to a catch-all `modelPrefix: ''` and\n * returned a confidently wrong capability.\n * @param request - The caller's intent, or `undefined` for no request at all.\n * @param serverTools - Server-side tools on the same request, which conflict with\n * structured output on two of the four formats.\n * @param usesResponsesApi - Whether the dispatcher will send this request to the\n * OpenAI Responses API rather than Chat Completions. See {@link effectiveFormat} —\n * the route is not a function of the model alone, so the capability declaration\n * cannot carry it.\n * @returns The decision, or `Failure` when the caller asked to fail rather than\n * degrade — or when the request conflicts with server tools, which is never\n * degradable because the caller asked for two things the provider cannot both do.\n * @internal\n */\nexport function resolveStructuredOutput(\n descriptor: IAiProviderDescriptor,\n model: string,\n request: StructuredOutputRequest | undefined,\n serverTools: ReadonlyArray<AiServerToolConfig> | undefined,\n usesResponsesApi: boolean,\n resolveCapability: (\n descriptor: IAiProviderDescriptor,\n model: string\n ) => IAiStructuredOutputCapability | undefined\n): Result<IResolvedStructuredOutput> {\n if (request === undefined) {\n return succeed(NO_STRUCTURED_OUTPUT);\n }\n const fallback: StructuredOutputFallback = request.onUnsupported ?? 'degrade';\n const capability = resolveCapability(descriptor, model);\n if (capability === undefined) {\n return fallback === 'fail'\n ? fail(\n `provider '${descriptor.id}' model '${model}' declares no structured-output capability; ` +\n `pass onUnsupported: 'degrade' to send the request unconstrained`\n )\n : succeed(NO_STRUCTURED_OUTPUT);\n }\n\n const format = effectiveFormat(capability.format, usesResponsesApi);\n\n // Resolve the wire FIRST, then judge conflicts against what it actually is.\n // Ordering matters: a format that would claim the tools channel does not claim\n // it when the request degraded to sending nothing.\n let resolved: IResolvedStructuredOutput | undefined;\n let unsupported: string | undefined;\n if (request.mode === 'schema') {\n const raw: JsonValue = request.schema.toJson();\n if (isOpenAiStrictFormat(format) && hasOptionalProperties(raw)) {\n // See `hasOptionalProperties` — a hard provider constraint, treated as a\n // capability mismatch rather than relocated into an opaque 400.\n unsupported =\n `the supplied schema declares optional properties, and OpenAI strict structured output ` +\n `requires every property to be required; author them as required, or pass ` +\n `onUnsupported: 'degrade' to send the request unconstrained`;\n } else {\n resolved = schemaWire(format, raw);\n }\n } else {\n resolved = jsonObjectWire(format);\n if (resolved === undefined) {\n // Today this is only `'json-object'` on Anthropic, whose mechanism needs a\n // schema to force a tool to.\n unsupported = `provider '${descriptor.id}' model '${model}' cannot enforce '${request.mode}' structured output`;\n }\n }\n\n if (resolved === undefined) {\n return fallback === 'fail' ? fail(`${unsupported}`) : succeed(NO_STRUCTURED_OUTPUT);\n }\n\n // Two formats cannot carry structured output and server-side tools at once, for\n // DIFFERENT reasons — worth separating, because a reader who assumes one\n // mechanism will reason wrongly about the other.\n //\n // anthropic-tool-forced: a wire-level clash. The constraint IS `tools` +\n // `tool_choice`, so server tools would be overwritten (and `tool_choice`\n // forces ours, which disables theirs anyway).\n // gemini-response-schema: NOT a wire clash — `responseMimeType` /\n // `responseSchema` live in `generationConfig`, nowhere near `tools`. It is\n // an API-level mutual exclusivity Gemini enforces, the same restriction the\n // client-tool path already pre-empts.\n //\n // Neither is degradable: silently dropping either half would give the caller\n // something they did not ask for, and `onUnsupported` speaks to what a model can\n // enforce, not to a caller asking for two incompatible things.\n if (serverTools !== undefined && serverTools.length > 0 && conflictsWithServerTools(format, resolved)) {\n const why =\n format === 'anthropic-tool-forced'\n ? 'Anthropic enforces structured output by forcing a tool, so it cannot be combined with'\n : 'Gemini cannot combine a response schema with';\n return fail(\n `${why} server-side tools (${serverTools.map((t) => t.type).join(', ')}) in the same request; ` +\n `send one or the other`\n );\n }\n\n return succeed(resolved);\n}\n\n/**\n * Every valid `StructuredOutputEnforcement`, for the wire-shape guard below.\n *\n * @remarks\n * A **total** `Record`, not a `Set` built from an array literal — the same reasoning\n * as `SCHEMA_NODE_TYPES` in `@fgv/ts-json-base`. A `Set` catches a removed or\n * misspelled member but not an *added* one, so a new enforcement value would compile\n * fine here while this guard silently began rejecting it off a proxy response. The\n * `Record` makes that addition a compile error at this line.\n * @internal\n */\nconst ENFORCEMENTS: Readonly<Record<StructuredOutputEnforcement, true>> = {\n none: true,\n 'json-mode': true,\n schema: true,\n 'tool-forced': true\n};\n\n/**\n * Whether an untyped value off a proxy response is a valid\n * `StructuredOutputEnforcement`.\n * @internal\n */\nexport function isStructuredOutputEnforcement(value: unknown): value is StructuredOutputEnforcement {\n // Indexed read compared to `true`, NOT `in` — `in` walks the prototype chain, so a\n // proxy answering `structuredOutput: 'constructor'` would pass it.\n return typeof value === 'string' && ENFORCEMENTS[value as StructuredOutputEnforcement] === true;\n}\n"]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Copyright (c) 2026 Erik Fortune
|
|
2
|
+
//
|
|
3
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
// of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
// in the Software without restriction, including without limitation the rights
|
|
6
|
+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
// copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
// furnished to do so, subject to the following conditions:
|
|
9
|
+
//
|
|
10
|
+
// The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
// copies or substantial portions of the Software.
|
|
12
|
+
//
|
|
13
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
// SOFTWARE.
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=structuredOutputTypes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"structuredOutputTypes.js","sourceRoot":"","sources":["../../../src/packlets/ai-assist/structuredOutputTypes.ts"],"names":[],"mappings":"AAAA,kCAAkC;AAClC,EAAE;AACF,+EAA+E;AAC/E,gFAAgF;AAChF,+EAA+E;AAC/E,4EAA4E;AAC5E,wEAAwE;AACxE,2DAA2D;AAC3D,EAAE;AACF,iFAAiF;AACjF,kDAAkD;AAClD,EAAE;AACF,6EAA6E;AAC7E,2EAA2E;AAC3E,8EAA8E;AAC9E,yEAAyE;AACzE,gFAAgF;AAChF,gFAAgF;AAChF,YAAY","sourcesContent":["// Copyright (c) 2026 Erik Fortune\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n/**\n * Structured-output types: the capability a provider declares, the request a\n * caller makes, and the enforcement the response reports.\n *\n * @remarks\n * Their own module rather than part of `model.ts` because they depend on nothing\n * there — `model.ts` imports them, not the reverse — and because `model.ts` was\n * at the `max-lines` cap. A dependency-free cut is one of the few available at a\n * moment like that which is not chosen under pressure.\n * @packageDocumentation\n */\n\nimport { type JsonSchema } from '@fgv/ts-json-base';\n\n// ============================================================================\n// Structured output — capability\n// ============================================================================\n\n/**\n * Wire format a provider uses to express a structured-output constraint.\n *\n * @remarks\n * Four shapes, not one, and they differ in more than field names: the OpenAI\n * pair carry the schema in the request body, Gemini carries it inside\n * `generationConfig`, and Anthropic has no response-format field at all —\n * its mechanism is forced tool use, which is why `'tool-forced'` is a distinct\n * {@link AiAssist.StructuredOutputEnforcement} value rather than a spelling of\n * `'schema'`.\n * @public\n */\nexport type AiStructuredOutputFormat =\n | 'openai-json-schema'\n | 'openai-responses-format'\n | 'gemini-response-schema'\n | 'anthropic-tool-forced';\n\n/**\n * Structured-output capability for a model family within a provider. Used as an\n * entry in `IAiProviderDescriptor.structuredOutput`.\n *\n * @remarks\n * Deliberately thinner than its `imageGeneration` / `embedding` siblings: it\n * carries no `supportsX` flags, because what each format can enforce is a\n * property of the provider's **API surface** rather than of any one model, and a\n * per-entry declaration of it could only ever disagree with the one in code.\n * @public\n */\nexport interface IAiStructuredOutputCapability {\n /**\n * Prefix matched against the resolved completion model id. The empty string is\n * the catch-all and matches every model. When multiple rules' prefixes match a\n * model id, the longest prefix wins; ties are broken by first-encountered.\n */\n readonly modelPrefix: string;\n /** Wire format used to express the constraint for matching models. */\n readonly format: AiStructuredOutputFormat;\n}\n\n/**\n * Which constraint the provider was **asked** to apply to this response.\n *\n * @remarks\n * Three questions hide inside *\"did it honour my schema\"*, and they have different\n * owners:\n *\n * | question | answerable by |\n * |---|---|\n * | did we send a constraint? | this client, at request-build time |\n * | which constraint did the provider apply? | this client, from the resolved model's capability |\n * | does *this response* conform to my shape? | the caller's converter, and nothing else |\n *\n * This type answers the first two and deliberately not the third. Reporting\n * conformance would mean re-validating against the caller's own schema to\n * re-derive an answer the caller already holds.\n *\n * - `'none'` — nothing was sent; the resolved model declares no capability.\n * - `'json-mode'` — syntactically valid JSON is guaranteed; the shape is not.\n * - `'schema'` — generation was constrained to the supplied schema.\n * - `'tool-forced'` — Anthropic-style forced tool use; the shape comes from the\n * forced tool's input schema, and `content` is the re-serialized tool input.\n * @public\n */\nexport type StructuredOutputEnforcement = 'none' | 'json-mode' | 'schema' | 'tool-forced';\n\n/**\n * What to do when the resolved model cannot apply the requested constraint.\n *\n * @remarks\n * `'degrade'` is the default, and it is only safe **because\n * `IAiCompletionResponse.structuredOutput` is required** rather than\n * optional. Degrade-and-tell-me is safe; degrade-silently is the failure this\n * whole surface exists to remove — so the two decisions are one decision, not\n * two independent ones.\n *\n * Reach for `'fail'` when the output is persisted or put on a wire, where an\n * unconstrained generation that happens to parse is worse than an error because\n * it is wrong quietly. Leave it at `'degrade'` on paths that are *designed* to\n * degrade — an extractor that may return nothing, a segmenter that floors to a\n * mechanical chunker — where a hard failure would make this library less safe\n * than the code it replaces.\n * @public\n */\nexport type StructuredOutputFallback = 'degrade' | 'fail';\n\n/**\n * Ask the provider for JSON constrained to a schema.\n * @public\n */\nexport interface ISchemaStructuredOutputRequest {\n readonly mode: 'schema';\n /**\n * The schema to constrain generation to — **the same object you validate the\n * reply with**, so the wire schema and the check cannot drift.\n *\n * @remarks\n * Author it with `JsonSchema.object({...})` from `@fgv/ts-json-base`. This is\n * the property `@fgv/ts-extras-ollama`'s `chatStructured` already has; this\n * surface is its cloud sibling.\n */\n readonly schema: JsonSchema.ISchemaValidator<unknown>;\n readonly onUnsupported?: StructuredOutputFallback;\n}\n\n/**\n * Ask the provider for syntactically valid JSON of arbitrary shape.\n *\n * @remarks\n * The weaker floor, and worth having on its own: the failure that motivated this\n * surface (`Expected ',' or '}' after property value` — an unescaped quote closing\n * a string early) is **syntactic**, so a JSON-mode guarantee removes it. Schema\n * constraint is what additionally buys shape. It is also the only mode some\n * model/provider pairs support.\n * @public\n */\nexport interface IJsonObjectStructuredOutputRequest {\n readonly mode: 'json-object';\n readonly onUnsupported?: StructuredOutputFallback;\n}\n\n/**\n * A caller's structured-output intent.\n *\n * @remarks\n * A discriminated union rather than an optional `schema` whose absence means\n * *\"json-object please\"* — an absence that means something is the shape this repo\n * has been burned by (see `MemoryEmbedOutcome` in `@fgv/ts-agent-memory`, which\n * exists because a three-ways-ambiguous absence could not be read).\n *\n * **The caller supplies intent; the response reports outcome.** A request never\n * needs to know whether the constraint will be honoured, because\n * `resolveProviderModel` resolves aliases and tiers at *call* time — a `tier`\n * request can cascade — so the concrete model that will serve a request is not\n * knowable to the caller up front. Requiring it to know would be unsound, which\n * is why the report rides on the response rather than being a lookup.\n * @public\n */\nexport type StructuredOutputRequest = ISchemaStructuredOutputRequest | IJsonObjectStructuredOutputRequest;\n"]}
|