@happyvertical/ai 0.111.6 → 0.111.7

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/README.md CHANGED
@@ -639,10 +639,42 @@ const key = await ai.admin!.createVirtualKey({
639
639
  },
640
640
  });
641
641
 
642
- console.log(key.key);
642
+ console.log(key.key); // the only time the key value is returned
643
+ // Persist key.id, the stable non-secret handle, never key.key.
644
+
645
+ // Budget sync: partial update, omitted fields stay unchanged.
646
+ await ai.admin!.updateVirtualKey(key.id!, {
647
+ budget: { maxLimit: 50, resetDuration: '1M' },
648
+ });
649
+
650
+ // Revoke: resolves false (does not throw) when the key is already gone.
651
+ const revoked = await ai.admin!.deleteVirtualKey(key.id!);
652
+
653
+ // Recover a tenant's project after a crash between create and persist.
654
+ const existing = await ai.admin!.listProjects({ tenantId: 'customer-tenant-a' });
655
+ const same = await ai.admin!.getProject(project.id); // undefined when missing
643
656
  ```
644
657
 
645
- LiteLLM uses the same SDK surface, mapping projects to LiteLLM teams and virtual keys to `/key/generate`.
658
+ | Method | Bifrost | LiteLLM |
659
+ | --- | --- | --- |
660
+ | `createProject` | `POST /api/governance/teams` | `POST /team/new` |
661
+ | `createVirtualKey` | `POST /api/governance/virtual-keys` | `POST /key/generate` |
662
+ | `updateVirtualKey` | `PUT /api/governance/virtual-keys/{id}` | `POST /key/update` |
663
+ | `deleteVirtualKey` | `DELETE /api/governance/virtual-keys/{id}` | `POST /key/delete` |
664
+ | `getProject` | `GET /api/governance/teams/{id}` | `GET /team/info?team_id=` |
665
+ | `listProjects` | `GET /api/governance/teams?customer_id=` | `GET /team/list`, filtered by `metadata.tenant_id` |
666
+
667
+ `AIVirtualKey.id` is the handle for update and delete: the Bifrost virtual-key
668
+ ID, or the LiteLLM hashed token (`token_id`). Only `createVirtualKey()` returns
669
+ a key value; the other methods leave `key` unset and redact key values from
670
+ `raw`, and admin error messages never include them.
671
+
672
+ `updateVirtualKey()` throws `ADMIN_UNSUPPORTED_FIELD` rather than dropping a
673
+ field the gateway's keys lack: Bifrost keys have no `models` or `metadata` (use
674
+ `providerConfigs[].allowedModels`), and LiteLLM keys have no
675
+ `providerConfigs`. On LiteLLM, `metadata` and `description` are merged into the
676
+ key's existing metadata, so the creation-time `tenant_id` is kept. Updating a
677
+ missing key throws; `getProject()` resolves `undefined` for a missing project.
646
678
 
647
679
  ## Generation Safety
648
680
 
@@ -1,6 +1,6 @@
1
1
  import { l as normalizeChatOptions, u as normalizeImageGenerationOptions } from "./usage-B40bJZ49.js";
2
- import { _ as OpenAIProvider } from "./src-EMvAiwjJ.js";
3
- import { i as BifrostAdmin, n as isSpeechModel, o as normalizeGatewayBaseUrl, r as isTranscriptionModel, s as resolveGatewayAdminBaseUrl, t as isAudioModel } from "./gateway-audio-models-Cs7vu956.js";
2
+ import { _ as OpenAIProvider } from "./src-BEkzdHzp.js";
3
+ import { i as BifrostAdmin, n as isSpeechModel, o as normalizeGatewayBaseUrl, r as isTranscriptionModel, s as resolveGatewayAdminBaseUrl, t as isAudioModel } from "./gateway-audio-models-jRog9_6Z.js";
4
4
  import { ValidationError } from "@happyvertical/utils";
5
5
  //#region src/shared/providers/bifrost.ts
6
6
  var BIFROST_CAPABILITIES = {
@@ -199,4 +199,4 @@ var BifrostProvider = class extends OpenAIProvider {
199
199
  //#endregion
200
200
  export { BifrostProvider };
201
201
 
202
- //# sourceMappingURL=bifrost-CrLHqZQJ.js.map
202
+ //# sourceMappingURL=bifrost-DYjHhnpH.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"bifrost-CrLHqZQJ.js","names":[],"sources":["../../src/shared/providers/bifrost.ts"],"sourcesContent":["import { ValidationError } from '@happyvertical/utils';\n\nimport {\n normalizeChatOptions,\n normalizeImageGenerationOptions,\n} from '../safety';\nimport type {\n AICapabilities,\n AIMessage,\n AIModel,\n AIResponse,\n BifrostOptions,\n ChatOptions,\n EmbeddingOptions,\n EmbeddingResponse,\n ImageDescriptionOptions,\n ImageEmbeddingOptions,\n ImageGenerationOptions,\n ImageGenerationResponse,\n} from '../types';\nimport {\n BifrostAdmin,\n normalizeGatewayBaseUrl,\n resolveGatewayAdminBaseUrl,\n} from './gateway-admin';\nimport {\n isAudioModel,\n isSpeechModel,\n isTranscriptionModel,\n} from './gateway-audio-models';\nimport { type OpenAICompatibleProfile, OpenAIProvider } from './openai';\n\nconst BIFROST_CAPABILITIES: AICapabilities = {\n chat: true,\n completion: true,\n embeddings: true,\n streaming: true,\n functions: true,\n vision: true,\n fineTuning: false,\n imageEmbeddings: true,\n imageGeneration: true,\n videoGeneration: false,\n tts: false,\n voiceCloning: false,\n voiceDesign: false,\n maxContextLength: 128000,\n supportedOperations: [\n 'chat',\n 'completion',\n 'embedding',\n 'streaming',\n 'functions',\n 'vision',\n 'image_embedding',\n 'image_generation',\n ],\n};\n\nfunction isEmbeddingModel(modelId: string): boolean {\n return /embed/i.test(modelId);\n}\n\nfunction isImageEmbeddingModel(modelId: string): boolean {\n return /titan-embed-image|multimodalembedding|image[-_]?embed|clip/i.test(\n modelId,\n );\n}\n\nfunction isImageGenerationModel(modelId: string): boolean {\n return /gpt-image|dall-e|imagen|stable[-_ ]diffusion|sdxl|flux|titan-image|image-generator/i.test(\n modelId,\n );\n}\n\nfunction isFilteredBifrostModel(modelId: string): boolean {\n return /moderation|rerank/i.test(modelId);\n}\n\nfunction inferBifrostContextLength(modelId: string): number {\n if (isEmbeddingModel(modelId)) return 8192;\n if (/gemini-1\\.5|gemini-2\\.0|gemini-2\\.5|gemini-3/i.test(modelId)) {\n return 1000000;\n }\n if (/claude/i.test(modelId)) return 200000;\n if (/gpt-4o|gpt-4\\.1|o1|o3|o4|llama|mistral|qwen|deepseek/i.test(modelId)) {\n return 128000;\n }\n return 32768;\n}\n\nfunction inferBifrostFunctions(modelId: string): boolean {\n if (isEmbeddingModel(modelId) || isAudioModel(modelId)) return false;\n return /gpt|claude|gemini|command|llama|mistral|qwen|deepseek|o1|o3|o4/i.test(\n modelId,\n );\n}\n\nfunction inferBifrostVision(modelId: string): boolean {\n if (isAudioModel(modelId)) return false;\n return /gpt-4o|gpt-4\\.1|vision|claude-3|gemini|pixtral|llava|qwen.*vl|vl-/i.test(\n modelId,\n );\n}\n\nfunction inferBifrostCapabilities(modelId: string): string[] {\n if (isTranscriptionModel(modelId)) {\n return ['transcription'];\n }\n\n if (isSpeechModel(modelId)) {\n return ['speech'];\n }\n\n if (isImageGenerationModel(modelId)) {\n return ['image_generation'];\n }\n\n if (isImageEmbeddingModel(modelId)) {\n return ['embeddings', 'image_embedding'];\n }\n\n if (isEmbeddingModel(modelId)) {\n return ['embeddings'];\n }\n\n const capabilities = ['text', 'chat'];\n\n if (inferBifrostFunctions(modelId)) {\n capabilities.push('functions');\n }\n\n if (inferBifrostVision(modelId)) {\n capabilities.push('vision');\n }\n\n return capabilities;\n}\n\nfunction ensureBifrostOptions(options: BifrostOptions): BifrostOptions {\n if (!options.baseUrl?.trim()) {\n throw new ValidationError('Bifrost baseUrl is required', {\n provider: 'bifrost',\n hint: 'Pass baseUrl like http://localhost:8080, http://localhost:8080/openai, or http://localhost:8080/v1 and optionally adminBaseUrl for governance endpoints',\n });\n }\n\n const baseUrl = normalizeGatewayBaseUrl(options.baseUrl);\n const inferenceBaseUrl = /\\/(?:openai|v1|pydanticai\\/v1)$/.test(baseUrl)\n ? baseUrl\n : `${baseUrl}/openai`;\n const headers = options.apiKey\n ? {\n ...options.headers,\n 'x-bf-vk': options.apiKey,\n 'x-api-key': options.apiKey,\n }\n : options.headers;\n\n return {\n ...options,\n baseUrl: inferenceBaseUrl,\n headers,\n };\n}\n\nconst BIFROST_PROFILE: OpenAICompatibleProfile = {\n providerLabel: 'Bifrost',\n providerName: 'bifrost',\n defaultModel: 'openai/gpt-4o-mini',\n capabilities: BIFROST_CAPABILITIES,\n describeModel: (modelId) => `Bifrost model: ${modelId}`,\n getContextLength: inferBifrostContextLength,\n getModelCapabilities: inferBifrostCapabilities,\n shouldIncludeModel: (modelId) => !isFilteredBifrostModel(modelId),\n supportsFunctions: inferBifrostFunctions,\n supportsVision: inferBifrostVision,\n};\n\ntype BifrostResolvedCapability =\n | 'chat'\n | 'vision'\n | 'embeddings'\n | 'image_generation';\n\nfunction supportsCapability(\n model: AIModel,\n capability: BifrostResolvedCapability,\n): boolean {\n if (capability === 'vision') {\n return (\n model.capabilities.includes('vision') || model.supportsVision === true\n );\n }\n\n if (capability === 'embeddings') {\n return model.capabilities.includes('embeddings');\n }\n\n if (capability === 'image_generation') {\n return model.capabilities.includes('image_generation');\n }\n\n return model.capabilities.includes('chat');\n}\n\n/**\n * Bifrost provider implementation.\n *\n * Bifrost exposes OpenAI-compatible inference and governance admin APIs. This\n * provider reuses the OpenAI-compatible transport for inference and exposes\n * `admin` for tenant project and virtual-key provisioning.\n */\nexport class BifrostProvider extends OpenAIProvider {\n readonly admin: BifrostAdmin;\n private readonly configuredDefaultModel?: string;\n private readonly resolvedModelCache = new Map<\n BifrostResolvedCapability,\n Promise<string>\n >();\n\n constructor(options: BifrostOptions) {\n const normalized = ensureBifrostOptions(options);\n super(normalized, BIFROST_PROFILE);\n const adminKey = normalized.adminApiKey || normalized.apiKey;\n const adminUsername = normalized.adminUser || normalized.adminUsername;\n this.admin = new BifrostAdmin({\n provider: 'bifrost',\n baseUrl: resolveGatewayAdminBaseUrl(\n normalized.baseUrl,\n normalized.adminUrl || normalized.adminBaseUrl,\n 'bifrost',\n ),\n apiKey: adminKey,\n username: adminUsername,\n password: normalized.adminPassword,\n headers:\n adminKey && !(adminUsername && normalized.adminPassword)\n ? {\n ...normalized.adminHeaders,\n 'x-bf-vk': adminKey,\n 'x-api-key': adminKey,\n }\n : normalized.adminHeaders,\n timeout: normalized.timeout,\n });\n this.configuredDefaultModel = normalized.defaultModel;\n }\n\n private async resolveModel(\n capability: BifrostResolvedCapability,\n explicitModel?: string,\n ): Promise<string> {\n if (explicitModel) {\n return explicitModel;\n }\n\n if (\n this.configuredDefaultModel &&\n (capability === 'chat' || capability === 'vision')\n ) {\n return this.configuredDefaultModel;\n }\n\n const cached = this.resolvedModelCache.get(capability);\n if (cached) {\n return cached;\n }\n\n const pending = this.selectModel(capability).catch((error) => {\n this.resolvedModelCache.delete(capability);\n throw error;\n });\n this.resolvedModelCache.set(capability, pending);\n return pending;\n }\n\n private async selectModel(\n capability: BifrostResolvedCapability,\n ): Promise<string> {\n const models = await super.getModels();\n const model = models.find((candidate) =>\n supportsCapability(candidate, capability),\n );\n\n if (!model) {\n throw new ValidationError(\n `No ${capability} model is available from the Bifrost gateway`,\n {\n provider: 'bifrost',\n capability,\n hint: 'Pass defaultModel explicitly or ensure the gateway returns a compatible model for this key',\n },\n );\n }\n\n return model.id;\n }\n\n async chat(\n messages: AIMessage[],\n options: ChatOptions = {},\n ): Promise<AIResponse> {\n const safe = normalizeChatOptions(\n this.options,\n options,\n 'bifrost',\n options.model || this.options.defaultModel,\n );\n return super.chat(messages, {\n ...safe,\n model: await this.resolveModel('chat', safe.model),\n });\n }\n\n async *stream(\n messages: AIMessage[],\n options: ChatOptions = {},\n ): AsyncIterable<string> {\n const safe = normalizeChatOptions(\n this.options,\n options,\n 'bifrost',\n options.model || this.options.defaultModel,\n );\n yield* super.stream(messages, {\n ...safe,\n model: await this.resolveModel('chat', safe.model),\n });\n }\n\n async describeImage(\n image: string | Buffer,\n prompt?: string,\n options: ImageDescriptionOptions = {},\n ): Promise<string> {\n const safe = normalizeChatOptions(\n this.options,\n options,\n 'bifrost',\n options.model || this.options.defaultModel,\n );\n return super.describeImage(image, prompt, {\n ...safe,\n model: await this.resolveModel('vision', safe.model),\n });\n }\n\n async embed(\n text: string | string[],\n options: EmbeddingOptions = {},\n ): Promise<EmbeddingResponse> {\n return super.embed(text, {\n ...options,\n model: await this.resolveModel('embeddings', options.model),\n });\n }\n\n async embedImage(\n image: string | Buffer,\n options: ImageEmbeddingOptions = {},\n ): Promise<EmbeddingResponse> {\n return super.embedImage(image, {\n ...options,\n model: await this.resolveModel('embeddings', options.model),\n });\n }\n\n async generateImage(\n prompt: string,\n options: ImageGenerationOptions = {},\n ): Promise<ImageGenerationResponse> {\n const safe = normalizeImageGenerationOptions(\n this.options,\n options,\n 'bifrost',\n options.model,\n );\n return super.generateImage(prompt, {\n ...safe,\n model: await this.resolveModel('image_generation', safe.model),\n });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"bifrost-DYjHhnpH.js","names":[],"sources":["../../src/shared/providers/bifrost.ts"],"sourcesContent":["import { ValidationError } from '@happyvertical/utils';\n\nimport {\n normalizeChatOptions,\n normalizeImageGenerationOptions,\n} from '../safety';\nimport type {\n AICapabilities,\n AIMessage,\n AIModel,\n AIResponse,\n BifrostOptions,\n ChatOptions,\n EmbeddingOptions,\n EmbeddingResponse,\n ImageDescriptionOptions,\n ImageEmbeddingOptions,\n ImageGenerationOptions,\n ImageGenerationResponse,\n} from '../types';\nimport {\n BifrostAdmin,\n normalizeGatewayBaseUrl,\n resolveGatewayAdminBaseUrl,\n} from './gateway-admin';\nimport {\n isAudioModel,\n isSpeechModel,\n isTranscriptionModel,\n} from './gateway-audio-models';\nimport { type OpenAICompatibleProfile, OpenAIProvider } from './openai';\n\nconst BIFROST_CAPABILITIES: AICapabilities = {\n chat: true,\n completion: true,\n embeddings: true,\n streaming: true,\n functions: true,\n vision: true,\n fineTuning: false,\n imageEmbeddings: true,\n imageGeneration: true,\n videoGeneration: false,\n tts: false,\n voiceCloning: false,\n voiceDesign: false,\n maxContextLength: 128000,\n supportedOperations: [\n 'chat',\n 'completion',\n 'embedding',\n 'streaming',\n 'functions',\n 'vision',\n 'image_embedding',\n 'image_generation',\n ],\n};\n\nfunction isEmbeddingModel(modelId: string): boolean {\n return /embed/i.test(modelId);\n}\n\nfunction isImageEmbeddingModel(modelId: string): boolean {\n return /titan-embed-image|multimodalembedding|image[-_]?embed|clip/i.test(\n modelId,\n );\n}\n\nfunction isImageGenerationModel(modelId: string): boolean {\n return /gpt-image|dall-e|imagen|stable[-_ ]diffusion|sdxl|flux|titan-image|image-generator/i.test(\n modelId,\n );\n}\n\nfunction isFilteredBifrostModel(modelId: string): boolean {\n return /moderation|rerank/i.test(modelId);\n}\n\nfunction inferBifrostContextLength(modelId: string): number {\n if (isEmbeddingModel(modelId)) return 8192;\n if (/gemini-1\\.5|gemini-2\\.0|gemini-2\\.5|gemini-3/i.test(modelId)) {\n return 1000000;\n }\n if (/claude/i.test(modelId)) return 200000;\n if (/gpt-4o|gpt-4\\.1|o1|o3|o4|llama|mistral|qwen|deepseek/i.test(modelId)) {\n return 128000;\n }\n return 32768;\n}\n\nfunction inferBifrostFunctions(modelId: string): boolean {\n if (isEmbeddingModel(modelId) || isAudioModel(modelId)) return false;\n return /gpt|claude|gemini|command|llama|mistral|qwen|deepseek|o1|o3|o4/i.test(\n modelId,\n );\n}\n\nfunction inferBifrostVision(modelId: string): boolean {\n if (isAudioModel(modelId)) return false;\n return /gpt-4o|gpt-4\\.1|vision|claude-3|gemini|pixtral|llava|qwen.*vl|vl-/i.test(\n modelId,\n );\n}\n\nfunction inferBifrostCapabilities(modelId: string): string[] {\n if (isTranscriptionModel(modelId)) {\n return ['transcription'];\n }\n\n if (isSpeechModel(modelId)) {\n return ['speech'];\n }\n\n if (isImageGenerationModel(modelId)) {\n return ['image_generation'];\n }\n\n if (isImageEmbeddingModel(modelId)) {\n return ['embeddings', 'image_embedding'];\n }\n\n if (isEmbeddingModel(modelId)) {\n return ['embeddings'];\n }\n\n const capabilities = ['text', 'chat'];\n\n if (inferBifrostFunctions(modelId)) {\n capabilities.push('functions');\n }\n\n if (inferBifrostVision(modelId)) {\n capabilities.push('vision');\n }\n\n return capabilities;\n}\n\nfunction ensureBifrostOptions(options: BifrostOptions): BifrostOptions {\n if (!options.baseUrl?.trim()) {\n throw new ValidationError('Bifrost baseUrl is required', {\n provider: 'bifrost',\n hint: 'Pass baseUrl like http://localhost:8080, http://localhost:8080/openai, or http://localhost:8080/v1 and optionally adminBaseUrl for governance endpoints',\n });\n }\n\n const baseUrl = normalizeGatewayBaseUrl(options.baseUrl);\n const inferenceBaseUrl = /\\/(?:openai|v1|pydanticai\\/v1)$/.test(baseUrl)\n ? baseUrl\n : `${baseUrl}/openai`;\n const headers = options.apiKey\n ? {\n ...options.headers,\n 'x-bf-vk': options.apiKey,\n 'x-api-key': options.apiKey,\n }\n : options.headers;\n\n return {\n ...options,\n baseUrl: inferenceBaseUrl,\n headers,\n };\n}\n\nconst BIFROST_PROFILE: OpenAICompatibleProfile = {\n providerLabel: 'Bifrost',\n providerName: 'bifrost',\n defaultModel: 'openai/gpt-4o-mini',\n capabilities: BIFROST_CAPABILITIES,\n describeModel: (modelId) => `Bifrost model: ${modelId}`,\n getContextLength: inferBifrostContextLength,\n getModelCapabilities: inferBifrostCapabilities,\n shouldIncludeModel: (modelId) => !isFilteredBifrostModel(modelId),\n supportsFunctions: inferBifrostFunctions,\n supportsVision: inferBifrostVision,\n};\n\ntype BifrostResolvedCapability =\n | 'chat'\n | 'vision'\n | 'embeddings'\n | 'image_generation';\n\nfunction supportsCapability(\n model: AIModel,\n capability: BifrostResolvedCapability,\n): boolean {\n if (capability === 'vision') {\n return (\n model.capabilities.includes('vision') || model.supportsVision === true\n );\n }\n\n if (capability === 'embeddings') {\n return model.capabilities.includes('embeddings');\n }\n\n if (capability === 'image_generation') {\n return model.capabilities.includes('image_generation');\n }\n\n return model.capabilities.includes('chat');\n}\n\n/**\n * Bifrost provider implementation.\n *\n * Bifrost exposes OpenAI-compatible inference and governance admin APIs. This\n * provider reuses the OpenAI-compatible transport for inference and exposes\n * `admin` for tenant project and virtual-key provisioning.\n */\nexport class BifrostProvider extends OpenAIProvider {\n readonly admin: BifrostAdmin;\n private readonly configuredDefaultModel?: string;\n private readonly resolvedModelCache = new Map<\n BifrostResolvedCapability,\n Promise<string>\n >();\n\n constructor(options: BifrostOptions) {\n const normalized = ensureBifrostOptions(options);\n super(normalized, BIFROST_PROFILE);\n const adminKey = normalized.adminApiKey || normalized.apiKey;\n const adminUsername = normalized.adminUser || normalized.adminUsername;\n this.admin = new BifrostAdmin({\n provider: 'bifrost',\n baseUrl: resolveGatewayAdminBaseUrl(\n normalized.baseUrl,\n normalized.adminUrl || normalized.adminBaseUrl,\n 'bifrost',\n ),\n apiKey: adminKey,\n username: adminUsername,\n password: normalized.adminPassword,\n headers:\n adminKey && !(adminUsername && normalized.adminPassword)\n ? {\n ...normalized.adminHeaders,\n 'x-bf-vk': adminKey,\n 'x-api-key': adminKey,\n }\n : normalized.adminHeaders,\n timeout: normalized.timeout,\n });\n this.configuredDefaultModel = normalized.defaultModel;\n }\n\n private async resolveModel(\n capability: BifrostResolvedCapability,\n explicitModel?: string,\n ): Promise<string> {\n if (explicitModel) {\n return explicitModel;\n }\n\n if (\n this.configuredDefaultModel &&\n (capability === 'chat' || capability === 'vision')\n ) {\n return this.configuredDefaultModel;\n }\n\n const cached = this.resolvedModelCache.get(capability);\n if (cached) {\n return cached;\n }\n\n const pending = this.selectModel(capability).catch((error) => {\n this.resolvedModelCache.delete(capability);\n throw error;\n });\n this.resolvedModelCache.set(capability, pending);\n return pending;\n }\n\n private async selectModel(\n capability: BifrostResolvedCapability,\n ): Promise<string> {\n const models = await super.getModels();\n const model = models.find((candidate) =>\n supportsCapability(candidate, capability),\n );\n\n if (!model) {\n throw new ValidationError(\n `No ${capability} model is available from the Bifrost gateway`,\n {\n provider: 'bifrost',\n capability,\n hint: 'Pass defaultModel explicitly or ensure the gateway returns a compatible model for this key',\n },\n );\n }\n\n return model.id;\n }\n\n async chat(\n messages: AIMessage[],\n options: ChatOptions = {},\n ): Promise<AIResponse> {\n const safe = normalizeChatOptions(\n this.options,\n options,\n 'bifrost',\n options.model || this.options.defaultModel,\n );\n return super.chat(messages, {\n ...safe,\n model: await this.resolveModel('chat', safe.model),\n });\n }\n\n async *stream(\n messages: AIMessage[],\n options: ChatOptions = {},\n ): AsyncIterable<string> {\n const safe = normalizeChatOptions(\n this.options,\n options,\n 'bifrost',\n options.model || this.options.defaultModel,\n );\n yield* super.stream(messages, {\n ...safe,\n model: await this.resolveModel('chat', safe.model),\n });\n }\n\n async describeImage(\n image: string | Buffer,\n prompt?: string,\n options: ImageDescriptionOptions = {},\n ): Promise<string> {\n const safe = normalizeChatOptions(\n this.options,\n options,\n 'bifrost',\n options.model || this.options.defaultModel,\n );\n return super.describeImage(image, prompt, {\n ...safe,\n model: await this.resolveModel('vision', safe.model),\n });\n }\n\n async embed(\n text: string | string[],\n options: EmbeddingOptions = {},\n ): Promise<EmbeddingResponse> {\n return super.embed(text, {\n ...options,\n model: await this.resolveModel('embeddings', options.model),\n });\n }\n\n async embedImage(\n image: string | Buffer,\n options: ImageEmbeddingOptions = {},\n ): Promise<EmbeddingResponse> {\n return super.embedImage(image, {\n ...options,\n model: await this.resolveModel('embeddings', options.model),\n });\n }\n\n async generateImage(\n prompt: string,\n options: ImageGenerationOptions = {},\n ): Promise<ImageGenerationResponse> {\n const safe = normalizeImageGenerationOptions(\n this.options,\n options,\n 'bifrost',\n options.model,\n );\n return super.generateImage(prompt, {\n ...safe,\n model: await this.resolveModel('image_generation', safe.model),\n });\n }\n}\n"],"mappings":"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"}
@@ -60,6 +60,88 @@ function createMetadata(options, tenantField = "tenant_id") {
60
60
  if (options.description) metadata.description = options.description;
61
61
  return Object.keys(metadata).length > 0 ? metadata : void 0;
62
62
  }
63
+ var REDACTED = "[REDACTED]";
64
+ /**
65
+ * Full gateway key values (`sk-...`, `sk-bf-...`). Masked forms such as
66
+ * `sk-...abcd` do not match because `.` is outside the character class.
67
+ */
68
+ var KEY_VALUE_PATTERN = /\bsk-[A-Za-z0-9_-]{8,}/g;
69
+ /** Response fields that carry key values or other secrets. */
70
+ var SECRET_FIELDS = /* @__PURE__ */ new Set([
71
+ "key",
72
+ "value",
73
+ "token",
74
+ "api_key",
75
+ "apikey",
76
+ "secret",
77
+ "password",
78
+ "master_key"
79
+ ]);
80
+ function escapeRegExp(value) {
81
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
82
+ }
83
+ /**
84
+ * Remove key values from a string: anything shaped like a gateway key, plus
85
+ * each caller-supplied secret (for example the identifier a caller passed,
86
+ * which may itself be a key value).
87
+ */
88
+ function redactSecretText(text, secrets = []) {
89
+ let result = text;
90
+ for (const secret of secrets) if (secret) result = result.replace(new RegExp(escapeRegExp(secret), "g"), REDACTED);
91
+ return result.replace(KEY_VALUE_PATTERN, REDACTED);
92
+ }
93
+ /**
94
+ * Deep-copy a provider response with secret fields and key-shaped strings
95
+ * replaced by `[REDACTED]`.
96
+ */
97
+ function redactSecrets(value, secrets = []) {
98
+ if (typeof value === "string") return redactSecretText(value, secrets);
99
+ if (Array.isArray(value)) return value.map((item) => redactSecrets(item, secrets));
100
+ if (value && typeof value === "object") {
101
+ const result = {};
102
+ for (const [field, item] of Object.entries(value)) result[field] = SECRET_FIELDS.has(field.toLowerCase()) && typeof item === "string" ? REDACTED : redactSecrets(item, secrets);
103
+ return result;
104
+ }
105
+ return value;
106
+ }
107
+ /**
108
+ * First candidate that is a non-empty string and not shaped like a key value,
109
+ * so an `AIVirtualKey.id` never carries a secret even when a caller managed a
110
+ * key by its value.
111
+ */
112
+ function safeKeyHandle(...candidates) {
113
+ for (const candidate of candidates) {
114
+ const value = stringValue(candidate);
115
+ if (value && redactSecretText(value) === value) return value;
116
+ }
117
+ }
118
+ /**
119
+ * Redact key-shaped values from the free-text fields of a result returned by
120
+ * a non-create admin method (a caller-chosen alias or team name), on top of
121
+ * `raw`. Identifiers (`id`, `projectId`, `tenantId`, `budgetId`) are left
122
+ * intact so tenant filtering and lookups still match; key ids are already
123
+ * non-secret via {@link safeKeyHandle}.
124
+ */
125
+ function redactResult(result) {
126
+ for (const field of ["name", "maskedKey"]) {
127
+ const value = result[field];
128
+ if (typeof value === "string") result[field] = redactSecretText(value);
129
+ }
130
+ return result;
131
+ }
132
+ function requireAdminId(id, provider, what) {
133
+ if (typeof id !== "string" || id.trim().length === 0) throw new AIError(`${provider} ${what} id is required`, "ADMIN_INVALID_ARGUMENT", provider);
134
+ return id;
135
+ }
136
+ function unsupportedField(provider, field) {
137
+ return new AIError(`${provider} does not support updating virtual key ${field}`, "ADMIN_UNSUPPORTED_FIELD", provider);
138
+ }
139
+ function isNotFoundError(error, options = {}) {
140
+ if (!(error instanceof AIError)) return false;
141
+ if (error.code === "ADMIN_HTTP_404") return true;
142
+ const pattern = options.messagePattern;
143
+ return Boolean(pattern && options.messageStatuses?.some((status) => error.code === `ADMIN_HTTP_${status}`) && pattern.test(error.message));
144
+ }
63
145
  function responseMessage(body, fallback) {
64
146
  const record = toJsonRecord(body);
65
147
  return stringValue(toJsonRecord(record.error).message) || stringValue(record.message) || stringValue(record.detail) || fallback;
@@ -81,7 +163,11 @@ var GatewayAdminTransport = class {
81
163
  this.headers = options.headers;
82
164
  this.timeout = options.timeout;
83
165
  }
84
- async request(method, path, body, query) {
166
+ /**
167
+ * Send an admin request. Error messages are redacted of key-shaped values
168
+ * and of every string in `secrets`.
169
+ */
170
+ async request(method, path, body, query, secrets = []) {
85
171
  const url = new URL(`${this.baseUrl}${path.startsWith("/") ? path : `/${path}`}`);
86
172
  if (query) {
87
173
  for (const [key, value] of Object.entries(query)) if (value !== void 0) url.searchParams.set(key, value);
@@ -102,11 +188,11 @@ var GatewayAdminTransport = class {
102
188
  });
103
189
  const text = await response.text();
104
190
  const parsed = text ? this.parseJson(text) : void 0;
105
- if (!response.ok) throw this.mapResponseError(response, parsed);
191
+ if (!response.ok) throw this.mapResponseError(response, parsed, secrets);
106
192
  return parsed;
107
193
  } catch (error) {
108
194
  if (error instanceof AIError) throw error;
109
- throw new AIError(`${this.provider} admin request failed: ${error instanceof Error ? error.message : String(error)}`, "ADMIN_REQUEST_FAILED", this.provider);
195
+ throw new AIError(redactSecretText(`${this.provider} admin request failed: ${error instanceof Error ? error.message : String(error)}`, secrets), "ADMIN_REQUEST_FAILED", this.provider);
110
196
  } finally {
111
197
  if (timeoutHandle) clearTimeout(timeoutHandle);
112
198
  }
@@ -122,8 +208,8 @@ var GatewayAdminTransport = class {
122
208
  return { message: text };
123
209
  }
124
210
  }
125
- mapResponseError(response, body) {
126
- const message = responseMessage(body, `${this.provider} admin request failed with ${response.status}`);
211
+ mapResponseError(response, body, secrets) {
212
+ const message = redactSecretText(responseMessage(body, `${this.provider} admin request failed with ${response.status}`), secrets);
127
213
  if (response.status === 401 || response.status === 403) return new AIError(message, "AUTH_ERROR", this.provider);
128
214
  if (response.status === 429) return new AIError(message, "RATE_LIMIT", this.provider, void 0, true);
129
215
  return new AIError(message, `ADMIN_HTTP_${response.status}`, this.provider, void 0, response.status >= 500);
@@ -167,6 +253,44 @@ function mapLiteLLMRateLimit(rateLimit) {
167
253
  rpm_limit: rateLimit?.rpmLimit ?? rateLimit?.requestMaxLimit
168
254
  });
169
255
  }
256
+ function arrayOfRecords(value, ...fields) {
257
+ if (Array.isArray(value)) return value.map(toJsonRecord);
258
+ const record = toJsonRecord(value);
259
+ for (const field of fields) if (Array.isArray(record[field])) return record[field].map(toJsonRecord);
260
+ return [];
261
+ }
262
+ function toBifrostProject(team, raw) {
263
+ const id = stringValue(team.id) || stringValue(team.team_id);
264
+ if (!id) return void 0;
265
+ return redactResult({
266
+ id,
267
+ name: stringValue(team.name) || stringValue(team.team_alias) || id,
268
+ tenantId: stringValue(team.customer_id),
269
+ budgetId: stringValue(team.budget_id),
270
+ provider: "bifrost",
271
+ raw: redactSecrets(raw)
272
+ });
273
+ }
274
+ function toLiteLLMProject(team, raw) {
275
+ const id = stringValue(team.team_id) || stringValue(team.id);
276
+ if (!id) return void 0;
277
+ return redactResult({
278
+ id,
279
+ name: stringValue(team.team_alias) || stringValue(team.name) || id,
280
+ tenantId: stringValue(toJsonRecord(team.metadata).tenant_id),
281
+ budgetId: stringValue(team.budget_id),
282
+ provider: "litellm",
283
+ raw: redactSecrets(raw)
284
+ });
285
+ }
286
+ /**
287
+ * LiteLLM reports some missing resources as 400/500 with a message rather
288
+ * than a 404, depending on version.
289
+ */
290
+ var LITELLM_NOT_FOUND = {
291
+ messageStatuses: [400, 500],
292
+ messagePattern: /not\s+found|does\s+not\s+exist|doesn't\s+exist|failed to delete all keys/i
293
+ };
170
294
  var BifrostAdmin = class {
171
295
  transport;
172
296
  constructor(options) {
@@ -219,6 +343,52 @@ var BifrostAdmin = class {
219
343
  raw: response
220
344
  };
221
345
  }
346
+ async updateVirtualKey(id, options) {
347
+ requireAdminId(id, "bifrost", "virtual key");
348
+ if (options.models !== void 0) throw unsupportedField("bifrost", "models (use providerConfigs)");
349
+ if (options.metadata !== void 0) throw unsupportedField("bifrost", "metadata");
350
+ const body = removeUndefinedValues({
351
+ description: options.description,
352
+ provider_configs: mapBifrostProviderConfigs(options.providerConfigs),
353
+ budget: mapBifrostBudget(options.budget),
354
+ rate_limit: mapBifrostRateLimit(options.rateLimit),
355
+ is_active: options.isActive
356
+ });
357
+ const response = await this.transport.request("PUT", `/api/governance/virtual-keys/${encodeURIComponent(id)}`, body, void 0, [id]);
358
+ const virtualKey = toJsonRecord(response?.virtual_key ?? response?.virtualKey ?? response);
359
+ return redactResult({
360
+ id: safeKeyHandle(virtualKey.id, id),
361
+ name: stringValue(virtualKey.name),
362
+ projectId: stringValue(virtualKey.team_id),
363
+ tenantId: stringValue(virtualKey.customer_id),
364
+ expiresAt: stringValue(virtualKey.expires) || stringValue(virtualKey.expires_at),
365
+ provider: "bifrost",
366
+ raw: redactSecrets(response, [id])
367
+ });
368
+ }
369
+ async deleteVirtualKey(id) {
370
+ requireAdminId(id, "bifrost", "virtual key");
371
+ try {
372
+ await this.transport.request("DELETE", `/api/governance/virtual-keys/${encodeURIComponent(id)}`, void 0, void 0, [id]);
373
+ return true;
374
+ } catch (error) {
375
+ if (isNotFoundError(error)) return false;
376
+ throw error;
377
+ }
378
+ }
379
+ async getProject(id) {
380
+ requireAdminId(id, "bifrost", "project");
381
+ try {
382
+ const response = await this.transport.request("GET", `/api/governance/teams/${encodeURIComponent(id)}`);
383
+ return toBifrostProject(toJsonRecord(response?.team ?? response), response);
384
+ } catch (error) {
385
+ if (isNotFoundError(error)) return void 0;
386
+ throw error;
387
+ }
388
+ }
389
+ async listProjects(filter = {}) {
390
+ return arrayOfRecords(await this.transport.request("GET", "/api/governance/teams", void 0, { customer_id: filter.tenantId }), "teams", "data").map((team) => toBifrostProject(team, team)).filter((project) => project !== void 0 && (filter.tenantId === void 0 || project.tenantId === filter.tenantId));
391
+ }
222
392
  };
223
393
  var LiteLLMAdmin = class {
224
394
  transport;
@@ -264,10 +434,12 @@ var LiteLLMAdmin = class {
264
434
  ...options.raw
265
435
  });
266
436
  const response = await this.transport.request("POST", "/key/generate", body);
437
+ const keyValue = stringValue(response.key);
438
+ const handle = stringValue(response.token_id) || stringValue(response.key_id);
267
439
  return {
268
- id: stringValue(response.token_id) || stringValue(response.key_id),
440
+ id: handle && handle !== keyValue ? safeKeyHandle(handle) : void 0,
269
441
  name: stringValue(response.key_alias) || stringValue(response.key_name) || options.name,
270
- key: stringValue(response.key),
442
+ key: keyValue,
271
443
  maskedKey: stringValue(response.key_name),
272
444
  projectId: stringValue(response.team_id) || options.projectId,
273
445
  tenantId: options.tenantId,
@@ -276,6 +448,64 @@ var LiteLLMAdmin = class {
276
448
  raw: response
277
449
  };
278
450
  }
451
+ async updateVirtualKey(id, options) {
452
+ requireAdminId(id, "litellm", "virtual key");
453
+ if (options.providerConfigs !== void 0) throw unsupportedField("litellm", "providerConfigs");
454
+ let metadata;
455
+ if (options.metadata !== void 0 || options.description !== void 0) metadata = {
456
+ ...toJsonRecord(toJsonRecord((await this.transport.request("GET", "/key/info", void 0, { key: id }, [id]))?.info).metadata),
457
+ ...options.metadata,
458
+ ...options.description !== void 0 ? { description: options.description } : {}
459
+ };
460
+ const body = removeUndefinedValues({
461
+ key: id,
462
+ models: options.models,
463
+ ...mapLiteLLMBudget(options.budget),
464
+ ...mapLiteLLMRateLimit(options.rateLimit),
465
+ metadata,
466
+ blocked: options.isActive === void 0 ? void 0 : !options.isActive
467
+ });
468
+ const response = await this.transport.request("POST", "/key/update", body, void 0, [id]);
469
+ const updated = toJsonRecord(response);
470
+ return redactResult({
471
+ id: safeKeyHandle(id, updated.token_id, updated.token),
472
+ name: stringValue(updated.key_alias),
473
+ maskedKey: stringValue(updated.key_name),
474
+ projectId: stringValue(updated.team_id),
475
+ tenantId: stringValue(toJsonRecord(updated.metadata ?? metadata).tenant_id),
476
+ expiresAt: stringValue(updated.expires),
477
+ provider: "litellm",
478
+ raw: redactSecrets(response, [id])
479
+ });
480
+ }
481
+ async deleteVirtualKey(id) {
482
+ requireAdminId(id, "litellm", "virtual key");
483
+ try {
484
+ const deleted = toJsonRecord(await this.transport.request("POST", "/key/delete", { keys: [id] }, void 0, [id])).deleted_keys;
485
+ return !(Array.isArray(deleted) && deleted.length === 0);
486
+ } catch (error) {
487
+ if (isNotFoundError(error, LITELLM_NOT_FOUND)) return false;
488
+ throw error;
489
+ }
490
+ }
491
+ async getProject(id) {
492
+ requireAdminId(id, "litellm", "project");
493
+ try {
494
+ const response = await this.transport.request("GET", "/team/info", void 0, { team_id: id });
495
+ const record = toJsonRecord(response);
496
+ const team = toJsonRecord(record.team_info ?? record);
497
+ return toLiteLLMProject({
498
+ ...team,
499
+ team_id: stringValue(team.team_id) || stringValue(record.team_id)
500
+ }, response);
501
+ } catch (error) {
502
+ if (isNotFoundError(error, LITELLM_NOT_FOUND)) return void 0;
503
+ throw error;
504
+ }
505
+ }
506
+ async listProjects(filter = {}) {
507
+ return arrayOfRecords(await this.transport.request("GET", "/team/list"), "teams", "data").map((team) => toLiteLLMProject(team, team)).filter((project) => project !== void 0 && (filter.tenantId === void 0 || project.tenantId === filter.tenantId));
508
+ }
279
509
  };
280
510
  //#endregion
281
511
  //#region src/shared/providers/gateway-audio-models.ts
@@ -330,4 +560,4 @@ function isAudioModel(modelId) {
330
560
  //#endregion
331
561
  export { LiteLLMAdmin as a, BifrostAdmin as i, isSpeechModel as n, normalizeGatewayBaseUrl as o, isTranscriptionModel as r, resolveGatewayAdminBaseUrl as s, isAudioModel as t };
332
562
 
333
- //# sourceMappingURL=gateway-audio-models-Cs7vu956.js.map
563
+ //# sourceMappingURL=gateway-audio-models-jRog9_6Z.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateway-audio-models-jRog9_6Z.js","names":[],"sources":["../../src/shared/providers/gateway-admin.ts","../../src/shared/providers/gateway-audio-models.ts"],"sourcesContent":["import type {\n AIAdminBudget,\n AIAdminInterface,\n AIAdminProject,\n AIAdminProviderConfig,\n AIAdminRateLimit,\n AIVirtualKey,\n CreateAIProjectOptions,\n CreateAIVirtualKeyOptions,\n ListAIProjectsFilter,\n UpdateAIVirtualKeyOptions,\n} from '../types';\nimport { AIError } from '../types';\n\ninterface GatewayAdminTransportOptions {\n provider: string;\n baseUrl: string;\n apiKey?: string;\n username?: string;\n password?: string;\n headers?: Record<string, string>;\n timeout?: number;\n}\n\ntype JsonRecord = Record<string, unknown>;\n\nfunction stripTrailingSlash(value: string): string {\n return value.endsWith('/') ? value.slice(0, -1) : value;\n}\n\nexport function normalizeGatewayBaseUrl(baseUrl: string): string {\n return stripTrailingSlash(baseUrl.trim());\n}\n\n/**\n * Derive the gateway root URL for admin APIs from an OpenAI-compatible base URL.\n *\n * Strips known inference path suffixes iteratively so that a baseUrl such as\n * `http://host/openai/v1` collapses to the gateway root rather than to the\n * partial `http://host/openai`.\n */\nexport function deriveGatewayAdminBaseUrl(baseUrl: string): string {\n let normalized = normalizeGatewayBaseUrl(baseUrl);\n const suffixes = ['/pydanticai/v1', '/openai', '/v1'];\n let stripped = true;\n\n while (stripped) {\n stripped = false;\n for (const suffix of suffixes) {\n if (normalized.endsWith(suffix)) {\n normalized = normalized.slice(0, -suffix.length);\n stripped = true;\n break;\n }\n }\n }\n\n return normalized || normalizeGatewayBaseUrl(baseUrl);\n}\n\nexport function resolveGatewayAdminBaseUrl(\n baseUrl: string | undefined,\n adminBaseUrl: string | undefined,\n provider: string,\n): string {\n const configuredBaseUrl = adminBaseUrl || baseUrl;\n if (!configuredBaseUrl?.trim()) {\n throw new AIError(\n `${provider} baseUrl is required for admin operations`,\n 'ADMIN_BASE_URL_REQUIRED',\n provider,\n );\n }\n\n return adminBaseUrl\n ? normalizeGatewayBaseUrl(adminBaseUrl)\n : deriveGatewayAdminBaseUrl(configuredBaseUrl);\n}\n\nexport function slugifyProjectId(name: string, tenantId?: string): string {\n const value = [tenantId, name].filter(Boolean).join('-');\n const slug = value\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '');\n\n return slug || 'ai-project';\n}\n\nfunction toJsonRecord(value: unknown): JsonRecord {\n return value && typeof value === 'object' && !Array.isArray(value)\n ? (value as JsonRecord)\n : {};\n}\n\nfunction stringValue(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\nfunction encodeBasicCredentials(username: string, password: string): string {\n const value = `${username}:${password}`;\n\n if (typeof btoa === 'function') {\n return btoa(value);\n }\n\n return Buffer.from(value, 'utf8').toString('base64');\n}\n\nfunction removeUndefinedValues<T extends JsonRecord>(value: T): T {\n for (const key of Object.keys(value)) {\n if (value[key] === undefined) {\n delete value[key];\n }\n }\n\n return value;\n}\n\nfunction createMetadata(\n options: {\n tenantId?: string;\n description?: string;\n metadata?: Record<string, unknown>;\n },\n tenantField = 'tenant_id',\n): Record<string, unknown> | undefined {\n const metadata = {\n ...options.metadata,\n };\n\n if (options.tenantId) {\n metadata[tenantField] = options.tenantId;\n }\n\n if (options.description) {\n metadata.description = options.description;\n }\n\n return Object.keys(metadata).length > 0 ? metadata : undefined;\n}\n\nconst REDACTED = '[REDACTED]';\n\n/**\n * Full gateway key values (`sk-...`, `sk-bf-...`). Masked forms such as\n * `sk-...abcd` do not match because `.` is outside the character class.\n */\nconst KEY_VALUE_PATTERN = /\\bsk-[A-Za-z0-9_-]{8,}/g;\n\n/** Response fields that carry key values or other secrets. */\nconst SECRET_FIELDS = new Set([\n 'key',\n 'value',\n 'token',\n 'api_key',\n 'apikey',\n 'secret',\n 'password',\n 'master_key',\n]);\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Remove key values from a string: anything shaped like a gateway key, plus\n * each caller-supplied secret (for example the identifier a caller passed,\n * which may itself be a key value).\n */\nexport function redactSecretText(text: string, secrets: string[] = []): string {\n let result = text;\n for (const secret of secrets) {\n if (secret) {\n result = result.replace(new RegExp(escapeRegExp(secret), 'g'), REDACTED);\n }\n }\n return result.replace(KEY_VALUE_PATTERN, REDACTED);\n}\n\n/**\n * Deep-copy a provider response with secret fields and key-shaped strings\n * replaced by `[REDACTED]`.\n */\nexport function redactSecrets(value: unknown, secrets: string[] = []): unknown {\n if (typeof value === 'string') {\n return redactSecretText(value, secrets);\n }\n\n if (Array.isArray(value)) {\n return value.map((item) => redactSecrets(item, secrets));\n }\n\n if (value && typeof value === 'object') {\n const result: JsonRecord = {};\n for (const [field, item] of Object.entries(value as JsonRecord)) {\n result[field] =\n SECRET_FIELDS.has(field.toLowerCase()) && typeof item === 'string'\n ? REDACTED\n : redactSecrets(item, secrets);\n }\n return result;\n }\n\n return value;\n}\n\n/**\n * First candidate that is a non-empty string and not shaped like a key value,\n * so an `AIVirtualKey.id` never carries a secret even when a caller managed a\n * key by its value.\n */\nfunction safeKeyHandle(...candidates: unknown[]): string | undefined {\n for (const candidate of candidates) {\n const value = stringValue(candidate);\n if (value && redactSecretText(value) === value) return value;\n }\n\n return undefined;\n}\n\n/**\n * Redact key-shaped values from the free-text fields of a result returned by\n * a non-create admin method (a caller-chosen alias or team name), on top of\n * `raw`. Identifiers (`id`, `projectId`, `tenantId`, `budgetId`) are left\n * intact so tenant filtering and lookups still match; key ids are already\n * non-secret via {@link safeKeyHandle}.\n */\nfunction redactResult<T extends { name?: string; maskedKey?: string }>(\n result: T,\n): T {\n for (const field of ['name', 'maskedKey'] as const) {\n const value = result[field];\n if (typeof value === 'string') {\n result[field] = redactSecretText(value) as T[typeof field];\n }\n }\n return result;\n}\n\nfunction requireAdminId(id: string, provider: string, what: string): string {\n if (typeof id !== 'string' || id.trim().length === 0) {\n throw new AIError(\n `${provider} ${what} id is required`,\n 'ADMIN_INVALID_ARGUMENT',\n provider,\n );\n }\n\n return id;\n}\n\nfunction unsupportedField(provider: string, field: string): AIError {\n return new AIError(\n `${provider} does not support updating virtual key ${field}`,\n 'ADMIN_UNSUPPORTED_FIELD',\n provider,\n );\n}\n\nfunction isNotFoundError(\n error: unknown,\n options: { messageStatuses?: number[]; messagePattern?: RegExp } = {},\n): boolean {\n if (!(error instanceof AIError)) return false;\n if (error.code === 'ADMIN_HTTP_404') return true;\n\n const pattern = options.messagePattern;\n return Boolean(\n pattern &&\n options.messageStatuses?.some(\n (status) => error.code === `ADMIN_HTTP_${status}`,\n ) &&\n pattern.test(error.message),\n );\n}\n\nfunction responseMessage(body: unknown, fallback: string): string {\n const record = toJsonRecord(body);\n const error = toJsonRecord(record.error);\n\n return (\n stringValue(error.message) ||\n stringValue(record.message) ||\n stringValue(record.detail) ||\n fallback\n );\n}\n\nclass GatewayAdminTransport {\n private readonly baseUrl: string;\n private readonly provider: string;\n private readonly apiKey?: string;\n private readonly username?: string;\n private readonly password?: string;\n private readonly headers?: Record<string, string>;\n private readonly timeout?: number;\n\n constructor(options: GatewayAdminTransportOptions) {\n this.provider = options.provider;\n this.baseUrl = normalizeGatewayBaseUrl(options.baseUrl);\n this.apiKey = options.apiKey;\n this.username = options.username;\n this.password = options.password;\n this.headers = options.headers;\n this.timeout = options.timeout;\n }\n\n /**\n * Send an admin request. Error messages are redacted of key-shaped values\n * and of every string in `secrets`.\n */\n async request<T>(\n method: string,\n path: string,\n body?: unknown,\n query?: Record<string, string | undefined>,\n secrets: string[] = [],\n ): Promise<T> {\n const url = new URL(\n `${this.baseUrl}${path.startsWith('/') ? path : `/${path}`}`,\n );\n\n if (query) {\n for (const [key, value] of Object.entries(query)) {\n if (value !== undefined) {\n url.searchParams.set(key, value);\n }\n }\n }\n\n const controller =\n typeof AbortController === 'undefined'\n ? undefined\n : new AbortController();\n const timeoutHandle =\n controller && this.timeout\n ? setTimeout(() => controller.abort(), this.timeout)\n : undefined;\n\n try {\n const response = await fetch(url, {\n method,\n headers: {\n Accept: 'application/json',\n ...(body !== undefined ? { 'Content-Type': 'application/json' } : {}),\n ...this.headers,\n ...this.createAuthHeaders(),\n },\n body: body === undefined ? undefined : JSON.stringify(body),\n signal: controller?.signal,\n });\n\n const text = await response.text();\n const parsed = text ? this.parseJson(text) : undefined;\n\n if (!response.ok) {\n throw this.mapResponseError(response, parsed, secrets);\n }\n\n return parsed as T;\n } catch (error) {\n if (error instanceof AIError) {\n throw error;\n }\n\n throw new AIError(\n redactSecretText(\n `${this.provider} admin request failed: ${\n error instanceof Error ? error.message : String(error)\n }`,\n secrets,\n ),\n 'ADMIN_REQUEST_FAILED',\n this.provider,\n );\n } finally {\n if (timeoutHandle) {\n clearTimeout(timeoutHandle);\n }\n }\n }\n\n private createAuthHeaders(): Record<string, string> {\n if (this.username && this.password) {\n return {\n Authorization: `Basic ${encodeBasicCredentials(\n this.username,\n this.password,\n )}`,\n };\n }\n\n return this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {};\n }\n\n private parseJson(text: string): unknown {\n try {\n return JSON.parse(text);\n } catch (_error) {\n return { message: text };\n }\n }\n\n private mapResponseError(\n response: Response,\n body: unknown,\n secrets: string[],\n ): AIError {\n const message = redactSecretText(\n responseMessage(\n body,\n `${this.provider} admin request failed with ${response.status}`,\n ),\n secrets,\n );\n\n if (response.status === 401 || response.status === 403) {\n return new AIError(message, 'AUTH_ERROR', this.provider);\n }\n\n if (response.status === 429) {\n return new AIError(message, 'RATE_LIMIT', this.provider, undefined, true);\n }\n\n return new AIError(\n message,\n `ADMIN_HTTP_${response.status}`,\n this.provider,\n undefined,\n response.status >= 500,\n );\n }\n}\n\nfunction mapBifrostBudget(budget?: AIAdminBudget): JsonRecord | undefined {\n if (!budget) return undefined;\n\n return removeUndefinedValues({\n max_limit: budget.maxLimit,\n reset_duration: budget.resetDuration,\n calendar_aligned: budget.calendarAligned,\n });\n}\n\nfunction mapBifrostRateLimit(\n rateLimit?: AIAdminRateLimit,\n): JsonRecord | undefined {\n if (!rateLimit) return undefined;\n\n return removeUndefinedValues({\n token_max_limit: rateLimit.tokenMaxLimit ?? rateLimit.tpmLimit,\n token_reset_duration: rateLimit.tokenResetDuration,\n request_max_limit: rateLimit.requestMaxLimit ?? rateLimit.rpmLimit,\n request_reset_duration: rateLimit.requestResetDuration,\n });\n}\n\nfunction mapBifrostProviderConfigs(\n configs?: AIAdminProviderConfig[],\n): JsonRecord[] | undefined {\n if (!configs) return undefined;\n\n return configs.map((config) =>\n removeUndefinedValues({\n provider: config.provider,\n weight: config.weight,\n allowed_models: config.allowedModels,\n key_ids: config.keyIds,\n }),\n );\n}\n\nfunction mapLiteLLMBudget(\n budget?: AIAdminBudget,\n): Pick<JsonRecord, 'max_budget' | 'budget_duration'> {\n return removeUndefinedValues({\n max_budget: budget?.maxLimit,\n budget_duration: budget?.resetDuration,\n });\n}\n\nfunction mapLiteLLMRateLimit(\n rateLimit?: AIAdminRateLimit,\n): Pick<JsonRecord, 'tpm_limit' | 'rpm_limit'> {\n return removeUndefinedValues({\n tpm_limit: rateLimit?.tpmLimit ?? rateLimit?.tokenMaxLimit,\n rpm_limit: rateLimit?.rpmLimit ?? rateLimit?.requestMaxLimit,\n });\n}\n\nfunction arrayOfRecords(value: unknown, ...fields: string[]): JsonRecord[] {\n if (Array.isArray(value)) return value.map(toJsonRecord);\n\n const record = toJsonRecord(value);\n for (const field of fields) {\n if (Array.isArray(record[field])) {\n return (record[field] as unknown[]).map(toJsonRecord);\n }\n }\n\n return [];\n}\n\nfunction toBifrostProject(\n team: JsonRecord,\n raw: unknown,\n): AIAdminProject | undefined {\n const id = stringValue(team.id) || stringValue(team.team_id);\n if (!id) return undefined;\n\n return redactResult({\n id,\n name: stringValue(team.name) || stringValue(team.team_alias) || id,\n tenantId: stringValue(team.customer_id),\n budgetId: stringValue(team.budget_id),\n provider: 'bifrost',\n raw: redactSecrets(raw),\n });\n}\n\nfunction toLiteLLMProject(\n team: JsonRecord,\n raw: unknown,\n): AIAdminProject | undefined {\n const id = stringValue(team.team_id) || stringValue(team.id);\n if (!id) return undefined;\n\n return redactResult({\n id,\n name: stringValue(team.team_alias) || stringValue(team.name) || id,\n tenantId: stringValue(toJsonRecord(team.metadata).tenant_id),\n budgetId: stringValue(team.budget_id),\n provider: 'litellm',\n raw: redactSecrets(raw),\n });\n}\n\n/**\n * LiteLLM reports some missing resources as 400/500 with a message rather\n * than a 404, depending on version.\n */\nconst LITELLM_NOT_FOUND = {\n messageStatuses: [400, 500],\n messagePattern:\n /not\\s+found|does\\s+not\\s+exist|doesn't\\s+exist|failed to delete all keys/i,\n};\n\nexport class BifrostAdmin implements AIAdminInterface {\n private readonly transport: GatewayAdminTransport;\n\n constructor(options: GatewayAdminTransportOptions) {\n this.transport = new GatewayAdminTransport(options);\n }\n\n async createProject(\n options: CreateAIProjectOptions,\n ): Promise<AIAdminProject> {\n const body = removeUndefinedValues({\n name: options.name,\n customer_id: options.tenantId,\n budget: mapBifrostBudget(options.budget),\n ...options.raw,\n });\n\n const response = await this.transport.request<JsonRecord>(\n 'POST',\n '/api/governance/teams',\n body,\n );\n const team = toJsonRecord(response.team ?? response);\n const id = stringValue(team.id) || stringValue(team.team_id);\n\n if (!id) {\n throw new AIError(\n 'Bifrost did not return a project id',\n 'ADMIN_INVALID_RESPONSE',\n 'bifrost',\n );\n }\n\n return {\n id,\n name:\n stringValue(team.name) || stringValue(team.team_alias) || options.name,\n tenantId: stringValue(team.customer_id) || options.tenantId,\n budgetId: stringValue(team.budget_id),\n provider: 'bifrost',\n raw: response,\n };\n }\n\n async createVirtualKey(\n options: CreateAIVirtualKeyOptions,\n ): Promise<AIVirtualKey> {\n const body = removeUndefinedValues({\n name: options.name,\n description: options.description,\n provider_configs: mapBifrostProviderConfigs(options.providerConfigs),\n team_id: options.projectId,\n customer_id: options.projectId ? undefined : options.tenantId,\n budget: mapBifrostBudget(options.budget),\n rate_limit: mapBifrostRateLimit(options.rateLimit),\n key_ids: options.keyIds,\n is_active: options.isActive ?? true,\n ...options.raw,\n });\n\n const response = await this.transport.request<JsonRecord>(\n 'POST',\n '/api/governance/virtual-keys',\n body,\n );\n const virtualKey = toJsonRecord(\n response.virtual_key ?? response.virtualKey ?? response,\n );\n\n return {\n id: stringValue(virtualKey.id) || stringValue(virtualKey.key_id),\n name: stringValue(virtualKey.name) || options.name,\n key: stringValue(virtualKey.value) || stringValue(virtualKey.key),\n maskedKey:\n stringValue(virtualKey.key_name) || stringValue(virtualKey.masked_key),\n projectId: stringValue(virtualKey.team_id) || options.projectId,\n tenantId: stringValue(virtualKey.customer_id) || options.tenantId,\n expiresAt:\n stringValue(virtualKey.expires) || stringValue(virtualKey.expires_at),\n provider: 'bifrost',\n raw: response,\n };\n }\n\n async updateVirtualKey(\n id: string,\n options: UpdateAIVirtualKeyOptions,\n ): Promise<AIVirtualKey> {\n requireAdminId(id, 'bifrost', 'virtual key');\n if (options.models !== undefined) {\n throw unsupportedField('bifrost', 'models (use providerConfigs)');\n }\n if (options.metadata !== undefined) {\n throw unsupportedField('bifrost', 'metadata');\n }\n\n const body = removeUndefinedValues({\n description: options.description,\n provider_configs: mapBifrostProviderConfigs(options.providerConfigs),\n budget: mapBifrostBudget(options.budget),\n rate_limit: mapBifrostRateLimit(options.rateLimit),\n is_active: options.isActive,\n });\n\n const response = await this.transport.request<JsonRecord>(\n 'PUT',\n `/api/governance/virtual-keys/${encodeURIComponent(id)}`,\n body,\n undefined,\n [id],\n );\n const virtualKey = toJsonRecord(\n response?.virtual_key ?? response?.virtualKey ?? response,\n );\n\n return redactResult({\n id: safeKeyHandle(virtualKey.id, id),\n name: stringValue(virtualKey.name),\n projectId: stringValue(virtualKey.team_id),\n tenantId: stringValue(virtualKey.customer_id),\n expiresAt:\n stringValue(virtualKey.expires) || stringValue(virtualKey.expires_at),\n provider: 'bifrost',\n raw: redactSecrets(response, [id]),\n });\n }\n\n async deleteVirtualKey(id: string): Promise<boolean> {\n requireAdminId(id, 'bifrost', 'virtual key');\n\n try {\n await this.transport.request<unknown>(\n 'DELETE',\n `/api/governance/virtual-keys/${encodeURIComponent(id)}`,\n undefined,\n undefined,\n [id],\n );\n return true;\n } catch (error) {\n if (isNotFoundError(error)) return false;\n throw error;\n }\n }\n\n async getProject(id: string): Promise<AIAdminProject | undefined> {\n requireAdminId(id, 'bifrost', 'project');\n\n try {\n const response = await this.transport.request<JsonRecord>(\n 'GET',\n `/api/governance/teams/${encodeURIComponent(id)}`,\n );\n return toBifrostProject(\n toJsonRecord(response?.team ?? response),\n response,\n );\n } catch (error) {\n if (isNotFoundError(error)) return undefined;\n throw error;\n }\n }\n\n async listProjects(\n filter: ListAIProjectsFilter = {},\n ): Promise<AIAdminProject[]> {\n const response = await this.transport.request<unknown>(\n 'GET',\n '/api/governance/teams',\n undefined,\n { customer_id: filter.tenantId },\n );\n\n return arrayOfRecords(response, 'teams', 'data')\n .map((team) => toBifrostProject(team, team))\n .filter(\n (project): project is AIAdminProject =>\n project !== undefined &&\n (filter.tenantId === undefined ||\n project.tenantId === filter.tenantId),\n );\n }\n}\n\nexport class LiteLLMAdmin implements AIAdminInterface {\n private readonly transport: GatewayAdminTransport;\n\n constructor(options: GatewayAdminTransportOptions) {\n this.transport = new GatewayAdminTransport(options);\n }\n\n async createProject(\n options: CreateAIProjectOptions,\n ): Promise<AIAdminProject> {\n const projectId =\n options.id || slugifyProjectId(options.name, options.tenantId);\n const body = removeUndefinedValues({\n team_id: projectId,\n team_alias: options.name,\n models: options.models,\n ...mapLiteLLMBudget(options.budget),\n ...mapLiteLLMRateLimit(options.rateLimit),\n metadata: createMetadata(options),\n blocked: options.isBlocked,\n ...options.raw,\n });\n\n const response = await this.transport.request<JsonRecord>(\n 'POST',\n '/team/new',\n body,\n );\n const team = toJsonRecord(response.team ?? response);\n\n return {\n id: stringValue(team.team_id) || projectId,\n name:\n stringValue(team.team_alias) || stringValue(team.name) || options.name,\n tenantId: options.tenantId,\n provider: 'litellm',\n raw: response,\n };\n }\n\n async createVirtualKey(\n options: CreateAIVirtualKeyOptions,\n ): Promise<AIVirtualKey> {\n const body = removeUndefinedValues({\n key_alias: options.name,\n team_id: options.projectId,\n user_id: options.userId,\n models: options.models,\n duration: options.duration,\n ...mapLiteLLMBudget(options.budget),\n ...mapLiteLLMRateLimit(options.rateLimit),\n metadata: createMetadata(options),\n aliases: options.aliases,\n config: options.config,\n permissions: options.permissions,\n blocked: options.isActive === undefined ? undefined : !options.isActive,\n ...options.raw,\n });\n\n const response = await this.transport.request<JsonRecord>(\n 'POST',\n '/key/generate',\n body,\n );\n\n const keyValue = stringValue(response.key);\n const handle =\n stringValue(response.token_id) || stringValue(response.key_id);\n\n return {\n // Never let the key value double as the stable handle.\n id: handle && handle !== keyValue ? safeKeyHandle(handle) : undefined,\n name:\n stringValue(response.key_alias) ||\n stringValue(response.key_name) ||\n options.name,\n key: keyValue,\n maskedKey: stringValue(response.key_name),\n projectId: stringValue(response.team_id) || options.projectId,\n tenantId: options.tenantId,\n expiresAt: stringValue(response.expires),\n provider: 'litellm',\n raw: response,\n };\n }\n\n async updateVirtualKey(\n id: string,\n options: UpdateAIVirtualKeyOptions,\n ): Promise<AIVirtualKey> {\n requireAdminId(id, 'litellm', 'virtual key');\n if (options.providerConfigs !== undefined) {\n throw unsupportedField('litellm', 'providerConfigs');\n }\n\n let metadata: Record<string, unknown> | undefined;\n if (options.metadata !== undefined || options.description !== undefined) {\n // LiteLLM replaces metadata wholesale; merge so tenant_id survives.\n const info = await this.transport.request<JsonRecord>(\n 'GET',\n '/key/info',\n undefined,\n { key: id },\n [id],\n );\n metadata = {\n ...toJsonRecord(toJsonRecord(info?.info).metadata),\n ...options.metadata,\n ...(options.description !== undefined\n ? { description: options.description }\n : {}),\n };\n }\n\n const body = removeUndefinedValues({\n key: id,\n models: options.models,\n ...mapLiteLLMBudget(options.budget),\n ...mapLiteLLMRateLimit(options.rateLimit),\n metadata,\n blocked: options.isActive === undefined ? undefined : !options.isActive,\n });\n\n const response = await this.transport.request<JsonRecord>(\n 'POST',\n '/key/update',\n body,\n undefined,\n [id],\n );\n const updated = toJsonRecord(response);\n\n return redactResult({\n id: safeKeyHandle(id, updated.token_id, updated.token),\n name: stringValue(updated.key_alias),\n maskedKey: stringValue(updated.key_name),\n projectId: stringValue(updated.team_id),\n tenantId: stringValue(\n toJsonRecord(updated.metadata ?? metadata).tenant_id,\n ),\n expiresAt: stringValue(updated.expires),\n provider: 'litellm',\n raw: redactSecrets(response, [id]),\n });\n }\n\n async deleteVirtualKey(id: string): Promise<boolean> {\n requireAdminId(id, 'litellm', 'virtual key');\n\n try {\n const response = await this.transport.request<JsonRecord>(\n 'POST',\n '/key/delete',\n { keys: [id] },\n undefined,\n [id],\n );\n const deleted = toJsonRecord(response).deleted_keys;\n return !(Array.isArray(deleted) && deleted.length === 0);\n } catch (error) {\n if (isNotFoundError(error, LITELLM_NOT_FOUND)) return false;\n throw error;\n }\n }\n\n async getProject(id: string): Promise<AIAdminProject | undefined> {\n requireAdminId(id, 'litellm', 'project');\n\n try {\n const response = await this.transport.request<JsonRecord>(\n 'GET',\n '/team/info',\n undefined,\n { team_id: id },\n );\n const record = toJsonRecord(response);\n const team = toJsonRecord(record.team_info ?? record);\n return toLiteLLMProject(\n {\n ...team,\n team_id: stringValue(team.team_id) || stringValue(record.team_id),\n },\n response,\n );\n } catch (error) {\n if (isNotFoundError(error, LITELLM_NOT_FOUND)) return undefined;\n throw error;\n }\n }\n\n async listProjects(\n filter: ListAIProjectsFilter = {},\n ): Promise<AIAdminProject[]> {\n const response = await this.transport.request<unknown>('GET', '/team/list');\n\n return arrayOfRecords(response, 'teams', 'data')\n .map((team) => toLiteLLMProject(team, team))\n .filter(\n (project): project is AIAdminProject =>\n project !== undefined &&\n (filter.tenantId === undefined ||\n project.tenantId === filter.tenantId),\n );\n }\n}\n","/**\n * Audio model classification shared by the Bifrost and LiteLLM gateway\n * providers.\n *\n * Markers are matched as whole model-ID tokens (split on `/`, `-`, `_`, `.`,\n * `:` and whitespace), never as raw substrings, so an author or model segment\n * that merely contains a marker (e.g. `tts` inside `mattshumer`) is not\n * classified as audio.\n */\n\nfunction modelIdTokens(modelId: string): string[] {\n return modelId\n .toLowerCase()\n .split(/[/_.:\\s-]+/)\n .filter(Boolean);\n}\n\nfunction hasTokenSequence(tokens: string[], sequence: string[]): boolean {\n for (let i = 0; i + sequence.length <= tokens.length; i++) {\n if (sequence.every((token, offset) => tokens[i + offset] === token)) {\n return true;\n }\n }\n return false;\n}\n\nconst TRANSCRIPTION_TOKENS = new Set([\n 'transcribe',\n 'transcriber',\n 'transcription',\n 'whisper',\n]);\n\nconst SPEECH_TOKENS = new Set(['tts', 'speech']);\n\n/**\n * Speech-to-text models (e.g. `whisper-1`, `gpt-4o-transcribe`,\n * `speech-to-text-v1`, `voxtral-mini-4b-realtime`). Listed with the\n * `transcription` capability so gateway consumers can route audio to them;\n * never eligible for chat, vision, embeddings, or image generation.\n */\nexport function isTranscriptionModel(modelId: string): boolean {\n const tokens = modelIdTokens(modelId);\n return (\n tokens.some((token) => TRANSCRIPTION_TOKENS.has(token)) ||\n hasTokenSequence(tokens, ['speech', 'to', 'text']) ||\n // Voxtral realtime checkpoints are streaming speech-to-text only; other\n // Voxtral models are audio-understanding chat models.\n (tokens.includes('voxtral') && tokens.includes('realtime'))\n );\n}\n\n/**\n * Text-to-speech models (e.g. `tts-1`, `gpt-4o-mini-tts`). Listed with the\n * `speech` capability; never eligible for chat, vision, embeddings, or image\n * generation.\n */\nexport function isSpeechModel(modelId: string): boolean {\n return (\n !isTranscriptionModel(modelId) &&\n modelIdTokens(modelId).some((token) => SPEECH_TOKENS.has(token))\n );\n}\n\nexport function isAudioModel(modelId: string): boolean {\n return isTranscriptionModel(modelId) || isSpeechModel(modelId);\n}\n"],"mappings":";;AA0BA,SAAS,mBAAmB,OAAuB;CACjD,OAAO,MAAM,SAAS,GAAG,IAAI,MAAM,MAAM,GAAG,EAAE,IAAI;AACpD;AAEA,SAAgB,wBAAwB,SAAyB;CAC/D,OAAO,mBAAmB,QAAQ,KAAK,CAAC;AAC1C;;;;;;;;AASA,SAAgB,0BAA0B,SAAyB;CACjE,IAAI,aAAa,wBAAwB,OAAO;CAChD,MAAM,WAAW;EAAC;EAAkB;EAAW;CAAK;CACpD,IAAI,WAAW;CAEf,OAAO,UAAU;EACf,WAAW;EACX,KAAK,MAAM,UAAU,UACnB,IAAI,WAAW,SAAS,MAAM,GAAG;GAC/B,aAAa,WAAW,MAAM,GAAG,CAAC,OAAO,MAAM;GAC/C,WAAW;GACX;EACF;CAEJ;CAEA,OAAO,cAAc,wBAAwB,OAAO;AACtD;AAEA,SAAgB,2BACd,SACA,cACA,UACQ;CACR,MAAM,oBAAoB,gBAAgB;CAC1C,IAAI,CAAC,mBAAmB,KAAK,GAC3B,MAAM,IAAI,QACR,GAAG,SAAS,4CACZ,2BACA,QACF;CAGF,OAAO,eACH,wBAAwB,YAAY,IACpC,0BAA0B,iBAAiB;AACjD;AAEA,SAAgB,iBAAiB,MAAc,UAA2B;CAOxE,OANc,CAAC,UAAU,IAAI,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GACvC,CAAA,CACV,YAAY,CAAC,CACb,QAAQ,eAAe,GAAG,CAAC,CAC3B,QAAQ,YAAY,EAEhB,KAAQ;AACjB;AAEA,SAAS,aAAa,OAA4B;CAChD,OAAO,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,IAC5D,QACD,CAAC;AACP;AAEA,SAAS,YAAY,OAAoC;CACvD,OAAO,OAAO,UAAU,YAAY,MAAM,SAAS,IAAI,QAAQ,KAAA;AACjE;AAEA,SAAS,uBAAuB,UAAkB,UAA0B;CAC1E,MAAM,QAAQ,GAAG,SAAS,GAAG;CAE7B,IAAI,OAAO,SAAS,YAClB,OAAO,KAAK,KAAK;CAGnB,OAAO,OAAO,KAAK,OAAO,MAAM,CAAC,CAAC,SAAS,QAAQ;AACrD;AAEA,SAAS,sBAA4C,OAAa;CAChE,KAAK,MAAM,OAAO,OAAO,KAAK,KAAK,GACjC,IAAI,MAAM,SAAS,KAAA,GACjB,OAAO,MAAM;CAIjB,OAAO;AACT;AAEA,SAAS,eACP,SAKA,cAAc,aACuB;CACrC,MAAM,WAAW,EACf,GAAG,QAAQ,SACb;CAEA,IAAI,QAAQ,UACV,SAAS,eAAe,QAAQ;CAGlC,IAAI,QAAQ,aACV,SAAS,cAAc,QAAQ;CAGjC,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,SAAS,IAAI,WAAW,KAAA;AACvD;AAEA,IAAM,WAAW;;;;;AAMjB,IAAM,oBAAoB;;AAG1B,IAAM,gCAAgB,IAAI,IAAI;CAC5B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAED,SAAS,aAAa,OAAuB;CAC3C,OAAO,MAAM,QAAQ,uBAAuB,MAAM;AACpD;;;;;;AAOA,SAAgB,iBAAiB,MAAc,UAAoB,CAAC,GAAW;CAC7E,IAAI,SAAS;CACb,KAAK,MAAM,UAAU,SACnB,IAAI,QACF,SAAS,OAAO,QAAQ,IAAI,OAAO,aAAa,MAAM,GAAG,GAAG,GAAG,QAAQ;CAG3E,OAAO,OAAO,QAAQ,mBAAmB,QAAQ;AACnD;;;;;AAMA,SAAgB,cAAc,OAAgB,UAAoB,CAAC,GAAY;CAC7E,IAAI,OAAO,UAAU,UACnB,OAAO,iBAAiB,OAAO,OAAO;CAGxC,IAAI,MAAM,QAAQ,KAAK,GACrB,OAAO,MAAM,KAAK,SAAS,cAAc,MAAM,OAAO,CAAC;CAGzD,IAAI,SAAS,OAAO,UAAU,UAAU;EACtC,MAAM,SAAqB,CAAC;EAC5B,KAAK,MAAM,CAAC,OAAO,SAAS,OAAO,QAAQ,KAAmB,GAC5D,OAAO,SACL,cAAc,IAAI,MAAM,YAAY,CAAC,KAAK,OAAO,SAAS,WACtD,WACA,cAAc,MAAM,OAAO;EAEnC,OAAO;CACT;CAEA,OAAO;AACT;;;;;;AAOA,SAAS,cAAc,GAAG,YAA2C;CACnE,KAAK,MAAM,aAAa,YAAY;EAClC,MAAM,QAAQ,YAAY,SAAS;EACnC,IAAI,SAAS,iBAAiB,KAAK,MAAM,OAAO,OAAO;CACzD;AAGF;;;;;;;;AASA,SAAS,aACP,QACG;CACH,KAAK,MAAM,SAAS,CAAC,QAAQ,WAAW,GAAY;EAClD,MAAM,QAAQ,OAAO;EACrB,IAAI,OAAO,UAAU,UACnB,OAAO,SAAS,iBAAiB,KAAK;CAE1C;CACA,OAAO;AACT;AAEA,SAAS,eAAe,IAAY,UAAkB,MAAsB;CAC1E,IAAI,OAAO,OAAO,YAAY,GAAG,KAAK,CAAC,CAAC,WAAW,GACjD,MAAM,IAAI,QACR,GAAG,SAAS,GAAG,KAAK,kBACpB,0BACA,QACF;CAGF,OAAO;AACT;AAEA,SAAS,iBAAiB,UAAkB,OAAwB;CAClE,OAAO,IAAI,QACT,GAAG,SAAS,yCAAyC,SACrD,2BACA,QACF;AACF;AAEA,SAAS,gBACP,OACA,UAAmE,CAAC,GAC3D;CACT,IAAI,EAAE,iBAAiB,UAAU,OAAO;CACxC,IAAI,MAAM,SAAS,kBAAkB,OAAO;CAE5C,MAAM,UAAU,QAAQ;CACxB,OAAO,QACL,WACE,QAAQ,iBAAiB,MACtB,WAAW,MAAM,SAAS,cAAc,QAC3C,KACA,QAAQ,KAAK,MAAM,OAAO,CAC9B;AACF;AAEA,SAAS,gBAAgB,MAAe,UAA0B;CAChE,MAAM,SAAS,aAAa,IAAI;CAGhC,OACE,YAHY,aAAa,OAAO,KAGpB,CAAA,CAAM,OAAO,KACzB,YAAY,OAAO,OAAO,KAC1B,YAAY,OAAO,MAAM,KACzB;AAEJ;AAEA,IAAM,wBAAN,MAA4B;CAC1B;CACA;CACA;CACA;CACA;CACA;CACA;CAEA,YAAY,SAAuC;EACjD,KAAK,WAAW,QAAQ;EACxB,KAAK,UAAU,wBAAwB,QAAQ,OAAO;EACtD,KAAK,SAAS,QAAQ;EACtB,KAAK,WAAW,QAAQ;EACxB,KAAK,WAAW,QAAQ;EACxB,KAAK,UAAU,QAAQ;EACvB,KAAK,UAAU,QAAQ;CACzB;;;;;CAMA,MAAM,QACJ,QACA,MACA,MACA,OACA,UAAoB,CAAC,GACT;EACZ,MAAM,MAAM,IAAI,IACd,GAAG,KAAK,UAAU,KAAK,WAAW,GAAG,IAAI,OAAO,IAAI,QACtD;EAEA,IAAI;QACG,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,GAC7C,IAAI,UAAU,KAAA,GACZ,IAAI,aAAa,IAAI,KAAK,KAAK;EAAA;EAKrC,MAAM,aACJ,OAAO,oBAAoB,cACvB,KAAA,IACA,IAAI,gBAAgB;EAC1B,MAAM,gBACJ,cAAc,KAAK,UACf,iBAAiB,WAAW,MAAM,GAAG,KAAK,OAAO,IACjD,KAAA;EAEN,IAAI;GACF,MAAM,WAAW,MAAM,MAAM,KAAK;IAChC;IACA,SAAS;KACP,QAAQ;KACR,GAAI,SAAS,KAAA,IAAY,EAAE,gBAAgB,mBAAmB,IAAI,CAAC;KACnE,GAAG,KAAK;KACR,GAAG,KAAK,kBAAkB;IAC5B;IACA,MAAM,SAAS,KAAA,IAAY,KAAA,IAAY,KAAK,UAAU,IAAI;IAC1D,QAAQ,YAAY;GACtB,CAAC;GAED,MAAM,OAAO,MAAM,SAAS,KAAK;GACjC,MAAM,SAAS,OAAO,KAAK,UAAU,IAAI,IAAI,KAAA;GAE7C,IAAI,CAAC,SAAS,IACZ,MAAM,KAAK,iBAAiB,UAAU,QAAQ,OAAO;GAGvD,OAAO;EACT,SAAS,OAAO;GACd,IAAI,iBAAiB,SACnB,MAAM;GAGR,MAAM,IAAI,QACR,iBACE,GAAG,KAAK,SAAS,yBACf,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,KAEvD,OACF,GACA,wBACA,KAAK,QACP;EACF,UAAU;GACR,IAAI,eACF,aAAa,aAAa;EAE9B;CACF;CAEA,oBAAoD;EAClD,IAAI,KAAK,YAAY,KAAK,UACxB,OAAO,EACL,eAAe,SAAS,uBACtB,KAAK,UACL,KAAK,QACP,IACF;EAGF,OAAO,KAAK,SAAS,EAAE,eAAe,UAAU,KAAK,SAAS,IAAI,CAAC;CACrE;CAEA,UAAkB,MAAuB;EACvC,IAAI;GACF,OAAO,KAAK,MAAM,IAAI;EACxB,SAAS,QAAQ;GACf,OAAO,EAAE,SAAS,KAAK;EACzB;CACF;CAEA,iBACE,UACA,MACA,SACS;EACT,MAAM,UAAU,iBACd,gBACE,MACA,GAAG,KAAK,SAAS,6BAA6B,SAAS,QACzD,GACA,OACF;EAEA,IAAI,SAAS,WAAW,OAAO,SAAS,WAAW,KACjD,OAAO,IAAI,QAAQ,SAAS,cAAc,KAAK,QAAQ;EAGzD,IAAI,SAAS,WAAW,KACtB,OAAO,IAAI,QAAQ,SAAS,cAAc,KAAK,UAAU,KAAA,GAAW,IAAI;EAG1E,OAAO,IAAI,QACT,SACA,cAAc,SAAS,UACvB,KAAK,UACL,KAAA,GACA,SAAS,UAAU,GACrB;CACF;AACF;AAEA,SAAS,iBAAiB,QAAgD;CACxE,IAAI,CAAC,QAAQ,OAAO,KAAA;CAEpB,OAAO,sBAAsB;EAC3B,WAAW,OAAO;EAClB,gBAAgB,OAAO;EACvB,kBAAkB,OAAO;CAC3B,CAAC;AACH;AAEA,SAAS,oBACP,WACwB;CACxB,IAAI,CAAC,WAAW,OAAO,KAAA;CAEvB,OAAO,sBAAsB;EAC3B,iBAAiB,UAAU,iBAAiB,UAAU;EACtD,sBAAsB,UAAU;EAChC,mBAAmB,UAAU,mBAAmB,UAAU;EAC1D,wBAAwB,UAAU;CACpC,CAAC;AACH;AAEA,SAAS,0BACP,SAC0B;CAC1B,IAAI,CAAC,SAAS,OAAO,KAAA;CAErB,OAAO,QAAQ,KAAK,WAClB,sBAAsB;EACpB,UAAU,OAAO;EACjB,QAAQ,OAAO;EACf,gBAAgB,OAAO;EACvB,SAAS,OAAO;CAClB,CAAC,CACH;AACF;AAEA,SAAS,iBACP,QACoD;CACpD,OAAO,sBAAsB;EAC3B,YAAY,QAAQ;EACpB,iBAAiB,QAAQ;CAC3B,CAAC;AACH;AAEA,SAAS,oBACP,WAC6C;CAC7C,OAAO,sBAAsB;EAC3B,WAAW,WAAW,YAAY,WAAW;EAC7C,WAAW,WAAW,YAAY,WAAW;CAC/C,CAAC;AACH;AAEA,SAAS,eAAe,OAAgB,GAAG,QAAgC;CACzE,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,MAAM,IAAI,YAAY;CAEvD,MAAM,SAAS,aAAa,KAAK;CACjC,KAAK,MAAM,SAAS,QAClB,IAAI,MAAM,QAAQ,OAAO,MAAM,GAC7B,OAAQ,OAAO,MAAM,CAAe,IAAI,YAAY;CAIxD,OAAO,CAAC;AACV;AAEA,SAAS,iBACP,MACA,KAC4B;CAC5B,MAAM,KAAK,YAAY,KAAK,EAAE,KAAK,YAAY,KAAK,OAAO;CAC3D,IAAI,CAAC,IAAI,OAAO,KAAA;CAEhB,OAAO,aAAa;EAClB;EACA,MAAM,YAAY,KAAK,IAAI,KAAK,YAAY,KAAK,UAAU,KAAK;EAChE,UAAU,YAAY,KAAK,WAAW;EACtC,UAAU,YAAY,KAAK,SAAS;EACpC,UAAU;EACV,KAAK,cAAc,GAAG;CACxB,CAAC;AACH;AAEA,SAAS,iBACP,MACA,KAC4B;CAC5B,MAAM,KAAK,YAAY,KAAK,OAAO,KAAK,YAAY,KAAK,EAAE;CAC3D,IAAI,CAAC,IAAI,OAAO,KAAA;CAEhB,OAAO,aAAa;EAClB;EACA,MAAM,YAAY,KAAK,UAAU,KAAK,YAAY,KAAK,IAAI,KAAK;EAChE,UAAU,YAAY,aAAa,KAAK,QAAQ,CAAC,CAAC,SAAS;EAC3D,UAAU,YAAY,KAAK,SAAS;EACpC,UAAU;EACV,KAAK,cAAc,GAAG;CACxB,CAAC;AACH;;;;;AAMA,IAAM,oBAAoB;CACxB,iBAAiB,CAAC,KAAK,GAAG;CAC1B,gBACE;AACJ;AAEA,IAAa,eAAb,MAAsD;CACpD;CAEA,YAAY,SAAuC;EACjD,KAAK,YAAY,IAAI,sBAAsB,OAAO;CACpD;CAEA,MAAM,cACJ,SACyB;EACzB,MAAM,OAAO,sBAAsB;GACjC,MAAM,QAAQ;GACd,aAAa,QAAQ;GACrB,QAAQ,iBAAiB,QAAQ,MAAM;GACvC,GAAG,QAAQ;EACb,CAAC;EAED,MAAM,WAAW,MAAM,KAAK,UAAU,QACpC,QACA,yBACA,IACF;EACA,MAAM,OAAO,aAAa,SAAS,QAAQ,QAAQ;EACnD,MAAM,KAAK,YAAY,KAAK,EAAE,KAAK,YAAY,KAAK,OAAO;EAE3D,IAAI,CAAC,IACH,MAAM,IAAI,QACR,uCACA,0BACA,SACF;EAGF,OAAO;GACL;GACA,MACE,YAAY,KAAK,IAAI,KAAK,YAAY,KAAK,UAAU,KAAK,QAAQ;GACpE,UAAU,YAAY,KAAK,WAAW,KAAK,QAAQ;GACnD,UAAU,YAAY,KAAK,SAAS;GACpC,UAAU;GACV,KAAK;EACP;CACF;CAEA,MAAM,iBACJ,SACuB;EACvB,MAAM,OAAO,sBAAsB;GACjC,MAAM,QAAQ;GACd,aAAa,QAAQ;GACrB,kBAAkB,0BAA0B,QAAQ,eAAe;GACnE,SAAS,QAAQ;GACjB,aAAa,QAAQ,YAAY,KAAA,IAAY,QAAQ;GACrD,QAAQ,iBAAiB,QAAQ,MAAM;GACvC,YAAY,oBAAoB,QAAQ,SAAS;GACjD,SAAS,QAAQ;GACjB,WAAW,QAAQ,YAAY;GAC/B,GAAG,QAAQ;EACb,CAAC;EAED,MAAM,WAAW,MAAM,KAAK,UAAU,QACpC,QACA,gCACA,IACF;EACA,MAAM,aAAa,aACjB,SAAS,eAAe,SAAS,cAAc,QACjD;EAEA,OAAO;GACL,IAAI,YAAY,WAAW,EAAE,KAAK,YAAY,WAAW,MAAM;GAC/D,MAAM,YAAY,WAAW,IAAI,KAAK,QAAQ;GAC9C,KAAK,YAAY,WAAW,KAAK,KAAK,YAAY,WAAW,GAAG;GAChE,WACE,YAAY,WAAW,QAAQ,KAAK,YAAY,WAAW,UAAU;GACvE,WAAW,YAAY,WAAW,OAAO,KAAK,QAAQ;GACtD,UAAU,YAAY,WAAW,WAAW,KAAK,QAAQ;GACzD,WACE,YAAY,WAAW,OAAO,KAAK,YAAY,WAAW,UAAU;GACtE,UAAU;GACV,KAAK;EACP;CACF;CAEA,MAAM,iBACJ,IACA,SACuB;EACvB,eAAe,IAAI,WAAW,aAAa;EAC3C,IAAI,QAAQ,WAAW,KAAA,GACrB,MAAM,iBAAiB,WAAW,8BAA8B;EAElE,IAAI,QAAQ,aAAa,KAAA,GACvB,MAAM,iBAAiB,WAAW,UAAU;EAG9C,MAAM,OAAO,sBAAsB;GACjC,aAAa,QAAQ;GACrB,kBAAkB,0BAA0B,QAAQ,eAAe;GACnE,QAAQ,iBAAiB,QAAQ,MAAM;GACvC,YAAY,oBAAoB,QAAQ,SAAS;GACjD,WAAW,QAAQ;EACrB,CAAC;EAED,MAAM,WAAW,MAAM,KAAK,UAAU,QACpC,OACA,gCAAgC,mBAAmB,EAAE,KACrD,MACA,KAAA,GACA,CAAC,EAAE,CACL;EACA,MAAM,aAAa,aACjB,UAAU,eAAe,UAAU,cAAc,QACnD;EAEA,OAAO,aAAa;GAClB,IAAI,cAAc,WAAW,IAAI,EAAE;GACnC,MAAM,YAAY,WAAW,IAAI;GACjC,WAAW,YAAY,WAAW,OAAO;GACzC,UAAU,YAAY,WAAW,WAAW;GAC5C,WACE,YAAY,WAAW,OAAO,KAAK,YAAY,WAAW,UAAU;GACtE,UAAU;GACV,KAAK,cAAc,UAAU,CAAC,EAAE,CAAC;EACnC,CAAC;CACH;CAEA,MAAM,iBAAiB,IAA8B;EACnD,eAAe,IAAI,WAAW,aAAa;EAE3C,IAAI;GACF,MAAM,KAAK,UAAU,QACnB,UACA,gCAAgC,mBAAmB,EAAE,KACrD,KAAA,GACA,KAAA,GACA,CAAC,EAAE,CACL;GACA,OAAO;EACT,SAAS,OAAO;GACd,IAAI,gBAAgB,KAAK,GAAG,OAAO;GACnC,MAAM;EACR;CACF;CAEA,MAAM,WAAW,IAAiD;EAChE,eAAe,IAAI,WAAW,SAAS;EAEvC,IAAI;GACF,MAAM,WAAW,MAAM,KAAK,UAAU,QACpC,OACA,yBAAyB,mBAAmB,EAAE,GAChD;GACA,OAAO,iBACL,aAAa,UAAU,QAAQ,QAAQ,GACvC,QACF;EACF,SAAS,OAAO;GACd,IAAI,gBAAgB,KAAK,GAAG,OAAO,KAAA;GACnC,MAAM;EACR;CACF;CAEA,MAAM,aACJ,SAA+B,CAAC,GACL;EAQ3B,OAAO,eAAe,MAPC,KAAK,UAAU,QACpC,OACA,yBACA,KAAA,GACA,EAAE,aAAa,OAAO,SAAS,CACjC,GAEgC,SAAS,MAAM,CAAC,CAC7C,KAAK,SAAS,iBAAiB,MAAM,IAAI,CAAC,CAAC,CAC3C,QACE,YACC,YAAY,KAAA,MACX,OAAO,aAAa,KAAA,KACnB,QAAQ,aAAa,OAAO,SAClC;CACJ;AACF;AAEA,IAAa,eAAb,MAAsD;CACpD;CAEA,YAAY,SAAuC;EACjD,KAAK,YAAY,IAAI,sBAAsB,OAAO;CACpD;CAEA,MAAM,cACJ,SACyB;EACzB,MAAM,YACJ,QAAQ,MAAM,iBAAiB,QAAQ,MAAM,QAAQ,QAAQ;EAC/D,MAAM,OAAO,sBAAsB;GACjC,SAAS;GACT,YAAY,QAAQ;GACpB,QAAQ,QAAQ;GAChB,GAAG,iBAAiB,QAAQ,MAAM;GAClC,GAAG,oBAAoB,QAAQ,SAAS;GACxC,UAAU,eAAe,OAAO;GAChC,SAAS,QAAQ;GACjB,GAAG,QAAQ;EACb,CAAC;EAED,MAAM,WAAW,MAAM,KAAK,UAAU,QACpC,QACA,aACA,IACF;EACA,MAAM,OAAO,aAAa,SAAS,QAAQ,QAAQ;EAEnD,OAAO;GACL,IAAI,YAAY,KAAK,OAAO,KAAK;GACjC,MACE,YAAY,KAAK,UAAU,KAAK,YAAY,KAAK,IAAI,KAAK,QAAQ;GACpE,UAAU,QAAQ;GAClB,UAAU;GACV,KAAK;EACP;CACF;CAEA,MAAM,iBACJ,SACuB;EACvB,MAAM,OAAO,sBAAsB;GACjC,WAAW,QAAQ;GACnB,SAAS,QAAQ;GACjB,SAAS,QAAQ;GACjB,QAAQ,QAAQ;GAChB,UAAU,QAAQ;GAClB,GAAG,iBAAiB,QAAQ,MAAM;GAClC,GAAG,oBAAoB,QAAQ,SAAS;GACxC,UAAU,eAAe,OAAO;GAChC,SAAS,QAAQ;GACjB,QAAQ,QAAQ;GAChB,aAAa,QAAQ;GACrB,SAAS,QAAQ,aAAa,KAAA,IAAY,KAAA,IAAY,CAAC,QAAQ;GAC/D,GAAG,QAAQ;EACb,CAAC;EAED,MAAM,WAAW,MAAM,KAAK,UAAU,QACpC,QACA,iBACA,IACF;EAEA,MAAM,WAAW,YAAY,SAAS,GAAG;EACzC,MAAM,SACJ,YAAY,SAAS,QAAQ,KAAK,YAAY,SAAS,MAAM;EAE/D,OAAO;GAEL,IAAI,UAAU,WAAW,WAAW,cAAc,MAAM,IAAI,KAAA;GAC5D,MACE,YAAY,SAAS,SAAS,KAC9B,YAAY,SAAS,QAAQ,KAC7B,QAAQ;GACV,KAAK;GACL,WAAW,YAAY,SAAS,QAAQ;GACxC,WAAW,YAAY,SAAS,OAAO,KAAK,QAAQ;GACpD,UAAU,QAAQ;GAClB,WAAW,YAAY,SAAS,OAAO;GACvC,UAAU;GACV,KAAK;EACP;CACF;CAEA,MAAM,iBACJ,IACA,SACuB;EACvB,eAAe,IAAI,WAAW,aAAa;EAC3C,IAAI,QAAQ,oBAAoB,KAAA,GAC9B,MAAM,iBAAiB,WAAW,iBAAiB;EAGrD,IAAI;EACJ,IAAI,QAAQ,aAAa,KAAA,KAAa,QAAQ,gBAAgB,KAAA,GAS5D,WAAW;GACT,GAAG,aAAa,cAAa,MARZ,KAAK,UAAU,QAChC,OACA,aACA,KAAA,GACA,EAAE,KAAK,GAAG,GACV,CAAC,EAAE,CACL,EAAA,EAEqC,IAAI,CAAC,CAAC,QAAQ;GACjD,GAAG,QAAQ;GACX,GAAI,QAAQ,gBAAgB,KAAA,IACxB,EAAE,aAAa,QAAQ,YAAY,IACnC,CAAC;EACP;EAGF,MAAM,OAAO,sBAAsB;GACjC,KAAK;GACL,QAAQ,QAAQ;GAChB,GAAG,iBAAiB,QAAQ,MAAM;GAClC,GAAG,oBAAoB,QAAQ,SAAS;GACxC;GACA,SAAS,QAAQ,aAAa,KAAA,IAAY,KAAA,IAAY,CAAC,QAAQ;EACjE,CAAC;EAED,MAAM,WAAW,MAAM,KAAK,UAAU,QACpC,QACA,eACA,MACA,KAAA,GACA,CAAC,EAAE,CACL;EACA,MAAM,UAAU,aAAa,QAAQ;EAErC,OAAO,aAAa;GAClB,IAAI,cAAc,IAAI,QAAQ,UAAU,QAAQ,KAAK;GACrD,MAAM,YAAY,QAAQ,SAAS;GACnC,WAAW,YAAY,QAAQ,QAAQ;GACvC,WAAW,YAAY,QAAQ,OAAO;GACtC,UAAU,YACR,aAAa,QAAQ,YAAY,QAAQ,CAAC,CAAC,SAC7C;GACA,WAAW,YAAY,QAAQ,OAAO;GACtC,UAAU;GACV,KAAK,cAAc,UAAU,CAAC,EAAE,CAAC;EACnC,CAAC;CACH;CAEA,MAAM,iBAAiB,IAA8B;EACnD,eAAe,IAAI,WAAW,aAAa;EAE3C,IAAI;GAQF,MAAM,UAAU,aAAa,MAPN,KAAK,UAAU,QACpC,QACA,eACA,EAAE,MAAM,CAAC,EAAE,EAAE,GACb,KAAA,GACA,CAAC,EAAE,CACL,CACqC,CAAC,CAAC;GACvC,OAAO,EAAE,MAAM,QAAQ,OAAO,KAAK,QAAQ,WAAW;EACxD,SAAS,OAAO;GACd,IAAI,gBAAgB,OAAO,iBAAiB,GAAG,OAAO;GACtD,MAAM;EACR;CACF;CAEA,MAAM,WAAW,IAAiD;EAChE,eAAe,IAAI,WAAW,SAAS;EAEvC,IAAI;GACF,MAAM,WAAW,MAAM,KAAK,UAAU,QACpC,OACA,cACA,KAAA,GACA,EAAE,SAAS,GAAG,CAChB;GACA,MAAM,SAAS,aAAa,QAAQ;GACpC,MAAM,OAAO,aAAa,OAAO,aAAa,MAAM;GACpD,OAAO,iBACL;IACE,GAAG;IACH,SAAS,YAAY,KAAK,OAAO,KAAK,YAAY,OAAO,OAAO;GAClE,GACA,QACF;EACF,SAAS,OAAO;GACd,IAAI,gBAAgB,OAAO,iBAAiB,GAAG,OAAO,KAAA;GACtD,MAAM;EACR;CACF;CAEA,MAAM,aACJ,SAA+B,CAAC,GACL;EAG3B,OAAO,eAAe,MAFC,KAAK,UAAU,QAAiB,OAAO,YAAY,GAE1C,SAAS,MAAM,CAAC,CAC7C,KAAK,SAAS,iBAAiB,MAAM,IAAI,CAAC,CAAC,CAC3C,QACE,YACC,YAAY,KAAA,MACX,OAAO,aAAa,KAAA,KACnB,QAAQ,aAAa,OAAO,SAClC;CACJ;AACF;;;;;;;;;;;;AC/5BA,SAAS,cAAc,SAA2B;CAChD,OAAO,QACJ,YAAY,CAAC,CACb,MAAM,YAAY,CAAC,CACnB,OAAO,OAAO;AACnB;AAEA,SAAS,iBAAiB,QAAkB,UAA6B;CACvE,KAAK,IAAI,IAAI,GAAG,IAAI,SAAS,UAAU,OAAO,QAAQ,KACpD,IAAI,SAAS,OAAO,OAAO,WAAW,OAAO,IAAI,YAAY,KAAK,GAChE,OAAO;CAGX,OAAO;AACT;AAEA,IAAM,uCAAuB,IAAI,IAAI;CACnC;CACA;CACA;CACA;AACF,CAAC;AAED,IAAM,gCAAgB,IAAI,IAAI,CAAC,OAAO,QAAQ,CAAC;;;;;;;AAQ/C,SAAgB,qBAAqB,SAA0B;CAC7D,MAAM,SAAS,cAAc,OAAO;CACpC,OACE,OAAO,MAAM,UAAU,qBAAqB,IAAI,KAAK,CAAC,KACtD,iBAAiB,QAAQ;EAAC;EAAU;EAAM;CAAM,CAAC,KAGhD,OAAO,SAAS,SAAS,KAAK,OAAO,SAAS,UAAU;AAE7D;;;;;;AAOA,SAAgB,cAAc,SAA0B;CACtD,OACE,CAAC,qBAAqB,OAAO,KAC7B,cAAc,OAAO,CAAC,CAAC,MAAM,UAAU,cAAc,IAAI,KAAK,CAAC;AAEnE;AAEA,SAAgB,aAAa,SAA0B;CACrD,OAAO,qBAAqB,OAAO,KAAK,cAAc,OAAO;AAC/D"}
@@ -1,6 +1,6 @@
1
1
  import { l as normalizeChatOptions, u as normalizeImageGenerationOptions } from "./usage-B40bJZ49.js";
2
- import { _ as OpenAIProvider } from "./src-EMvAiwjJ.js";
3
- import { a as LiteLLMAdmin, n as isSpeechModel, r as isTranscriptionModel, s as resolveGatewayAdminBaseUrl, t as isAudioModel } from "./gateway-audio-models-Cs7vu956.js";
2
+ import { _ as OpenAIProvider } from "./src-BEkzdHzp.js";
3
+ import { a as LiteLLMAdmin, n as isSpeechModel, r as isTranscriptionModel, s as resolveGatewayAdminBaseUrl, t as isAudioModel } from "./gateway-audio-models-jRog9_6Z.js";
4
4
  import { ValidationError } from "@happyvertical/utils";
5
5
  //#region src/shared/providers/litellm.ts
6
6
  /** Chat Completions has effort, not a generic reasoning budget/thoughts envelope. */
@@ -194,4 +194,4 @@ var LiteLLMProvider = class extends OpenAIProvider {
194
194
  //#endregion
195
195
  export { LiteLLMProvider };
196
196
 
197
- //# sourceMappingURL=litellm-Cc6xC6sj.js.map
197
+ //# sourceMappingURL=litellm-CrDQhsbb.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"litellm-Cc6xC6sj.js","names":[],"sources":["../../src/shared/providers/litellm.ts"],"sourcesContent":["import { ValidationError } from '@happyvertical/utils';\n\nimport {\n normalizeChatOptions,\n normalizeImageGenerationOptions,\n} from '../safety';\nimport type {\n AICapabilities,\n AIMessage,\n AIModel,\n AIResponse,\n ChatOptions,\n EmbeddingOptions,\n EmbeddingResponse,\n ImageDescriptionOptions,\n ImageEmbeddingOptions,\n ImageGenerationOptions,\n ImageGenerationResponse,\n LiteLLMOptions,\n} from '../types';\nimport { LiteLLMAdmin, resolveGatewayAdminBaseUrl } from './gateway-admin';\nimport {\n isAudioModel,\n isSpeechModel,\n isTranscriptionModel,\n} from './gateway-audio-models';\nimport { type OpenAICompatibleProfile, OpenAIProvider } from './openai';\n\n/** Chat Completions has effort, not a generic reasoning budget/thoughts envelope. */\nfunction assertChatReasoningControls(\n options: Pick<ChatOptions, 'reasoning' | 'includeThoughts'>,\n): void {\n if (\n options.reasoning?.maxTokens !== undefined ||\n options.reasoning?.includeThoughts !== undefined ||\n options.includeThoughts !== undefined\n ) {\n throw new ValidationError(\n 'LiteLLM Chat Completions does not support generic reasoning.maxTokens or includeThoughts controls; use reasoning.effort and an output token limit',\n { provider: 'litellm' },\n );\n }\n}\n\nconst LITELLM_CAPABILITIES: AICapabilities = {\n chat: true,\n completion: true,\n embeddings: true,\n streaming: true,\n functions: true,\n vision: true,\n fineTuning: false,\n imageEmbeddings: true,\n imageGeneration: true,\n videoGeneration: false,\n tts: false,\n voiceCloning: false,\n voiceDesign: false,\n maxContextLength: 128000,\n supportedOperations: [\n 'chat',\n 'completion',\n 'embedding',\n 'streaming',\n 'functions',\n 'vision',\n 'image_embedding',\n 'image_generation',\n ],\n};\n\nfunction isEmbeddingModel(modelId: string): boolean {\n return /embed/i.test(modelId);\n}\n\nfunction isImageEmbeddingModel(modelId: string): boolean {\n return /titan-embed-image|multimodalembedding|image[-_]?embed|clip/i.test(\n modelId,\n );\n}\n\nfunction isImageGenerationModel(modelId: string): boolean {\n return /gpt-image|dall-e|imagen|stable[-_ ]diffusion|sdxl|flux|titan-image|image-generator/i.test(\n modelId,\n );\n}\n\nfunction isFilteredLiteLLMModel(modelId: string): boolean {\n return /moderation|rerank/i.test(modelId);\n}\n\nfunction inferLiteLLMContextLength(modelId: string): number {\n if (isEmbeddingModel(modelId)) return 8192;\n if (/gemini-1\\.5|gemini-2\\.0|gemini-2\\.5|gemini-3/i.test(modelId)) {\n return 1000000;\n }\n if (/claude/i.test(modelId)) return 200000;\n if (/gpt-4o|gpt-4\\.1|o1|o3|o4|llama|mistral|qwen|deepseek/i.test(modelId)) {\n return 128000;\n }\n return 32768;\n}\n\nfunction inferLiteLLMFunctions(modelId: string): boolean {\n if (isEmbeddingModel(modelId) || isAudioModel(modelId)) return false;\n return /gpt|claude|gemini|command|llama|mistral|qwen|deepseek|o1|o3|o4/i.test(\n modelId,\n );\n}\n\nfunction inferLiteLLMVision(modelId: string): boolean {\n if (isAudioModel(modelId)) return false;\n return /gpt-4o|gpt-4\\.1|vision|claude-3|gemini|pixtral|llava|qwen.*vl|vl-/i.test(\n modelId,\n );\n}\n\nfunction inferLiteLLMCapabilities(modelId: string): string[] {\n if (isTranscriptionModel(modelId)) {\n return ['transcription'];\n }\n\n if (isSpeechModel(modelId)) {\n return ['speech'];\n }\n\n if (isImageGenerationModel(modelId)) {\n return ['image_generation'];\n }\n\n if (isImageEmbeddingModel(modelId)) {\n return ['embeddings', 'image_embedding'];\n }\n\n if (isEmbeddingModel(modelId)) {\n return ['embeddings'];\n }\n\n const capabilities = ['text', 'chat'];\n\n if (inferLiteLLMFunctions(modelId)) {\n capabilities.push('functions');\n }\n\n if (inferLiteLLMVision(modelId)) {\n capabilities.push('vision');\n }\n\n return capabilities;\n}\n\nfunction normalizeBaseUrl(baseUrl: string): string {\n return baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;\n}\n\nfunction ensureLiteLLMOptions(options: LiteLLMOptions): LiteLLMOptions {\n if (!options.baseUrl?.trim()) {\n throw new ValidationError('LiteLLM baseUrl is required', {\n provider: 'litellm',\n hint: 'Pass baseUrl like https://llm.happyvertical.com/v1 or set HAVE_AI_BASE_URL / LITELLM_BASE_URL',\n });\n }\n\n return {\n ...options,\n baseUrl: normalizeBaseUrl(options.baseUrl.trim()),\n };\n}\n\nconst LITELLM_PROFILE: OpenAICompatibleProfile = {\n providerLabel: 'LiteLLM',\n providerName: 'litellm',\n defaultModel: 'gpt-4',\n capabilities: LITELLM_CAPABILITIES,\n describeModel: (modelId) => `LiteLLM model: ${modelId}`,\n getContextLength: inferLiteLLMContextLength,\n getModelCapabilities: inferLiteLLMCapabilities,\n shouldIncludeModel: (modelId) => !isFilteredLiteLLMModel(modelId),\n supportsFunctions: inferLiteLLMFunctions,\n supportsVision: inferLiteLLMVision,\n};\n\ntype LiteLLMResolvedCapability =\n | 'chat'\n | 'vision'\n | 'embeddings'\n | 'image_generation';\n\nfunction supportsCapability(\n model: AIModel,\n capability: LiteLLMResolvedCapability,\n): boolean {\n if (capability === 'vision') {\n return (\n model.capabilities.includes('vision') || model.supportsVision === true\n );\n }\n\n if (capability === 'embeddings') {\n return model.capabilities.includes('embeddings');\n }\n\n if (capability === 'image_generation') {\n return model.capabilities.includes('image_generation');\n }\n\n return model.capabilities.includes('chat');\n}\n\n/**\n * LiteLLM provider implementation.\n *\n * LiteLLM exposes an OpenAI-compatible API surface, so this provider reuses the\n * OpenAI transport while customizing provider identity, model discovery, and\n * capability heuristics for gateway-backed deployments.\n */\nexport class LiteLLMProvider extends OpenAIProvider {\n readonly admin: LiteLLMAdmin;\n private readonly configuredDefaultModel?: string;\n private readonly resolvedModelCache = new Map<\n LiteLLMResolvedCapability,\n Promise<string>\n >();\n\n constructor(options: LiteLLMOptions) {\n const normalized = ensureLiteLLMOptions(options);\n super(normalized, LITELLM_PROFILE);\n this.admin = new LiteLLMAdmin({\n provider: 'litellm',\n baseUrl: resolveGatewayAdminBaseUrl(\n normalized.baseUrl,\n normalized.adminUrl || normalized.adminBaseUrl,\n 'litellm',\n ),\n apiKey: normalized.adminApiKey || normalized.apiKey,\n headers: normalized.adminHeaders,\n timeout: normalized.timeout,\n });\n this.configuredDefaultModel = normalized.defaultModel;\n }\n\n private async resolveModel(\n capability: LiteLLMResolvedCapability,\n explicitModel?: string,\n ): Promise<string> {\n if (explicitModel) {\n return explicitModel;\n }\n\n if (\n this.configuredDefaultModel &&\n (capability === 'chat' || capability === 'vision')\n ) {\n return this.configuredDefaultModel;\n }\n\n const cached = this.resolvedModelCache.get(capability);\n if (cached) {\n return cached;\n }\n\n const pending = this.selectModel(capability).catch((error) => {\n this.resolvedModelCache.delete(capability);\n throw error;\n });\n this.resolvedModelCache.set(capability, pending);\n return pending;\n }\n\n private async selectModel(\n capability: LiteLLMResolvedCapability,\n ): Promise<string> {\n const models = await super.getModels();\n const model = models.find((candidate) =>\n supportsCapability(candidate, capability),\n );\n\n if (!model) {\n throw new ValidationError(\n `No ${capability} model is available from the LiteLLM gateway`,\n {\n provider: 'litellm',\n capability,\n hint: 'Pass defaultModel explicitly or ensure /models returns a compatible model for this key',\n },\n );\n }\n\n return model.id;\n }\n\n async chat(\n messages: AIMessage[],\n options: ChatOptions = {},\n ): Promise<AIResponse> {\n assertChatReasoningControls(options);\n const safe = normalizeChatOptions(\n this.options,\n options,\n 'litellm',\n options.model || this.options.defaultModel,\n );\n return super.chat(messages, {\n ...safe,\n model: await this.resolveModel('chat', safe.model),\n });\n }\n\n async *stream(\n messages: AIMessage[],\n options: ChatOptions = {},\n ): AsyncIterable<string> {\n assertChatReasoningControls(options);\n const safe = normalizeChatOptions(\n this.options,\n options,\n 'litellm',\n options.model || this.options.defaultModel,\n );\n yield* super.stream(messages, {\n ...safe,\n model: await this.resolveModel('chat', safe.model),\n });\n }\n\n async describeImage(\n image: string | Buffer,\n prompt?: string,\n options: ImageDescriptionOptions = {},\n ): Promise<string> {\n assertChatReasoningControls(options);\n const safe = normalizeChatOptions(\n this.options,\n options,\n 'litellm',\n options.model || this.options.defaultModel,\n );\n return super.describeImage(image, prompt, {\n ...safe,\n // The inherited image path calls this.chat(); do not reclassify the\n // normalization-inferred budget as an explicit caller control.\n reasoning: options.reasoning,\n model: await this.resolveModel('vision', safe.model),\n });\n }\n\n async embed(\n text: string | string[],\n options: EmbeddingOptions = {},\n ): Promise<EmbeddingResponse> {\n return super.embed(text, {\n ...options,\n model: await this.resolveModel('embeddings', options.model),\n });\n }\n\n async embedImage(\n image: string | Buffer,\n options: ImageEmbeddingOptions = {},\n ): Promise<EmbeddingResponse> {\n return super.embedImage(image, {\n ...options,\n model: await this.resolveModel('embeddings', options.model),\n });\n }\n\n async generateImage(\n prompt: string,\n options: ImageGenerationOptions = {},\n ): Promise<ImageGenerationResponse> {\n const safe = normalizeImageGenerationOptions(\n this.options,\n options,\n 'litellm',\n options.model,\n );\n return super.generateImage(prompt, {\n ...safe,\n model: await this.resolveModel('image_generation', safe.model),\n });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"litellm-CrDQhsbb.js","names":[],"sources":["../../src/shared/providers/litellm.ts"],"sourcesContent":["import { ValidationError } from '@happyvertical/utils';\n\nimport {\n normalizeChatOptions,\n normalizeImageGenerationOptions,\n} from '../safety';\nimport type {\n AICapabilities,\n AIMessage,\n AIModel,\n AIResponse,\n ChatOptions,\n EmbeddingOptions,\n EmbeddingResponse,\n ImageDescriptionOptions,\n ImageEmbeddingOptions,\n ImageGenerationOptions,\n ImageGenerationResponse,\n LiteLLMOptions,\n} from '../types';\nimport { LiteLLMAdmin, resolveGatewayAdminBaseUrl } from './gateway-admin';\nimport {\n isAudioModel,\n isSpeechModel,\n isTranscriptionModel,\n} from './gateway-audio-models';\nimport { type OpenAICompatibleProfile, OpenAIProvider } from './openai';\n\n/** Chat Completions has effort, not a generic reasoning budget/thoughts envelope. */\nfunction assertChatReasoningControls(\n options: Pick<ChatOptions, 'reasoning' | 'includeThoughts'>,\n): void {\n if (\n options.reasoning?.maxTokens !== undefined ||\n options.reasoning?.includeThoughts !== undefined ||\n options.includeThoughts !== undefined\n ) {\n throw new ValidationError(\n 'LiteLLM Chat Completions does not support generic reasoning.maxTokens or includeThoughts controls; use reasoning.effort and an output token limit',\n { provider: 'litellm' },\n );\n }\n}\n\nconst LITELLM_CAPABILITIES: AICapabilities = {\n chat: true,\n completion: true,\n embeddings: true,\n streaming: true,\n functions: true,\n vision: true,\n fineTuning: false,\n imageEmbeddings: true,\n imageGeneration: true,\n videoGeneration: false,\n tts: false,\n voiceCloning: false,\n voiceDesign: false,\n maxContextLength: 128000,\n supportedOperations: [\n 'chat',\n 'completion',\n 'embedding',\n 'streaming',\n 'functions',\n 'vision',\n 'image_embedding',\n 'image_generation',\n ],\n};\n\nfunction isEmbeddingModel(modelId: string): boolean {\n return /embed/i.test(modelId);\n}\n\nfunction isImageEmbeddingModel(modelId: string): boolean {\n return /titan-embed-image|multimodalembedding|image[-_]?embed|clip/i.test(\n modelId,\n );\n}\n\nfunction isImageGenerationModel(modelId: string): boolean {\n return /gpt-image|dall-e|imagen|stable[-_ ]diffusion|sdxl|flux|titan-image|image-generator/i.test(\n modelId,\n );\n}\n\nfunction isFilteredLiteLLMModel(modelId: string): boolean {\n return /moderation|rerank/i.test(modelId);\n}\n\nfunction inferLiteLLMContextLength(modelId: string): number {\n if (isEmbeddingModel(modelId)) return 8192;\n if (/gemini-1\\.5|gemini-2\\.0|gemini-2\\.5|gemini-3/i.test(modelId)) {\n return 1000000;\n }\n if (/claude/i.test(modelId)) return 200000;\n if (/gpt-4o|gpt-4\\.1|o1|o3|o4|llama|mistral|qwen|deepseek/i.test(modelId)) {\n return 128000;\n }\n return 32768;\n}\n\nfunction inferLiteLLMFunctions(modelId: string): boolean {\n if (isEmbeddingModel(modelId) || isAudioModel(modelId)) return false;\n return /gpt|claude|gemini|command|llama|mistral|qwen|deepseek|o1|o3|o4/i.test(\n modelId,\n );\n}\n\nfunction inferLiteLLMVision(modelId: string): boolean {\n if (isAudioModel(modelId)) return false;\n return /gpt-4o|gpt-4\\.1|vision|claude-3|gemini|pixtral|llava|qwen.*vl|vl-/i.test(\n modelId,\n );\n}\n\nfunction inferLiteLLMCapabilities(modelId: string): string[] {\n if (isTranscriptionModel(modelId)) {\n return ['transcription'];\n }\n\n if (isSpeechModel(modelId)) {\n return ['speech'];\n }\n\n if (isImageGenerationModel(modelId)) {\n return ['image_generation'];\n }\n\n if (isImageEmbeddingModel(modelId)) {\n return ['embeddings', 'image_embedding'];\n }\n\n if (isEmbeddingModel(modelId)) {\n return ['embeddings'];\n }\n\n const capabilities = ['text', 'chat'];\n\n if (inferLiteLLMFunctions(modelId)) {\n capabilities.push('functions');\n }\n\n if (inferLiteLLMVision(modelId)) {\n capabilities.push('vision');\n }\n\n return capabilities;\n}\n\nfunction normalizeBaseUrl(baseUrl: string): string {\n return baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;\n}\n\nfunction ensureLiteLLMOptions(options: LiteLLMOptions): LiteLLMOptions {\n if (!options.baseUrl?.trim()) {\n throw new ValidationError('LiteLLM baseUrl is required', {\n provider: 'litellm',\n hint: 'Pass baseUrl like https://llm.happyvertical.com/v1 or set HAVE_AI_BASE_URL / LITELLM_BASE_URL',\n });\n }\n\n return {\n ...options,\n baseUrl: normalizeBaseUrl(options.baseUrl.trim()),\n };\n}\n\nconst LITELLM_PROFILE: OpenAICompatibleProfile = {\n providerLabel: 'LiteLLM',\n providerName: 'litellm',\n defaultModel: 'gpt-4',\n capabilities: LITELLM_CAPABILITIES,\n describeModel: (modelId) => `LiteLLM model: ${modelId}`,\n getContextLength: inferLiteLLMContextLength,\n getModelCapabilities: inferLiteLLMCapabilities,\n shouldIncludeModel: (modelId) => !isFilteredLiteLLMModel(modelId),\n supportsFunctions: inferLiteLLMFunctions,\n supportsVision: inferLiteLLMVision,\n};\n\ntype LiteLLMResolvedCapability =\n | 'chat'\n | 'vision'\n | 'embeddings'\n | 'image_generation';\n\nfunction supportsCapability(\n model: AIModel,\n capability: LiteLLMResolvedCapability,\n): boolean {\n if (capability === 'vision') {\n return (\n model.capabilities.includes('vision') || model.supportsVision === true\n );\n }\n\n if (capability === 'embeddings') {\n return model.capabilities.includes('embeddings');\n }\n\n if (capability === 'image_generation') {\n return model.capabilities.includes('image_generation');\n }\n\n return model.capabilities.includes('chat');\n}\n\n/**\n * LiteLLM provider implementation.\n *\n * LiteLLM exposes an OpenAI-compatible API surface, so this provider reuses the\n * OpenAI transport while customizing provider identity, model discovery, and\n * capability heuristics for gateway-backed deployments.\n */\nexport class LiteLLMProvider extends OpenAIProvider {\n readonly admin: LiteLLMAdmin;\n private readonly configuredDefaultModel?: string;\n private readonly resolvedModelCache = new Map<\n LiteLLMResolvedCapability,\n Promise<string>\n >();\n\n constructor(options: LiteLLMOptions) {\n const normalized = ensureLiteLLMOptions(options);\n super(normalized, LITELLM_PROFILE);\n this.admin = new LiteLLMAdmin({\n provider: 'litellm',\n baseUrl: resolveGatewayAdminBaseUrl(\n normalized.baseUrl,\n normalized.adminUrl || normalized.adminBaseUrl,\n 'litellm',\n ),\n apiKey: normalized.adminApiKey || normalized.apiKey,\n headers: normalized.adminHeaders,\n timeout: normalized.timeout,\n });\n this.configuredDefaultModel = normalized.defaultModel;\n }\n\n private async resolveModel(\n capability: LiteLLMResolvedCapability,\n explicitModel?: string,\n ): Promise<string> {\n if (explicitModel) {\n return explicitModel;\n }\n\n if (\n this.configuredDefaultModel &&\n (capability === 'chat' || capability === 'vision')\n ) {\n return this.configuredDefaultModel;\n }\n\n const cached = this.resolvedModelCache.get(capability);\n if (cached) {\n return cached;\n }\n\n const pending = this.selectModel(capability).catch((error) => {\n this.resolvedModelCache.delete(capability);\n throw error;\n });\n this.resolvedModelCache.set(capability, pending);\n return pending;\n }\n\n private async selectModel(\n capability: LiteLLMResolvedCapability,\n ): Promise<string> {\n const models = await super.getModels();\n const model = models.find((candidate) =>\n supportsCapability(candidate, capability),\n );\n\n if (!model) {\n throw new ValidationError(\n `No ${capability} model is available from the LiteLLM gateway`,\n {\n provider: 'litellm',\n capability,\n hint: 'Pass defaultModel explicitly or ensure /models returns a compatible model for this key',\n },\n );\n }\n\n return model.id;\n }\n\n async chat(\n messages: AIMessage[],\n options: ChatOptions = {},\n ): Promise<AIResponse> {\n assertChatReasoningControls(options);\n const safe = normalizeChatOptions(\n this.options,\n options,\n 'litellm',\n options.model || this.options.defaultModel,\n );\n return super.chat(messages, {\n ...safe,\n model: await this.resolveModel('chat', safe.model),\n });\n }\n\n async *stream(\n messages: AIMessage[],\n options: ChatOptions = {},\n ): AsyncIterable<string> {\n assertChatReasoningControls(options);\n const safe = normalizeChatOptions(\n this.options,\n options,\n 'litellm',\n options.model || this.options.defaultModel,\n );\n yield* super.stream(messages, {\n ...safe,\n model: await this.resolveModel('chat', safe.model),\n });\n }\n\n async describeImage(\n image: string | Buffer,\n prompt?: string,\n options: ImageDescriptionOptions = {},\n ): Promise<string> {\n assertChatReasoningControls(options);\n const safe = normalizeChatOptions(\n this.options,\n options,\n 'litellm',\n options.model || this.options.defaultModel,\n );\n return super.describeImage(image, prompt, {\n ...safe,\n // The inherited image path calls this.chat(); do not reclassify the\n // normalization-inferred budget as an explicit caller control.\n reasoning: options.reasoning,\n model: await this.resolveModel('vision', safe.model),\n });\n }\n\n async embed(\n text: string | string[],\n options: EmbeddingOptions = {},\n ): Promise<EmbeddingResponse> {\n return super.embed(text, {\n ...options,\n model: await this.resolveModel('embeddings', options.model),\n });\n }\n\n async embedImage(\n image: string | Buffer,\n options: ImageEmbeddingOptions = {},\n ): Promise<EmbeddingResponse> {\n return super.embedImage(image, {\n ...options,\n model: await this.resolveModel('embeddings', options.model),\n });\n }\n\n async generateImage(\n prompt: string,\n options: ImageGenerationOptions = {},\n ): Promise<ImageGenerationResponse> {\n const safe = normalizeImageGenerationOptions(\n this.options,\n options,\n 'litellm',\n options.model,\n );\n return super.generateImage(prompt, {\n ...safe,\n model: await this.resolveModel('image_generation', safe.model),\n });\n }\n}\n"],"mappings":"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"}