@open-mercato/ai-assistant 0.6.1-develop.3246.1.dbef9d7392 → 0.6.1-develop.3256.1.fe3dec2464
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/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +82 -18
- package/dist/modules/ai_assistant/__integration__/TC-AI-RUNTIME-OVERRIDES-006-model-picker.spec.js +370 -0
- package/dist/modules/ai_assistant/__integration__/TC-AI-RUNTIME-OVERRIDES-006-model-picker.spec.js.map +7 -0
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/models/route.js +194 -0
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/models/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/ai/agents/route.js +4 -0
- package/dist/modules/ai_assistant/api/ai/agents/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/ai/chat/route.js +169 -5
- package/dist/modules/ai_assistant/api/ai/chat/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/route/route.js +38 -19
- package/dist/modules/ai_assistant/api/route/route.js.map +3 -3
- package/dist/modules/ai_assistant/api/settings/allowlist/route.js +195 -0
- package/dist/modules/ai_assistant/api/settings/allowlist/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/settings/route.js +537 -22
- package/dist/modules/ai_assistant/api/settings/route.js.map +3 -3
- package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.js +701 -147
- package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.js.map +2 -2
- package/dist/modules/ai_assistant/backend/config/ai-assistant/allowlist/AiTenantAllowlistPageClient.js +338 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/allowlist/AiTenantAllowlistPageClient.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/allowlist/page.js +10 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/allowlist/page.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/allowlist/page.meta.js +25 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/allowlist/page.meta.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/legacy/page.js +1 -1
- package/dist/modules/ai_assistant/backend/config/ai-assistant/legacy/page.js.map +2 -2
- package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.js +75 -26
- package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.js.map +2 -2
- package/dist/modules/ai_assistant/backend/config/ai-assistant/settings/page.js +10 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/settings/page.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/settings/page.meta.js +25 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/settings/page.meta.js.map +7 -0
- package/dist/modules/ai_assistant/components/AiAssistantSettingsPageClient.js +503 -168
- package/dist/modules/ai_assistant/components/AiAssistantSettingsPageClient.js.map +2 -2
- package/dist/modules/ai_assistant/data/entities/AiAgentRuntimeOverride.js +5 -0
- package/dist/modules/ai_assistant/data/entities/AiAgentRuntimeOverride.js.map +7 -0
- package/dist/modules/ai_assistant/data/entities/AiTenantModelAllowlist.js +5 -0
- package/dist/modules/ai_assistant/data/entities/AiTenantModelAllowlist.js.map +7 -0
- package/dist/modules/ai_assistant/data/entities.js +123 -1
- package/dist/modules/ai_assistant/data/entities.js.map +2 -2
- package/dist/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.js +157 -0
- package/dist/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.js.map +7 -0
- package/dist/modules/ai_assistant/data/repositories/AiTenantModelAllowlistRepository.js +77 -0
- package/dist/modules/ai_assistant/data/repositories/AiTenantModelAllowlistRepository.js.map +7 -0
- package/dist/modules/ai_assistant/frontend/components/AiAssistantSettingsPageClient.js +1 -1
- package/dist/modules/ai_assistant/frontend/components/AiAssistantSettingsPageClient.js.map +2 -2
- package/dist/modules/ai_assistant/i18n/de.json +90 -1
- package/dist/modules/ai_assistant/i18n/en.json +90 -1
- package/dist/modules/ai_assistant/i18n/es.json +90 -1
- package/dist/modules/ai_assistant/i18n/pl.json +90 -1
- package/dist/modules/ai_assistant/lib/agent-registry.js +17 -1
- package/dist/modules/ai_assistant/lib/agent-registry.js.map +2 -2
- package/dist/modules/ai_assistant/lib/agent-runtime.js +133 -36
- package/dist/modules/ai_assistant/lib/agent-runtime.js.map +2 -2
- package/dist/modules/ai_assistant/lib/ai-agent-definition.js.map +2 -2
- package/dist/modules/ai_assistant/lib/baseurl-allowlist.js +29 -0
- package/dist/modules/ai_assistant/lib/baseurl-allowlist.js.map +7 -0
- package/dist/modules/ai_assistant/lib/llm-adapters/anthropic.js +4 -1
- package/dist/modules/ai_assistant/lib/llm-adapters/anthropic.js.map +2 -2
- package/dist/modules/ai_assistant/lib/llm-adapters/google.js +4 -1
- package/dist/modules/ai_assistant/lib/llm-adapters/google.js.map +2 -2
- package/dist/modules/ai_assistant/lib/model-allowlist.js +211 -0
- package/dist/modules/ai_assistant/lib/model-allowlist.js.map +7 -0
- package/dist/modules/ai_assistant/lib/model-factory.js +203 -31
- package/dist/modules/ai_assistant/lib/model-factory.js.map +2 -2
- package/dist/modules/ai_assistant/lib/openai-compatible-presets.js +32 -1
- package/dist/modules/ai_assistant/lib/openai-compatible-presets.js.map +2 -2
- package/dist/modules/ai_assistant/migrations/Migration20260508140000.js +18 -0
- package/dist/modules/ai_assistant/migrations/Migration20260508140000.js.map +7 -0
- package/dist/modules/ai_assistant/migrations/Migration20260512090000.js +16 -0
- package/dist/modules/ai_assistant/migrations/Migration20260512090000.js.map +7 -0
- package/dist/modules/ai_assistant/migrations/Migration20260512130000.js +15 -0
- package/dist/modules/ai_assistant/migrations/Migration20260512130000.js.map +7 -0
- package/generated/entities/ai_agent_runtime_override/index.ts +13 -0
- package/generated/entities/ai_tenant_model_allowlist/index.ts +9 -0
- package/generated/entities.ids.generated.ts +2 -0
- package/generated/entity-fields-registry.ts +26 -0
- package/jest.config.cjs +2 -0
- package/package.json +4 -4
- package/src/modules/ai_assistant/__integration__/TC-AI-RUNTIME-OVERRIDES-006-model-picker.spec.ts +477 -0
- package/src/modules/ai_assistant/__tests__/settings-page-logic.test.ts +116 -0
- package/src/modules/ai_assistant/api/ai/agents/[agentId]/models/__tests__/route.test.ts +240 -0
- package/src/modules/ai_assistant/api/ai/agents/[agentId]/models/route.ts +251 -0
- package/src/modules/ai_assistant/api/ai/agents/route.ts +4 -0
- package/src/modules/ai_assistant/api/ai/chat/__tests__/route.test.ts +273 -0
- package/src/modules/ai_assistant/api/ai/chat/route.ts +211 -2
- package/src/modules/ai_assistant/api/route/route.ts +49 -25
- package/src/modules/ai_assistant/api/settings/__tests__/route.test.ts +408 -0
- package/src/modules/ai_assistant/api/settings/allowlist/route.ts +221 -0
- package/src/modules/ai_assistant/api/settings/route.ts +721 -27
- package/src/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.tsx +858 -177
- package/src/modules/ai_assistant/backend/config/ai-assistant/allowlist/AiTenantAllowlistPageClient.tsx +458 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/allowlist/page.meta.ts +23 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/allowlist/page.tsx +12 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/legacy/page.tsx +1 -1
- package/src/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.tsx +89 -12
- package/src/modules/ai_assistant/backend/config/ai-assistant/settings/page.meta.ts +23 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/settings/page.tsx +18 -0
- package/src/modules/ai_assistant/components/AiAssistantSettingsPageClient.tsx +617 -209
- package/src/modules/ai_assistant/data/entities/AiAgentRuntimeOverride.ts +7 -0
- package/src/modules/ai_assistant/data/entities/AiTenantModelAllowlist.ts +2 -0
- package/src/modules/ai_assistant/data/entities.ts +164 -0
- package/src/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.ts +227 -0
- package/src/modules/ai_assistant/data/repositories/AiTenantModelAllowlistRepository.ts +132 -0
- package/src/modules/ai_assistant/data/repositories/__tests__/AiAgentRuntimeOverrideRepository.test.ts +337 -0
- package/src/modules/ai_assistant/data/repositories/__tests__/AiTenantModelAllowlistRepository.test.ts +181 -0
- package/src/modules/ai_assistant/frontend/components/AiAssistantSettingsPageClient.tsx +1 -1
- package/src/modules/ai_assistant/i18n/de.json +90 -1
- package/src/modules/ai_assistant/i18n/en.json +90 -1
- package/src/modules/ai_assistant/i18n/es.json +90 -1
- package/src/modules/ai_assistant/i18n/pl.json +90 -1
- package/src/modules/ai_assistant/lib/__tests__/agent-runtime-phase4a.test.ts +396 -0
- package/src/modules/ai_assistant/lib/__tests__/agent-runtime.test.ts +60 -6
- package/src/modules/ai_assistant/lib/__tests__/ai-api-operation-runner.test.ts +4 -2
- package/src/modules/ai_assistant/lib/__tests__/baseurl-allowlist.test.ts +75 -0
- package/src/modules/ai_assistant/lib/__tests__/llm-adapters-anthropic.test.ts +18 -0
- package/src/modules/ai_assistant/lib/__tests__/llm-adapters-google.test.ts +18 -0
- package/src/modules/ai_assistant/lib/__tests__/llm-adapters-openai.test.ts +150 -4
- package/src/modules/ai_assistant/lib/__tests__/model-allowlist.test.ts +290 -0
- package/src/modules/ai_assistant/lib/__tests__/model-factory.test.ts +634 -0
- package/src/modules/ai_assistant/lib/agent-registry.ts +20 -1
- package/src/modules/ai_assistant/lib/agent-runtime.ts +220 -44
- package/src/modules/ai_assistant/lib/ai-agent-definition.ts +48 -0
- package/src/modules/ai_assistant/lib/baseurl-allowlist.ts +64 -0
- package/src/modules/ai_assistant/lib/llm-adapters/anthropic.ts +11 -1
- package/src/modules/ai_assistant/lib/llm-adapters/google.ts +4 -1
- package/src/modules/ai_assistant/lib/model-allowlist.ts +407 -0
- package/src/modules/ai_assistant/lib/model-factory.ts +486 -58
- package/src/modules/ai_assistant/lib/openai-compatible-presets.ts +44 -0
- package/src/modules/ai_assistant/migrations/.snapshot-open-mercato.json +704 -235
- package/src/modules/ai_assistant/migrations/Migration20260508140000.ts +18 -0
- package/src/modules/ai_assistant/migrations/Migration20260512090000.ts +16 -0
- package/src/modules/ai_assistant/migrations/Migration20260512130000.ts +13 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/ai_assistant/lib/model-factory.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Shared AI model factory.\n *\n * Consolidates the previously-per-module model-creation plumbing (inbox_ops's\n * `llmProvider.ts`, the agent-runtime's inline `resolveAgentModel`) behind a\n * single DI-friendly port. Every AI-runtime caller (chat, object, inbox-ops\n * extraction, future agents) resolves the `LanguageModelV1` it hands to the\n * Vercel AI SDK through `createModelFactory(container).resolveModel(...)` so\n * all of them share one resolution order:\n *\n * 1. `callerOverride` (non-empty string) \u2014 highest precedence, e.g. the\n * `modelOverride` field on `runAiAgentText`/`runAiAgentObject`.\n * 2. Env variable `OM_AI_<MODULE>_MODEL` (uppercased `moduleId`) when\n * `moduleId` is provided. Example:\n * `OM_AI_INBOX_OPS_MODEL=claude-haiku-4-5`,\n * `OM_AI_CATALOG_MODEL=gpt-4o-mini`. The legacy\n * `<MODULE>_AI_MODEL` form (e.g. `INBOX_OPS_AI_MODEL`) is read as a\n * backward-compatibility fallback when the canonical name is unset.\n * 3. `agentDefaultModel` \u2014 typically `AiAgentDefinition.defaultModel`.\n * 4. Global env `OM_AI_MODEL` (canonical) with `OPENCODE_MODEL` kept as\n * a backward-compatibility fallback. Accepts either a plain model id\n * (`gpt-5-mini`) or a slash-qualified id (`openai/gpt-5-mini`).\n * Slash qualifiers consume the provider axis at the same step \u2014 a\n * higher-priority provider source still wins, but a lower-priority\n * one cannot overwrite a slash-qualified model.\n * 5. The configured provider's own default model id\n * (`provider.defaultModel`).\n *\n * Resolution walks the `llmProviderRegistry`'s `resolveFirstConfigured()`\n * output. The walk's `order` argument is seeded from (in priority order):\n *\n * 1. The slash-qualified provider hint extracted from `OM_AI_MODEL` \u2014\n * consumes the provider axis for this resolution.\n * 2. `OM_AI_PROVIDER` (canonical) with `OPENCODE_PROVIDER` as a\n * backward-compatibility fallback \u2014 names a registered provider id;\n * falls through transparently when the named provider is\n * registered-but-unconfigured.\n *\n * The factory throws {@link AiModelFactoryError} when no provider is\n * configured \u2014 every current call site already expects the throw (see the\n * bare `throw new Error('No LLM provider is configured...')` in\n * `agent-runtime.ts` prior to the consolidation).\n *\n * @see packages/shared/src/lib/ai/llm-provider-registry.ts\n * @see packages/ai-assistant/src/modules/ai_assistant/lib/agent-runtime.ts\n * @see packages/core/src/modules/inbox_ops/lib/llmProvider.ts\n */\n\nimport type { AwilixContainer } from 'awilix'\nimport type { EnvLookup, LlmProvider } from '@open-mercato/shared/lib/ai/llm-provider'\nimport { llmProviderRegistry } from '@open-mercato/shared/lib/ai/llm-provider-registry'\nimport { resolveAiProviderIdFromEnv } from '@open-mercato/shared/lib/ai/opencode-provider'\n\n/**\n * Minimal AI SDK LanguageModel shape \u2014 the factory exposes the protocol-\n * agnostic `unknown`-typed return from {@link LlmProvider.createModel} under a\n * dedicated alias so callers can document intent without importing the AI SDK\n * here. Call sites that hand the result to `generateText` / `streamText` /\n * `generateObject` / `streamObject` continue to cast to the SDK's\n * `LanguageModelV1` / `LanguageModel` union exactly as they already do.\n */\nexport type AiModelInstance = unknown\n\n/**\n * Input accepted by {@link AiModelFactory.resolveModel}. All fields are\n * optional \u2014 passing an empty input resolves the provider default.\n */\nexport interface AiModelFactoryInput {\n /**\n * Owning module id (matches `Module.id`). When set, the factory checks\n * `OM_AI_<MODULE>_MODEL` (uppercased) as the env-override source, with\n * the legacy `<MODULE>_AI_MODEL` form honored as a backward-compatibility\n * fallback. Example: `moduleId: 'inbox_ops'` \u2192 canonical env var\n * `OM_AI_INBOX_OPS_MODEL` (legacy `INBOX_OPS_AI_MODEL`).\n */\n moduleId?: string\n /**\n * Agent-level default, typically `AiAgentDefinition.defaultModel`. Used\n * when neither `callerOverride` nor the module env override is present.\n */\n agentDefaultModel?: string\n /**\n * Per-call override (e.g. `runAiAgentText({ modelOverride })`). Wins over\n * every other source when it is a non-empty trimmed string. Empty strings\n * are treated as \"no override\" so the next source in the chain wins \u2014\n * callers MUST NOT need a separate \"clear override\" API.\n */\n callerOverride?: string\n}\n\n/**\n * Materialized output returned by {@link AiModelFactory.resolveModel}.\n */\nexport interface AiModelResolution {\n /**\n * Concrete AI SDK model instance ready to pass to\n * `generateText`/`streamText`/`generateObject`/`streamObject`. Typed as\n * {@link AiModelInstance} to avoid coupling this port to a specific SDK\n * major version.\n */\n model: AiModelInstance\n /** Resolved upstream model id (e.g. `claude-haiku-4-5-20251001`). */\n modelId: string\n /** Stable provider id from {@link LlmProvider.id}. */\n providerId: string\n /**\n * Which source won resolution. Useful for logs and tests; never exposed\n * as a public contract beyond these enum values.\n *\n * - `env_default` indicates `OM_AI_MODEL` (preferred) or the legacy\n * `OPENCODE_MODEL` fallback supplied the model id.\n */\n source:\n | 'caller_override'\n | 'module_env'\n | 'agent_default'\n | 'env_default'\n | 'provider_default'\n}\n\n/**\n * Port exposed by {@link createModelFactory}. Stateless \u2014 the factory\n * re-reads the registry + env on every `resolveModel` call so hot-reload\n * and test overrides work without needing factory re-creation.\n */\nexport interface AiModelFactory {\n resolveModel(input: AiModelFactoryInput): AiModelResolution\n}\n\n/**\n * Typed error thrown by the factory when it cannot materialize a model.\n *\n * `code` is a stable string union so downstream callers can branch without\n * parsing error messages. `AiModelFactoryError`s bubble through\n * `runAiAgentText`/`runAiAgentObject` unchanged \u2014 the agent runtime does\n * NOT catch them, matching the pre-consolidation behavior of the inline\n * resolver.\n */\nexport type AiModelFactoryErrorCode =\n | 'no_provider_configured'\n | 'api_key_missing'\n\nexport class AiModelFactoryError extends Error {\n readonly code: AiModelFactoryErrorCode\n\n constructor(code: AiModelFactoryErrorCode, message: string) {\n super(message)\n this.name = 'AiModelFactoryError'\n this.code = code\n }\n}\n\n/**\n * Subset of {@link import('@open-mercato/shared/lib/ai/llm-provider-registry').LlmProviderRegistry}\n * the factory consumes. Defined locally so test doubles only need to mock\n * the methods the factory actually calls.\n */\nexport interface AiModelFactoryRegistry {\n resolveFirstConfigured(options?: {\n env?: EnvLookup\n order?: readonly string[]\n }): LlmProvider | null\n /**\n * Optional registry lookup used by the slash-shorthand parser to validate\n * a provider hint. When absent, slash parsing is disabled and the entire\n * model token is treated as a model id (mirrors the pre-Phase-0\n * behavior).\n */\n get?(id: string): LlmProvider | null\n}\n\n/**\n * Internal dependencies of the factory. Exposed for tests only; production\n * callers rely on the defaults wired by {@link createModelFactory}.\n */\nexport interface CreateModelFactoryDependencies {\n /**\n * Registry used to resolve the first configured provider. Defaults to the\n * singleton `llmProviderRegistry`. Implementations MAY honor the optional\n * `order` argument to prefer the operator-selected provider.\n */\n registry?: AiModelFactoryRegistry\n /** Env lookup for `<MODULE>_AI_MODEL` + provider credentials. */\n env?: EnvLookup\n}\n\nfunction normalizeOverride(value: string | undefined): string | null {\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : null\n}\n\n/**\n * Reads the operator-selected provider id from the unified env vars.\n * Returns `null` when neither `OM_AI_PROVIDER` nor the legacy\n * `OPENCODE_PROVIDER` resolves to a known provider \u2014 in that case the\n * registry falls back to its default registration walk.\n */\nfunction readProviderOrderFromEnv(env: EnvLookup): readonly string[] | undefined {\n const raw = normalizeOverride(env.OM_AI_PROVIDER) ?? normalizeOverride(env.OPENCODE_PROVIDER)\n if (!raw) return undefined\n // Reuse the shared resolver so unknown ids fall back through both keys.\n // When the raw value is unknown the resolver returns the default; passing\n // that through as an explicit hint is still safe because the registry\n // only honors registered + configured providers.\n const resolved = resolveAiProviderIdFromEnv(env)\n return [resolved]\n}\n\n/**\n * Reads the global model hint from the unified env vars. `OM_AI_MODEL`\n * wins over the legacy `OPENCODE_MODEL`.\n */\nfunction readGlobalModelFromEnv(env: EnvLookup): string | null {\n return normalizeOverride(env.OM_AI_MODEL) ?? normalizeOverride(env.OPENCODE_MODEL)\n}\n\n/** Canonical per-module model env. Example: `OM_AI_INBOX_OPS_MODEL`. */\nfunction moduleEnvVarName(moduleId: string): string {\n return `OM_AI_${moduleId.toUpperCase()}_MODEL`\n}\n\n/**\n * Legacy per-module model env (pre-OM_AI_* rename). Example:\n * `INBOX_OPS_AI_MODEL`. Read as a backward-compatibility fallback only.\n */\nfunction legacyModuleEnvVarName(moduleId: string): string {\n return `${moduleId.toUpperCase()}_AI_MODEL`\n}\n\nfunction readModuleEnvOverride(env: EnvLookup, moduleId: string): string | null {\n return (\n normalizeOverride(env[moduleEnvVarName(moduleId)]) ??\n normalizeOverride(env[legacyModuleEnvVarName(moduleId)])\n )\n}\n\n/**\n * Splits a slash-qualified model token (e.g. `openai/gpt-5-mini`) into\n * `{ providerHint, modelId }` when the prefix matches a registered provider\n * id, otherwise returns the entire token as the model id and a null hint.\n *\n * The registry-membership guard avoids mis-splitting model ids that already\n * contain slashes (DeepInfra: `meta-llama/Llama-3.3-70B-Instruct-Turbo`,\n * `zai-org/GLM-5.1`). When the registry does not expose `get`, slash\n * parsing is disabled \u2014 callers without a configured registry behave as if\n * the entire token were a plain model id.\n *\n * Exported for test coverage; production callers go through\n * {@link createModelFactory}.\n */\nexport function parseSlashShorthand(\n token: string,\n registry: Pick<AiModelFactoryRegistry, 'get'>,\n): { providerHint: string | null; modelId: string } {\n const slashIndex = token.indexOf('/')\n if (slashIndex < 0) return { providerHint: null, modelId: token }\n const before = token.slice(0, slashIndex)\n const after = token.slice(slashIndex + 1)\n if (!before || !after) return { providerHint: null, modelId: token }\n if (!registry.get) return { providerHint: null, modelId: token }\n const provider = registry.get(before)\n if (!provider) return { providerHint: null, modelId: token }\n return { providerHint: before, modelId: after }\n}\n\n/**\n * Creates an {@link AiModelFactory} bound to the DI container. The container\n * reference is accepted for API symmetry with other runtime helpers (and so\n * future work can read provider overrides registered on the container); the\n * current implementation only needs the registry + env. No breaking change\n * when later implementations DO consult the container.\n */\nexport function createModelFactory(\n _container: AwilixContainer,\n deps: CreateModelFactoryDependencies = {},\n): AiModelFactory {\n const registry: AiModelFactoryRegistry = deps.registry ?? llmProviderRegistry\n const env = deps.env ?? process.env\n\n return {\n resolveModel(input: AiModelFactoryInput): AiModelResolution {\n // OM_AI_MODEL is canonical; the legacy OPENCODE_MODEL is read as a\n // backward-compatibility fallback through readGlobalModelFromEnv.\n const globalModelEnv = readGlobalModelFromEnv(env)\n // Slash-qualified env-model values consume the provider axis at the\n // env-default step. Phase 1 of the per-axis-overrides spec generalizes\n // the parser to every model-axis source.\n const globalModelParsed = globalModelEnv\n ? parseSlashShorthand(globalModelEnv, registry)\n : null\n const slashProviderHint = globalModelParsed?.providerHint ?? null\n const providerOrderFromEnv = readProviderOrderFromEnv(env)\n const order = slashProviderHint\n ? [slashProviderHint, ...(providerOrderFromEnv ?? [])]\n : providerOrderFromEnv\n\n const provider = registry.resolveFirstConfigured({ env, order })\n if (!provider) {\n throw new AiModelFactoryError(\n 'no_provider_configured',\n 'No LLM provider is configured. Set OM_AI_PROVIDER (or the legacy OPENCODE_PROVIDER) plus a matching API key such as OPENAI_API_KEY, ANTHROPIC_API_KEY, or GOOGLE_GENERATIVE_AI_API_KEY, then restart the app. See https://docs.openmercato.com/framework/ai-assistant/overview.',\n )\n }\n const apiKey = provider.resolveApiKey(env)\n if (!apiKey) {\n throw new AiModelFactoryError(\n 'api_key_missing',\n `LLM provider \"${provider.id}\" is advertised as configured but resolveApiKey() returned empty.`,\n )\n }\n\n const callerOverride = normalizeOverride(input.callerOverride)\n const moduleEnvOverride =\n input.moduleId && input.moduleId.length > 0\n ? readModuleEnvOverride(env, input.moduleId)\n : null\n const agentDefault = normalizeOverride(input.agentDefaultModel)\n // The slash parser already split the global model token; use the\n // post-parse model id so `OM_AI_MODEL=openai/gpt-5-mini` resolves\n // model `gpt-5-mini` against provider `openai`.\n const envDefaultModel = globalModelParsed?.modelId ?? globalModelEnv\n\n let modelId: string\n let source: AiModelResolution['source']\n if (callerOverride) {\n modelId = callerOverride\n source = 'caller_override'\n } else if (moduleEnvOverride) {\n modelId = moduleEnvOverride\n source = 'module_env'\n } else if (agentDefault) {\n modelId = agentDefault\n source = 'agent_default'\n } else if (envDefaultModel) {\n modelId = envDefaultModel\n source = 'env_default'\n } else {\n modelId = provider.defaultModel\n source = 'provider_default'\n }\n\n const model = provider.createModel({ modelId, apiKey })\n return {\n model,\n modelId,\n providerId: provider.id,\n source,\n }\n },\n }\n}\n"],
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["/**\n * Shared AI model factory.\n *\n * Consolidates the previously-per-module model-creation plumbing (inbox_ops's\n * `llmProvider.ts`, the agent-runtime's inline `resolveAgentModel`) behind a\n * single DI-friendly port. Every AI-runtime caller (chat, object, inbox-ops\n * extraction, future agents) resolves the `LanguageModelV1` it hands to the\n * Vercel AI SDK through `createModelFactory(container).resolveModel(...)` so\n * all of them share one resolution order:\n *\n * 1. `callerOverride` (non-empty string) \u2014 highest precedence, e.g. the\n * `modelOverride` field on `runAiAgentText`/`runAiAgentObject`.\n * Accepts a slash-qualified `<provider>/<model>` shorthand (Phase 1).\n * 2. Env variable `OM_AI_<MODULE>_MODEL` (uppercased `moduleId`) when\n * `moduleId` is provided. Example:\n * `OM_AI_INBOX_OPS_MODEL=claude-haiku-4-5`,\n * `OM_AI_CATALOG_MODEL=gpt-4o-mini`. The legacy\n * `<MODULE>_AI_MODEL` form (e.g. `INBOX_OPS_AI_MODEL`) is read as a\n * backward-compatibility fallback when the canonical name is unset.\n * Accepts a slash-qualified shorthand (Phase 1).\n * 3. `agentDefaultModel` \u2014 typically `AiAgentDefinition.defaultModel`.\n * Accepts a slash-qualified `<provider>/<model>` shorthand (Phase 1).\n * 4. Global env `OM_AI_MODEL` (canonical) with `OPENCODE_MODEL` kept as\n * a backward-compatibility fallback. Accepts either a plain model id\n * (`gpt-5-mini`) or a slash-qualified id (`openai/gpt-5-mini`).\n * Slash qualifiers consume the provider axis at the same step \u2014 a\n * higher-priority provider source still wins, but a lower-priority\n * one cannot overwrite a slash-qualified model.\n * 5. The configured provider's own default model id\n * (`provider.defaultModel`).\n *\n * Every model-axis source is parsed through {@link parseSlashShorthand}.\n * Resolution walks the chain top-down and takes the first non-null hint as\n * the registry-walk seed:\n *\n * Provider-axis seed order (highest priority first):\n * 1. Slash-prefix from `callerOverride` (Phase 1).\n * 2. `providerOverride` \u2014 request-time provider override (Phase 1).\n * 3. Slash-prefix from `OM_AI_<MODULE>_MODEL` (legacy `<MODULE>_AI_MODEL`) (Phase 1).\n * 4. `OM_AI_<MODULE>_PROVIDER` env (legacy `<MODULE>_AI_PROVIDER`) (Phase 1).\n * 5. Slash-prefix from `agentDefaultModel` (Phase 1).\n * 6. `agentDefaultProvider` \u2014 `AiAgentDefinition.defaultProvider` (Phase 1).\n * 7. Slash-prefix from `OM_AI_MODEL` (legacy `OPENCODE_MODEL`) (Phase 0).\n * 8. `OM_AI_PROVIDER` (legacy `OPENCODE_PROVIDER`) (Phase 0).\n *\n * The `OM_AI_*` env knobs are canonical; the legacy `OPENCODE_PROVIDER` /\n * `OPENCODE_MODEL` envs stay bound to the OpenCode Code Mode stack and are\n * also honored as backward-compatibility fallbacks here.\n *\n * The factory throws {@link AiModelFactoryError} when no provider is\n * configured \u2014 every current call site already expects the throw (see the\n * bare `throw new Error('No LLM provider is configured...')` in\n * `agent-runtime.ts` prior to the consolidation).\n *\n * @see packages/shared/src/lib/ai/llm-provider-registry.ts\n * @see packages/ai-assistant/src/modules/ai_assistant/lib/agent-runtime.ts\n * @see packages/core/src/modules/inbox_ops/lib/llmProvider.ts\n */\n\nimport type { AwilixContainer } from 'awilix'\nimport type { EnvLookup, LlmProvider } from '@open-mercato/shared/lib/ai/llm-provider'\nimport { llmProviderRegistry } from '@open-mercato/shared/lib/ai/llm-provider-registry'\nimport {\n intersectAllowlists,\n canonicalProviderId,\n isModelAllowedForProviderInEffective,\n isProviderAllowedInEffective,\n providerIdAliases,\n type EffectiveAllowlist,\n type TenantAllowlistSnapshot,\n} from './model-allowlist'\n\n/**\n * Minimal AI SDK LanguageModel shape \u2014 the factory exposes the protocol-\n * agnostic `unknown`-typed return from {@link LlmProvider.createModel} under a\n * dedicated alias so callers can document intent without importing the AI SDK\n * here. Call sites that hand the result to `generateText` / `streamText` /\n * `generateObject` / `streamObject` continue to cast to the SDK's\n * `LanguageModelV1` / `LanguageModel` union exactly as they already do.\n */\nexport type AiModelInstance = unknown\n\n/**\n * Input accepted by {@link AiModelFactory.resolveModel}. All fields are\n * optional \u2014 passing an empty input resolves the provider default.\n */\nexport interface AiModelFactoryInput {\n /**\n * Owning module id (matches `Module.id`). When set, the factory checks\n * `OM_AI_<MODULE>_MODEL` (uppercased) as the env-override source, with\n * the legacy `<MODULE>_AI_MODEL` form honored as a backward-compatibility\n * fallback. Example: `moduleId: 'inbox_ops'` \u2192 canonical env var\n * `OM_AI_INBOX_OPS_MODEL` (legacy `INBOX_OPS_AI_MODEL`).\n *\n * Also enables the `OM_AI_<MODULE>_PROVIDER` env axis (legacy\n * `<MODULE>_AI_PROVIDER` honored as a backward-compatibility fallback).\n */\n moduleId?: string\n /**\n * Agent-level default, typically `AiAgentDefinition.defaultModel`. Used\n * when neither `callerOverride` nor the module env override is present.\n * Accepts a slash-qualified `<provider>/<model>` shorthand (Phase 1).\n */\n agentDefaultModel?: string\n /**\n * Agent-level default provider, typically `AiAgentDefinition.defaultProvider`.\n * Named provider id; falls through transparently when the named provider is\n * registered-but-unconfigured. Sits between `OM_AI_<MODULE>_PROVIDER`\n * and the global `OM_AI_PROVIDER` in the provider-axis seed list above.\n *\n * Phase 1 of spec `2026-04-27-ai-agents-provider-model-baseurl-overrides`.\n */\n agentDefaultProvider?: string\n /**\n * Per-call override (e.g. `runAiAgentText({ modelOverride })`). Wins over\n * every other source when it is a non-empty trimmed string. Empty strings\n * are treated as \"no override\" so the next source in the chain wins \u2014\n * callers MUST NOT need a separate \"clear override\" API.\n */\n callerOverride?: string\n /**\n * Request-time provider override \u2014 wins for the provider axis at the same\n * priority as `callerOverride` for the model axis. A non-empty string\n * that does not match any registered provider id is silently ignored and\n * the factory falls through to the next provider source.\n *\n * Phase 1 of spec `2026-04-27-ai-agents-provider-model-baseurl-overrides`.\n */\n providerOverride?: string\n /**\n * Agent-level default base URL, typically `AiAgentDefinition.defaultBaseUrl`.\n * Sits between the `<MODULE>_AI_BASE_URL` env var and the preset's own\n * `baseURLEnvKeys` in the resolution chain.\n *\n * Phase 2 of spec `2026-04-27-ai-agents-provider-model-baseurl-overrides`.\n */\n agentDefaultBaseUrl?: string\n /**\n * Per-call base URL override that wins over every other source. Intended\n * for programmatic callers only \u2014 the HTTP query-param baseUrl and the\n * AI_RUNTIME_BASEURL_ALLOWLIST arrive in Phase 4a.\n *\n * Phase 2 of spec `2026-04-27-ai-agents-provider-model-baseurl-overrides`.\n */\n baseUrlOverride?: string\n /**\n * Per-tenant default loaded from `ai_agent_runtime_overrides` by the agent\n * runtime (best-effort, fail-open). Sits at step 3 of the resolution chain\n * between the caller/request override (step 1\u20132) and the module-env axis\n * (step 4).\n *\n * Honored ONLY when `allowRuntimeModelOverride !== false` on the agent\n * definition. The agent runtime is responsible for hydration \u2014 the factory\n * does NOT load the row itself.\n *\n * Phase 4a of spec `2026-04-27-ai-agents-provider-model-baseurl-overrides`.\n */\n tenantOverride?: {\n providerId?: string | null\n modelId?: string | null\n baseURL?: string | null\n }\n /**\n * Per-request override forwarded from the HTTP dispatcher query params\n * (`?provider=`, `?model=`, `?baseUrl=`). Sits at step 1 of the resolution\n * chain \u2014 wins over everything else for that turn.\n *\n * Honored ONLY when `allowRuntimeModelOverride !== false` on the agent.\n * The dispatcher validates all three values before setting this input.\n *\n * Phase 4a of spec `2026-04-27-ai-agents-provider-model-baseurl-overrides`.\n */\n requestOverride?: {\n providerId?: string | null\n modelId?: string | null\n baseURL?: string | null\n }\n /**\n * When false, steps 1 (requestOverride) and 3 (tenantOverride) of the\n * resolution chain are skipped. Agents that pin a specific model for\n * correctness reasons set `AiAgentDefinition.allowRuntimeModelOverride =\n * false`. Default behavior (omitted) is permissive (= true).\n *\n * Phase 4a of spec `2026-04-27-ai-agents-provider-model-baseurl-overrides`.\n */\n allowRuntimeModelOverride?: boolean\n /**\n * Optional tenant allowlist snapshot (Phase 1780-6). When supplied, the\n * factory clips the resolved (provider, model) to the intersection of the\n * env allowlist (`OM_AI_AVAILABLE_*`) and this tenant allowlist. Pass `null`\n * or omit to fall back to env-only enforcement.\n *\n * The settings PUT route validates writes against the env allowlist before\n * persisting, so the snapshot here is trusted to be a subset of env. The\n * factory still defends against drift (env tightened after write) by\n * intersecting at resolution time.\n */\n tenantAllowlist?: TenantAllowlistSnapshot | null\n}\n\n/**\n * Materialized output returned by {@link AiModelFactory.resolveModel}.\n */\nexport interface AiModelResolution {\n /**\n * Concrete AI SDK model instance ready to pass to\n * `generateText`/`streamText`/`generateObject`/`streamObject`. Typed as\n * {@link AiModelInstance} to avoid coupling this port to a specific SDK\n * major version.\n */\n model: AiModelInstance\n /** Resolved upstream model id (e.g. `claude-haiku-4-5-20251001`). */\n modelId: string\n /** Stable provider id from {@link LlmProvider.id}. */\n providerId: string\n /**\n * Which source won resolution. Useful for logs and tests; never exposed\n * as a public contract beyond these enum values.\n *\n * - `env_default` indicates `OM_AI_MODEL` (preferred) or the legacy\n * `OPENCODE_MODEL` fallback supplied the model id.\n */\n source:\n | 'request_override'\n | 'caller_override'\n | 'tenant_override'\n | 'module_env'\n | 'agent_default'\n | 'env_default'\n | 'provider_default'\n | 'allowlist_fallback'\n /**\n * Resolved base URL passed to the adapter (if any). Undefined when the\n * adapter will use its built-in default. Included for observability and\n * test assertions; never exposed over HTTP (Phase 4a adds the allowlist).\n */\n baseURL?: string\n /**\n * Populated when the env-driven OM_AI_AVAILABLE_PROVIDERS /\n * OM_AI_AVAILABLE_MODELS_<PROVIDER> allowlist rejected the originally\n * resolved (provider, model) and the factory fell back to a safe pair.\n * Includes the rejected ids and a human-readable reason so the UI / logs\n * can surface why the runtime did not honor the requested combination.\n */\n allowlistFallback?: {\n originalProviderId: string\n originalModelId: string\n reason: string\n }\n}\n\n/**\n * Port exposed by {@link createModelFactory}. Stateless \u2014 the factory\n * re-reads the registry + env on every `resolveModel` call so hot-reload\n * and test overrides work without needing factory re-creation.\n */\nexport interface AiModelFactory {\n resolveModel(input: AiModelFactoryInput): AiModelResolution\n}\n\n/**\n * Typed error thrown by the factory when it cannot materialize a model.\n *\n * `code` is a stable string union so downstream callers can branch without\n * parsing error messages. `AiModelFactoryError`s bubble through\n * `runAiAgentText`/`runAiAgentObject` unchanged \u2014 the agent runtime does\n * NOT catch them, matching the pre-consolidation behavior of the inline\n * resolver.\n */\nexport type AiModelFactoryErrorCode =\n | 'no_provider_configured'\n | 'api_key_missing'\n\nexport class AiModelFactoryError extends Error {\n readonly code: AiModelFactoryErrorCode\n\n constructor(code: AiModelFactoryErrorCode, message: string) {\n super(message)\n this.name = 'AiModelFactoryError'\n this.code = code\n }\n}\n\n/**\n * Subset of {@link import('@open-mercato/shared/lib/ai/llm-provider-registry').LlmProviderRegistry}\n * the factory consumes. Defined locally so test doubles only need to mock\n * the methods the factory actually calls.\n */\nexport interface AiModelFactoryRegistry {\n resolveFirstConfigured(options?: {\n env?: EnvLookup\n order?: readonly string[]\n }): LlmProvider | null\n /**\n * Optional registry lookup used by the slash-shorthand parser to validate\n * a provider hint. When absent, slash parsing is disabled and the entire\n * model token is treated as a model id (mirrors the pre-Phase-0\n * behavior).\n */\n get?(id: string): LlmProvider | null\n /**\n * Optional registry enumeration used by the Phase 1780-6 allowlist\n * intersection so the env model lists are pre-loaded for every provider\n * (and not just the resolved one). Test doubles MAY omit this \u2014 the\n * factory still defends correctly by also seeding the resolved provider's\n * id directly into `intersectAllowlists(...)`.\n */\n list?(): readonly LlmProvider[]\n}\n\n/**\n * Internal dependencies of the factory. Exposed for tests only; production\n * callers rely on the defaults wired by {@link createModelFactory}.\n */\nexport interface CreateModelFactoryDependencies {\n /**\n * Registry used to resolve the first configured provider. Defaults to the\n * singleton `llmProviderRegistry`. Implementations MAY honor the optional\n * `order` argument to prefer the operator-selected provider.\n */\n registry?: AiModelFactoryRegistry\n /** Env lookup for `OM_AI_<MODULE>_MODEL` + provider credentials. */\n env?: EnvLookup\n}\n\nfunction normalizeOverride(value: string | undefined): string | null {\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : null\n}\n\n/**\n * Reads the operator-selected provider id from the unified env vars.\n * Returns `null` when neither `OM_AI_PROVIDER` nor the legacy\n * `OPENCODE_PROVIDER` resolves to a known provider \u2014 in that case the\n * registry falls back to its default registration walk.\n */\nfunction readGlobalProviderFromEnv(\n env: EnvLookup,\n registry: Pick<AiModelFactoryRegistry, 'get'>,\n): string | null {\n const candidates = [normalizeOverride(env.OM_AI_PROVIDER), normalizeOverride(env.OPENCODE_PROVIDER)]\n for (const candidate of candidates) {\n if (!candidate) continue\n if (!registry.get) return providerIdAliases(candidate)[0] ?? candidate\n for (const alias of providerIdAliases(candidate)) {\n if (registry.get(alias)) return alias\n }\n }\n return null\n}\n\n/**\n * Reads the global model hint from the unified env vars. `OM_AI_MODEL`\n * wins over the legacy `OPENCODE_MODEL`.\n */\nfunction readGlobalModelFromEnv(env: EnvLookup): string | null {\n return normalizeOverride(env.OM_AI_MODEL) ?? normalizeOverride(env.OPENCODE_MODEL)\n}\n\n/** Canonical per-module model env. Example: `OM_AI_INBOX_OPS_MODEL`. */\nfunction moduleModelEnvVarName(moduleId: string): string {\n return `OM_AI_${moduleId.toUpperCase()}_MODEL`\n}\n\n/**\n * Legacy per-module model env (pre-OM_AI_* rename). Example:\n * `INBOX_OPS_AI_MODEL`. Read as a backward-compatibility fallback only.\n */\nfunction legacyModuleModelEnvVarName(moduleId: string): string {\n return `${moduleId.toUpperCase()}_AI_MODEL`\n}\n\nfunction readModuleModelEnvOverride(env: EnvLookup, moduleId: string): string | null {\n return (\n normalizeOverride(env[moduleModelEnvVarName(moduleId)]) ??\n normalizeOverride(env[legacyModuleModelEnvVarName(moduleId)])\n )\n}\n\n/** Canonical per-module provider env. Example: `OM_AI_INBOX_OPS_PROVIDER`. */\nfunction moduleProviderEnvVarName(moduleId: string): string {\n return `OM_AI_${moduleId.toUpperCase()}_PROVIDER`\n}\n\n/**\n * Legacy per-module provider env (pre-OM_AI_* rename). Example:\n * `INBOX_OPS_AI_PROVIDER`. Read as a backward-compatibility fallback only.\n */\nfunction legacyModuleProviderEnvVarName(moduleId: string): string {\n return `${moduleId.toUpperCase()}_AI_PROVIDER`\n}\n\nfunction readModuleProviderEnvOverride(env: EnvLookup, moduleId: string): string | null {\n return (\n normalizeOverride(env[moduleProviderEnvVarName(moduleId)]) ??\n normalizeOverride(env[legacyModuleProviderEnvVarName(moduleId)])\n )\n}\n\nfunction normalizeProviderHint(\n providerId: string | null,\n registry: AiModelFactoryRegistry,\n): string | null {\n if (!providerId) return null\n const knownProviderIds = registry.list?.().map((provider) => provider.id) ?? []\n if (knownProviderIds.length > 0) {\n return canonicalProviderId(providerId, knownProviderIds)\n }\n return providerIdAliases(providerId)[0] ?? providerId\n}\n\nfunction moduleBaseUrlEnvVarName(moduleId: string): string {\n return `${moduleId.toUpperCase()}_AI_BASE_URL`\n}\n\n/**\n * Splits a slash-qualified model token (e.g. `openai/gpt-5-mini`) into\n * `{ providerHint, modelId }` when the prefix matches a registered provider\n * id, otherwise returns the entire token as the model id and a null hint.\n *\n * The registry-membership guard avoids mis-splitting model ids that already\n * contain slashes (DeepInfra: `meta-llama/Llama-3.3-70B-Instruct-Turbo`,\n * `zai-org/GLM-5.1`). When the registry does not expose `get`, slash\n * parsing is disabled \u2014 callers without a configured registry behave as if\n * the entire token were a plain model id.\n *\n * Exported for test coverage; production callers go through\n * {@link createModelFactory}.\n */\nexport function parseSlashShorthand(\n token: string,\n registry: Pick<AiModelFactoryRegistry, 'get'>,\n): { providerHint: string | null; modelId: string } {\n const slashIndex = token.indexOf('/')\n if (slashIndex < 0) return { providerHint: null, modelId: token }\n const before = token.slice(0, slashIndex)\n const after = token.slice(slashIndex + 1)\n if (!before || !after) return { providerHint: null, modelId: token }\n if (!registry.get) return { providerHint: null, modelId: token }\n const provider = registry.get(before)\n if (!provider) return { providerHint: null, modelId: token }\n return { providerHint: before, modelId: after }\n}\n\n/**\n * Creates an {@link AiModelFactory} bound to the DI container. The container\n * reference is accepted for API symmetry with other runtime helpers (and so\n * future work can read provider overrides registered on the container); the\n * current implementation only needs the registry + env. No breaking change\n * when later implementations DO consult the container.\n */\nexport function createModelFactory(\n _container: AwilixContainer,\n deps: CreateModelFactoryDependencies = {},\n): AiModelFactory {\n const registry: AiModelFactoryRegistry = deps.registry ?? llmProviderRegistry\n const env = deps.env ?? process.env\n\n return {\n resolveModel(input: AiModelFactoryInput): AiModelResolution {\n const hasModule = typeof input.moduleId === 'string' && input.moduleId.length > 0\n // When allowRuntimeModelOverride is explicitly false, skip steps 1\n // (requestOverride) and 3 (tenantOverride) \u2014 the agent pins a model.\n const runtimeOverridesAllowed = input.allowRuntimeModelOverride !== false\n\n // --- Step 1: requestOverride (HTTP query params) \u2014 gated by flag ---\n const requestModelRaw = runtimeOverridesAllowed\n ? normalizeOverride(input.requestOverride?.modelId ?? undefined)\n : null\n const requestProviderRaw = runtimeOverridesAllowed\n ? normalizeOverride(input.requestOverride?.providerId ?? undefined)\n : null\n const requestBaseUrlRaw = runtimeOverridesAllowed\n ? normalizeOverride(input.requestOverride?.baseURL ?? undefined)\n : null\n\n // --- Step 2: callerOverride (programmatic) ---\n const callerRaw = normalizeOverride(input.callerOverride)\n\n // --- Step 3: tenantOverride (DB row) \u2014 gated by flag ---\n const tenantModelRaw = runtimeOverridesAllowed\n ? normalizeOverride(input.tenantOverride?.modelId ?? undefined)\n : null\n const tenantProviderRaw = runtimeOverridesAllowed\n ? normalizeOverride(input.tenantOverride?.providerId ?? undefined)\n : null\n const tenantBaseUrlRaw = runtimeOverridesAllowed\n ? normalizeOverride(input.tenantOverride?.baseURL ?? undefined)\n : null\n\n // --- Steps 4+: env / agent / global ---\n const moduleModelRaw = hasModule\n ? readModuleModelEnvOverride(env, input.moduleId!)\n : null\n const agentModelRaw = normalizeOverride(input.agentDefaultModel)\n // OM_AI_MODEL is canonical; the legacy OPENCODE_MODEL is read as a\n // backward-compatibility fallback through readGlobalModelFromEnv.\n const globalModelRaw = readGlobalModelFromEnv(env)\n\n // Parse slash shorthand on every model-axis source.\n const requestModelParsed = requestModelRaw ? parseSlashShorthand(requestModelRaw, registry) : null\n const callerParsed = callerRaw ? parseSlashShorthand(callerRaw, registry) : null\n const tenantModelParsed = tenantModelRaw ? parseSlashShorthand(tenantModelRaw, registry) : null\n const moduleModelParsed = moduleModelRaw ? parseSlashShorthand(moduleModelRaw, registry) : null\n const agentModelParsed = agentModelRaw ? parseSlashShorthand(agentModelRaw, registry) : null\n const globalModelParsed = globalModelRaw ? parseSlashShorthand(globalModelRaw, registry) : null\n\n // --- Provider-axis: walk from highest to lowest priority for the seed.\n // A slash-qualified hint from a model source wins over a plain provider\n // source at the same priority step. We walk top-down and take the first\n // non-null hint.\n const providerOverrideRaw = normalizeOverride(input.providerOverride)\n const moduleProviderRaw = hasModule\n ? readModuleProviderEnvOverride(env, input.moduleId!)\n : null\n const agentDefaultProviderRaw = normalizeOverride(input.agentDefaultProvider)\n // OM_AI_PROVIDER is canonical; the legacy OPENCODE_PROVIDER is read as\n // a backward-compatibility fallback through readGlobalProviderFromEnv.\n const globalProviderRaw = readGlobalProviderFromEnv(env, registry)\n\n // Walk the provider-axis seed list: slash hint beats plain provider at\n // the same step. We keep only the first (highest-priority) non-null hint.\n const providerHintCandidates: Array<string | null> = [\n requestModelParsed?.providerHint ?? null,\n normalizeProviderHint(requestProviderRaw, registry),\n callerParsed?.providerHint ?? null,\n normalizeProviderHint(providerOverrideRaw, registry),\n tenantModelParsed?.providerHint ?? null,\n normalizeProviderHint(tenantProviderRaw, registry),\n moduleModelParsed?.providerHint ?? null,\n normalizeProviderHint(moduleProviderRaw, registry),\n agentModelParsed?.providerHint ?? null,\n normalizeProviderHint(agentDefaultProviderRaw, registry),\n globalModelParsed?.providerHint ?? null,\n globalProviderRaw,\n ]\n const orderHint = providerHintCandidates.find((hint) => hint !== null) ?? null\n const order = orderHint ? [orderHint] : undefined\n\n const provider = registry.resolveFirstConfigured({ env, order })\n if (!provider) {\n throw new AiModelFactoryError(\n 'no_provider_configured',\n 'No LLM provider is configured. Set OM_AI_PROVIDER (or the legacy OPENCODE_PROVIDER) plus a matching API key such as OPENAI_API_KEY, ANTHROPIC_API_KEY, or GOOGLE_GENERATIVE_AI_API_KEY, then restart the app. See https://docs.openmercato.com/framework/ai-assistant/overview.',\n )\n }\n const apiKey = provider.resolveApiKey(env)\n if (!apiKey) {\n throw new AiModelFactoryError(\n 'api_key_missing',\n `LLM provider \"${provider.id}\" is advertised as configured but resolveApiKey() returned empty.`,\n )\n }\n\n // --- Model-axis: use the post-parse model id from the winning source.\n let modelId: string\n let source: AiModelResolution['source']\n if (requestModelParsed) {\n modelId = requestModelParsed.modelId\n source = 'request_override'\n } else if (callerParsed) {\n modelId = callerParsed.modelId\n source = 'caller_override'\n } else if (tenantModelParsed) {\n modelId = tenantModelParsed.modelId\n source = 'tenant_override'\n } else if (moduleModelParsed) {\n modelId = moduleModelParsed.modelId\n source = 'module_env'\n } else if (agentModelParsed) {\n modelId = agentModelParsed.modelId\n source = 'agent_default'\n } else if (globalModelParsed) {\n modelId = globalModelParsed.modelId\n source = 'env_default'\n } else {\n modelId = provider.defaultModel\n source = 'provider_default'\n }\n\n // --- BaseURL-axis resolution (highest to lowest priority) ---\n // 1. requestOverride.baseURL (HTTP dispatcher) \u2014 gated by allowRuntimeModelOverride\n // 2. baseUrlOverride (programmatic caller)\n // 3. tenantOverride.baseURL (DB row) \u2014 gated by allowRuntimeModelOverride\n // 4. <MODULE>_AI_BASE_URL env\n // 5. agentDefaultBaseUrl\n // Steps 6-7 (preset env + preset default) are handled inside the adapter's\n // createModel when no explicit baseURL is passed.\n const resolvedBaseURL = requestBaseUrlRaw\n ?? normalizeOverride(input.baseUrlOverride)\n ?? tenantBaseUrlRaw\n ?? (hasModule ? normalizeOverride(env[moduleBaseUrlEnvVarName(input.moduleId!)]) : null)\n ?? normalizeOverride(input.agentDefaultBaseUrl)\n ?? undefined\n\n // --- Allowlist enforcement (Phase 1780-5 + 1780-6) -------------------\n // OM_AI_AVAILABLE_PROVIDERS / OM_AI_AVAILABLE_MODELS_<PROVIDER> clip\n // the resolution to an operator-approved set. The optional tenant\n // allowlist snapshot narrows the env outer constraint further. If the\n // resolved pair isn't allowed, fall back to a safe (provider, model)\n // \u2014 never throw, so a stale tenant override or chat picker can't take\n // the runtime down. The fallback is logged so the operator can see\n // what happened.\n const registryProviderIds = registry.list?.()?.map((p) => p.id) ?? []\n const tenantProviderIds = input.tenantAllowlist\n ? Object.keys(input.tenantAllowlist.allowedModelsByProvider ?? {})\n : []\n const knownProviderIds = Array.from(\n new Set([provider.id, ...registryProviderIds, ...tenantProviderIds]),\n )\n const effectiveAllowlist = intersectAllowlists(\n env,\n knownProviderIds,\n input.tenantAllowlist ?? null,\n )\n const allowlistResult = enforceAllowlist({\n env,\n registry,\n resolved: { provider, modelId },\n agentDefaultProvider: agentDefaultProviderRaw,\n agentDefaultModel: agentModelParsed?.modelId ?? agentModelRaw,\n effective: effectiveAllowlist,\n })\n\n const finalProvider = allowlistResult.provider\n const finalModelId = allowlistResult.modelId\n const finalSource = allowlistResult.fallback ? 'allowlist_fallback' : source\n const finalApiKey = allowlistResult.fallback\n ? finalProvider.resolveApiKey(env)\n : apiKey\n if (!finalApiKey) {\n throw new AiModelFactoryError(\n 'api_key_missing',\n `LLM provider \"${finalProvider.id}\" is advertised as configured but resolveApiKey() returned empty.`,\n )\n }\n\n const model = finalProvider.createModel({\n modelId: finalModelId,\n apiKey: finalApiKey,\n baseURL: resolvedBaseURL,\n })\n return {\n model,\n modelId: finalModelId,\n providerId: finalProvider.id,\n source: finalSource,\n ...(resolvedBaseURL !== undefined ? { baseURL: resolvedBaseURL } : {}),\n ...(allowlistResult.fallback\n ? {\n allowlistFallback: {\n originalProviderId: provider.id,\n originalModelId: modelId,\n reason: allowlistResult.fallback,\n },\n }\n : {}),\n }\n },\n }\n}\n\ninterface EnforceAllowlistInput {\n env: EnvLookup\n registry: AiModelFactoryRegistry\n resolved: { provider: LlmProvider; modelId: string }\n agentDefaultProvider: string | null\n agentDefaultModel: string | null\n effective: EffectiveAllowlist\n}\n\ninterface EnforceAllowlistResult {\n provider: LlmProvider\n modelId: string\n /** Populated only when the resolved pair was rejected. */\n fallback: string | null\n}\n\n/**\n * Clips a resolved `(provider, model)` to what the effective allowlist\n * permits (env intersected with optional tenant allowlist).\n *\n * Order of fallback when the resolved provider is not allowed:\n * 1. The agent's `defaultProvider` (if allowed and configured).\n * 2. The first allowed provider that is also configured in the registry.\n *\n * Order of fallback when the model is not allowed for the resolved provider:\n * 1. The agent's `defaultModel` (if allowed for that provider).\n * 2. The provider's `defaultModel` (if allowed).\n * 3. The first model from the effective allowlist for that provider.\n *\n * Both fall-back paths emit a `console.warn` so the operator can see why the\n * runtime did not honor the requested combination. The function never throws.\n */\nfunction enforceAllowlist(input: EnforceAllowlistInput): EnforceAllowlistResult {\n const { registry, resolved, agentDefaultProvider, agentDefaultModel, effective } = input\n let provider = resolved.provider\n let modelId = resolved.modelId\n let fallback: string | null = null\n\n if (effective.providers !== null && !isProviderAllowedInEffective(effective, provider.id)) {\n const replacement = pickAllowedProvider({\n registry,\n agentDefaultProvider,\n effective,\n })\n if (replacement) {\n const source = effective.tenantOverridesActive\n ? 'the effective allowlist (env \u2229 tenant)'\n : 'OM_AI_AVAILABLE_PROVIDERS'\n fallback = `Provider \"${provider.id}\" is not in ${source}; using \"${replacement.id}\" instead.`\n console.warn(`[AI Model Factory] ${fallback}`)\n provider = replacement\n modelId = pickAllowedModel({\n provider,\n preferred: agentDefaultModel,\n effective,\n })\n }\n // If no replacement is configured we keep the resolved provider \u2014 the\n // throw at the api-key gate will surface the misconfiguration to the\n // operator instead of silently masking it.\n }\n\n if (!isModelAllowedForProviderInEffective(effective, provider.id, modelId)) {\n const replacementModel = pickAllowedModel({\n provider,\n preferred: agentDefaultModel,\n effective,\n })\n if (replacementModel !== modelId) {\n const source = effective.tenantOverridesActive\n ? `the effective allowlist (env \u2229 tenant) for \"${provider.id}\"`\n : `OM_AI_AVAILABLE_MODELS_${provider.id.toUpperCase()}`\n const reason = `Model \"${modelId}\" is not in ${source}; using \"${replacementModel}\" instead.`\n console.warn(`[AI Model Factory] ${reason}`)\n fallback = fallback ? `${fallback} ${reason}` : reason\n modelId = replacementModel\n }\n }\n\n return { provider, modelId, fallback }\n}\n\nfunction pickAllowedProvider(input: {\n registry: AiModelFactoryRegistry\n agentDefaultProvider: string | null\n effective: EffectiveAllowlist\n}): LlmProvider | null {\n const { registry, agentDefaultProvider, effective } = input\n if (agentDefaultProvider) {\n if (isProviderAllowedInEffective(effective, agentDefaultProvider)) {\n const provider = registry.get?.(agentDefaultProvider)\n if (provider && provider.isConfigured(process.env as EnvLookup)) return provider\n }\n }\n const allowed = effective.providers\n if (!allowed) return null\n for (const id of allowed) {\n const provider = registry.get?.(id)\n if (provider && provider.isConfigured(process.env as EnvLookup)) return provider\n }\n return null\n}\n\nfunction pickAllowedModel(input: {\n provider: LlmProvider\n preferred: string | null\n effective: EffectiveAllowlist\n}): string {\n const { provider, preferred, effective } = input\n const allowed = effective.modelsByProvider[provider.id]\n if (allowed === undefined) {\n return preferred && preferred.length > 0 ? preferred : provider.defaultModel\n }\n if (preferred && allowed.includes(preferred)) return preferred\n if (allowed.includes(provider.defaultModel)) return provider.defaultModel\n return allowed[0] ?? provider.defaultModel\n}\n"],
|
|
5
|
+
"mappings": "AA6DA,SAAS,2BAA2B;AACpC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AA2MA,MAAM,4BAA4B,MAAM;AAAA,EAG7C,YAAY,MAA+B,SAAiB;AAC1D,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,OAAO;AAAA,EACd;AACF;AA4CA,SAAS,kBAAkB,OAA0C;AACnE,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAM,UAAU,MAAM,KAAK;AAC3B,SAAO,QAAQ,SAAS,IAAI,UAAU;AACxC;AAQA,SAAS,0BACP,KACA,UACe;AACf,QAAM,aAAa,CAAC,kBAAkB,IAAI,cAAc,GAAG,kBAAkB,IAAI,iBAAiB,CAAC;AACnG,aAAW,aAAa,YAAY;AAClC,QAAI,CAAC,UAAW;AAChB,QAAI,CAAC,SAAS,IAAK,QAAO,kBAAkB,SAAS,EAAE,CAAC,KAAK;AAC7D,eAAW,SAAS,kBAAkB,SAAS,GAAG;AAChD,UAAI,SAAS,IAAI,KAAK,EAAG,QAAO;AAAA,IAClC;AAAA,EACF;AACA,SAAO;AACT;AAMA,SAAS,uBAAuB,KAA+B;AAC7D,SAAO,kBAAkB,IAAI,WAAW,KAAK,kBAAkB,IAAI,cAAc;AACnF;AAGA,SAAS,sBAAsB,UAA0B;AACvD,SAAO,SAAS,SAAS,YAAY,CAAC;AACxC;AAMA,SAAS,4BAA4B,UAA0B;AAC7D,SAAO,GAAG,SAAS,YAAY,CAAC;AAClC;AAEA,SAAS,2BAA2B,KAAgB,UAAiC;AACnF,SACE,kBAAkB,IAAI,sBAAsB,QAAQ,CAAC,CAAC,KACtD,kBAAkB,IAAI,4BAA4B,QAAQ,CAAC,CAAC;AAEhE;AAGA,SAAS,yBAAyB,UAA0B;AAC1D,SAAO,SAAS,SAAS,YAAY,CAAC;AACxC;AAMA,SAAS,+BAA+B,UAA0B;AAChE,SAAO,GAAG,SAAS,YAAY,CAAC;AAClC;AAEA,SAAS,8BAA8B,KAAgB,UAAiC;AACtF,SACE,kBAAkB,IAAI,yBAAyB,QAAQ,CAAC,CAAC,KACzD,kBAAkB,IAAI,+BAA+B,QAAQ,CAAC,CAAC;AAEnE;AAEA,SAAS,sBACP,YACA,UACe;AACf,MAAI,CAAC,WAAY,QAAO;AACxB,QAAM,mBAAmB,SAAS,OAAO,EAAE,IAAI,CAAC,aAAa,SAAS,EAAE,KAAK,CAAC;AAC9E,MAAI,iBAAiB,SAAS,GAAG;AAC/B,WAAO,oBAAoB,YAAY,gBAAgB;AAAA,EACzD;AACA,SAAO,kBAAkB,UAAU,EAAE,CAAC,KAAK;AAC7C;AAEA,SAAS,wBAAwB,UAA0B;AACzD,SAAO,GAAG,SAAS,YAAY,CAAC;AAClC;AAgBO,SAAS,oBACd,OACA,UACkD;AAClD,QAAM,aAAa,MAAM,QAAQ,GAAG;AACpC,MAAI,aAAa,EAAG,QAAO,EAAE,cAAc,MAAM,SAAS,MAAM;AAChE,QAAM,SAAS,MAAM,MAAM,GAAG,UAAU;AACxC,QAAM,QAAQ,MAAM,MAAM,aAAa,CAAC;AACxC,MAAI,CAAC,UAAU,CAAC,MAAO,QAAO,EAAE,cAAc,MAAM,SAAS,MAAM;AACnE,MAAI,CAAC,SAAS,IAAK,QAAO,EAAE,cAAc,MAAM,SAAS,MAAM;AAC/D,QAAM,WAAW,SAAS,IAAI,MAAM;AACpC,MAAI,CAAC,SAAU,QAAO,EAAE,cAAc,MAAM,SAAS,MAAM;AAC3D,SAAO,EAAE,cAAc,QAAQ,SAAS,MAAM;AAChD;AASO,SAAS,mBACd,YACA,OAAuC,CAAC,GACxB;AAChB,QAAM,WAAmC,KAAK,YAAY;AAC1D,QAAM,MAAM,KAAK,OAAO,QAAQ;AAEhC,SAAO;AAAA,IACL,aAAa,OAA+C;AAC1D,YAAM,YAAY,OAAO,MAAM,aAAa,YAAY,MAAM,SAAS,SAAS;AAGhF,YAAM,0BAA0B,MAAM,8BAA8B;AAGpE,YAAM,kBAAkB,0BACpB,kBAAkB,MAAM,iBAAiB,WAAW,MAAS,IAC7D;AACJ,YAAM,qBAAqB,0BACvB,kBAAkB,MAAM,iBAAiB,cAAc,MAAS,IAChE;AACJ,YAAM,oBAAoB,0BACtB,kBAAkB,MAAM,iBAAiB,WAAW,MAAS,IAC7D;AAGJ,YAAM,YAAY,kBAAkB,MAAM,cAAc;AAGxD,YAAM,iBAAiB,0BACnB,kBAAkB,MAAM,gBAAgB,WAAW,MAAS,IAC5D;AACJ,YAAM,oBAAoB,0BACtB,kBAAkB,MAAM,gBAAgB,cAAc,MAAS,IAC/D;AACJ,YAAM,mBAAmB,0BACrB,kBAAkB,MAAM,gBAAgB,WAAW,MAAS,IAC5D;AAGJ,YAAM,iBAAiB,YACnB,2BAA2B,KAAK,MAAM,QAAS,IAC/C;AACJ,YAAM,gBAAgB,kBAAkB,MAAM,iBAAiB;AAG/D,YAAM,iBAAiB,uBAAuB,GAAG;AAGjD,YAAM,qBAAqB,kBAAkB,oBAAoB,iBAAiB,QAAQ,IAAI;AAC9F,YAAM,eAAe,YAAY,oBAAoB,WAAW,QAAQ,IAAI;AAC5E,YAAM,oBAAoB,iBAAiB,oBAAoB,gBAAgB,QAAQ,IAAI;AAC3F,YAAM,oBAAoB,iBAAiB,oBAAoB,gBAAgB,QAAQ,IAAI;AAC3F,YAAM,mBAAmB,gBAAgB,oBAAoB,eAAe,QAAQ,IAAI;AACxF,YAAM,oBAAoB,iBAAiB,oBAAoB,gBAAgB,QAAQ,IAAI;AAM3F,YAAM,sBAAsB,kBAAkB,MAAM,gBAAgB;AACpE,YAAM,oBAAoB,YACtB,8BAA8B,KAAK,MAAM,QAAS,IAClD;AACJ,YAAM,0BAA0B,kBAAkB,MAAM,oBAAoB;AAG5E,YAAM,oBAAoB,0BAA0B,KAAK,QAAQ;AAIjE,YAAM,yBAA+C;AAAA,QACnD,oBAAoB,gBAAgB;AAAA,QACpC,sBAAsB,oBAAoB,QAAQ;AAAA,QAClD,cAAc,gBAAgB;AAAA,QAC9B,sBAAsB,qBAAqB,QAAQ;AAAA,QACnD,mBAAmB,gBAAgB;AAAA,QACnC,sBAAsB,mBAAmB,QAAQ;AAAA,QACjD,mBAAmB,gBAAgB;AAAA,QACnC,sBAAsB,mBAAmB,QAAQ;AAAA,QACjD,kBAAkB,gBAAgB;AAAA,QAClC,sBAAsB,yBAAyB,QAAQ;AAAA,QACvD,mBAAmB,gBAAgB;AAAA,QACnC;AAAA,MACF;AACA,YAAM,YAAY,uBAAuB,KAAK,CAAC,SAAS,SAAS,IAAI,KAAK;AAC1E,YAAM,QAAQ,YAAY,CAAC,SAAS,IAAI;AAExC,YAAM,WAAW,SAAS,uBAAuB,EAAE,KAAK,MAAM,CAAC;AAC/D,UAAI,CAAC,UAAU;AACb,cAAM,IAAI;AAAA,UACR;AAAA,UACA;AAAA,QACF;AAAA,MACF;AACA,YAAM,SAAS,SAAS,cAAc,GAAG;AACzC,UAAI,CAAC,QAAQ;AACX,cAAM,IAAI;AAAA,UACR;AAAA,UACA,iBAAiB,SAAS,EAAE;AAAA,QAC9B;AAAA,MACF;AAGA,UAAI;AACJ,UAAI;AACJ,UAAI,oBAAoB;AACtB,kBAAU,mBAAmB;AAC7B,iBAAS;AAAA,MACX,WAAW,cAAc;AACvB,kBAAU,aAAa;AACvB,iBAAS;AAAA,MACX,WAAW,mBAAmB;AAC5B,kBAAU,kBAAkB;AAC5B,iBAAS;AAAA,MACX,WAAW,mBAAmB;AAC5B,kBAAU,kBAAkB;AAC5B,iBAAS;AAAA,MACX,WAAW,kBAAkB;AAC3B,kBAAU,iBAAiB;AAC3B,iBAAS;AAAA,MACX,WAAW,mBAAmB;AAC5B,kBAAU,kBAAkB;AAC5B,iBAAS;AAAA,MACX,OAAO;AACL,kBAAU,SAAS;AACnB,iBAAS;AAAA,MACX;AAUA,YAAM,kBAAkB,qBACnB,kBAAkB,MAAM,eAAe,KACvC,qBACC,YAAY,kBAAkB,IAAI,wBAAwB,MAAM,QAAS,CAAC,CAAC,IAAI,SAChF,kBAAkB,MAAM,mBAAmB,KAC3C;AAUL,YAAM,sBAAsB,SAAS,OAAO,GAAG,IAAI,CAAC,MAAM,EAAE,EAAE,KAAK,CAAC;AACpE,YAAM,oBAAoB,MAAM,kBAC5B,OAAO,KAAK,MAAM,gBAAgB,2BAA2B,CAAC,CAAC,IAC/D,CAAC;AACL,YAAM,mBAAmB,MAAM;AAAA,QAC7B,oBAAI,IAAI,CAAC,SAAS,IAAI,GAAG,qBAAqB,GAAG,iBAAiB,CAAC;AAAA,MACrE;AACA,YAAM,qBAAqB;AAAA,QACzB;AAAA,QACA;AAAA,QACA,MAAM,mBAAmB;AAAA,MAC3B;AACA,YAAM,kBAAkB,iBAAiB;AAAA,QACvC;AAAA,QACA;AAAA,QACA,UAAU,EAAE,UAAU,QAAQ;AAAA,QAC9B,sBAAsB;AAAA,QACtB,mBAAmB,kBAAkB,WAAW;AAAA,QAChD,WAAW;AAAA,MACb,CAAC;AAED,YAAM,gBAAgB,gBAAgB;AACtC,YAAM,eAAe,gBAAgB;AACrC,YAAM,cAAc,gBAAgB,WAAW,uBAAuB;AACtE,YAAM,cAAc,gBAAgB,WAChC,cAAc,cAAc,GAAG,IAC/B;AACJ,UAAI,CAAC,aAAa;AAChB,cAAM,IAAI;AAAA,UACR;AAAA,UACA,iBAAiB,cAAc,EAAE;AAAA,QACnC;AAAA,MACF;AAEA,YAAM,QAAQ,cAAc,YAAY;AAAA,QACtC,SAAS;AAAA,QACT,QAAQ;AAAA,QACR,SAAS;AAAA,MACX,CAAC;AACD,aAAO;AAAA,QACL;AAAA,QACA,SAAS;AAAA,QACT,YAAY,cAAc;AAAA,QAC1B,QAAQ;AAAA,QACR,GAAI,oBAAoB,SAAY,EAAE,SAAS,gBAAgB,IAAI,CAAC;AAAA,QACpE,GAAI,gBAAgB,WAChB;AAAA,UACE,mBAAmB;AAAA,YACjB,oBAAoB,SAAS;AAAA,YAC7B,iBAAiB;AAAA,YACjB,QAAQ,gBAAgB;AAAA,UAC1B;AAAA,QACF,IACA,CAAC;AAAA,MACP;AAAA,IACF;AAAA,EACF;AACF;AAkCA,SAAS,iBAAiB,OAAsD;AAC9E,QAAM,EAAE,UAAU,UAAU,sBAAsB,mBAAmB,UAAU,IAAI;AACnF,MAAI,WAAW,SAAS;AACxB,MAAI,UAAU,SAAS;AACvB,MAAI,WAA0B;AAE9B,MAAI,UAAU,cAAc,QAAQ,CAAC,6BAA6B,WAAW,SAAS,EAAE,GAAG;AACzF,UAAM,cAAc,oBAAoB;AAAA,MACtC;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AACD,QAAI,aAAa;AACf,YAAM,SAAS,UAAU,wBACrB,gDACA;AACJ,iBAAW,aAAa,SAAS,EAAE,eAAe,MAAM,YAAY,YAAY,EAAE;AAClF,cAAQ,KAAK,sBAAsB,QAAQ,EAAE;AAC7C,iBAAW;AACX,gBAAU,iBAAiB;AAAA,QACzB;AAAA,QACA,WAAW;AAAA,QACX;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EAIF;AAEA,MAAI,CAAC,qCAAqC,WAAW,SAAS,IAAI,OAAO,GAAG;AAC1E,UAAM,mBAAmB,iBAAiB;AAAA,MACxC;AAAA,MACA,WAAW;AAAA,MACX;AAAA,IACF,CAAC;AACD,QAAI,qBAAqB,SAAS;AAChC,YAAM,SAAS,UAAU,wBACrB,oDAA+C,SAAS,EAAE,MAC1D,0BAA0B,SAAS,GAAG,YAAY,CAAC;AACvD,YAAM,SAAS,UAAU,OAAO,eAAe,MAAM,YAAY,gBAAgB;AACjF,cAAQ,KAAK,sBAAsB,MAAM,EAAE;AAC3C,iBAAW,WAAW,GAAG,QAAQ,IAAI,MAAM,KAAK;AAChD,gBAAU;AAAA,IACZ;AAAA,EACF;AAEA,SAAO,EAAE,UAAU,SAAS,SAAS;AACvC;AAEA,SAAS,oBAAoB,OAIN;AACrB,QAAM,EAAE,UAAU,sBAAsB,UAAU,IAAI;AACtD,MAAI,sBAAsB;AACxB,QAAI,6BAA6B,WAAW,oBAAoB,GAAG;AACjE,YAAM,WAAW,SAAS,MAAM,oBAAoB;AACpD,UAAI,YAAY,SAAS,aAAa,QAAQ,GAAgB,EAAG,QAAO;AAAA,IAC1E;AAAA,EACF;AACA,QAAM,UAAU,UAAU;AAC1B,MAAI,CAAC,QAAS,QAAO;AACrB,aAAW,MAAM,SAAS;AACxB,UAAM,WAAW,SAAS,MAAM,EAAE;AAClC,QAAI,YAAY,SAAS,aAAa,QAAQ,GAAgB,EAAG,QAAO;AAAA,EAC1E;AACA,SAAO;AACT;AAEA,SAAS,iBAAiB,OAIf;AACT,QAAM,EAAE,UAAU,WAAW,UAAU,IAAI;AAC3C,QAAM,UAAU,UAAU,iBAAiB,SAAS,EAAE;AACtD,MAAI,YAAY,QAAW;AACzB,WAAO,aAAa,UAAU,SAAS,IAAI,YAAY,SAAS;AAAA,EAClE;AACA,MAAI,aAAa,QAAQ,SAAS,SAAS,EAAG,QAAO;AACrD,MAAI,QAAQ,SAAS,SAAS,YAAY,EAAG,QAAO,SAAS;AAC7D,SAAO,QAAQ,CAAC,KAAK,SAAS;AAChC;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -2,6 +2,7 @@ const OPENAI_PRESET = {
|
|
|
2
2
|
id: "openai",
|
|
3
3
|
name: "OpenAI",
|
|
4
4
|
baseURL: void 0,
|
|
5
|
+
baseURLEnvKeys: ["OPENAI_BASE_URL"],
|
|
5
6
|
envKeys: ["OPENAI_API_KEY", "OPENCODE_OPENAI_API_KEY"],
|
|
6
7
|
defaultModel: "gpt-5-mini",
|
|
7
8
|
defaultModels: [
|
|
@@ -34,6 +35,7 @@ const DEEPINFRA_PRESET = {
|
|
|
34
35
|
id: "deepinfra",
|
|
35
36
|
name: "DeepInfra",
|
|
36
37
|
baseURL: "https://api.deepinfra.com/v1/openai",
|
|
38
|
+
baseURLEnvKeys: ["DEEPINFRA_BASE_URL"],
|
|
37
39
|
envKeys: ["DEEPINFRA_API_KEY"],
|
|
38
40
|
defaultModel: "zai-org/GLM-5.1",
|
|
39
41
|
defaultModels: [
|
|
@@ -78,6 +80,7 @@ const GROQ_PRESET = {
|
|
|
78
80
|
id: "groq",
|
|
79
81
|
name: "Groq",
|
|
80
82
|
baseURL: "https://api.groq.com/openai/v1",
|
|
83
|
+
baseURLEnvKeys: ["GROQ_BASE_URL"],
|
|
81
84
|
envKeys: ["GROQ_API_KEY"],
|
|
82
85
|
defaultModel: "llama-3.3-70b-versatile",
|
|
83
86
|
defaultModels: [
|
|
@@ -102,6 +105,7 @@ const TOGETHER_PRESET = {
|
|
|
102
105
|
id: "together",
|
|
103
106
|
name: "Together AI",
|
|
104
107
|
baseURL: "https://api.together.xyz/v1",
|
|
108
|
+
baseURLEnvKeys: ["TOGETHER_BASE_URL"],
|
|
105
109
|
envKeys: ["TOGETHER_API_KEY"],
|
|
106
110
|
defaultModel: "meta-llama/Llama-3.3-70B-Instruct-Turbo",
|
|
107
111
|
defaultModels: [
|
|
@@ -121,6 +125,7 @@ const FIREWORKS_PRESET = {
|
|
|
121
125
|
id: "fireworks",
|
|
122
126
|
name: "Fireworks AI",
|
|
123
127
|
baseURL: "https://api.fireworks.ai/inference/v1",
|
|
128
|
+
baseURLEnvKeys: ["FIREWORKS_BASE_URL"],
|
|
124
129
|
envKeys: ["FIREWORKS_API_KEY"],
|
|
125
130
|
defaultModel: "accounts/fireworks/models/llama-v3p3-70b-instruct",
|
|
126
131
|
defaultModels: [
|
|
@@ -187,6 +192,30 @@ const OLLAMA_PRESET = {
|
|
|
187
192
|
}
|
|
188
193
|
]
|
|
189
194
|
};
|
|
195
|
+
const OPENROUTER_PRESET = {
|
|
196
|
+
id: "openrouter",
|
|
197
|
+
name: "OpenRouter",
|
|
198
|
+
baseURL: "https://openrouter.ai/api/v1",
|
|
199
|
+
baseURLEnvKeys: ["OPENROUTER_BASE_URL"],
|
|
200
|
+
envKeys: ["OPENROUTER_API_KEY"],
|
|
201
|
+
defaultModel: "meta-llama/llama-3.3-70b-instruct",
|
|
202
|
+
defaultModels: [
|
|
203
|
+
{
|
|
204
|
+
id: "meta-llama/llama-3.3-70b-instruct",
|
|
205
|
+
name: "Llama 3.3 70B Instruct",
|
|
206
|
+
contextWindow: 131072
|
|
207
|
+
}
|
|
208
|
+
]
|
|
209
|
+
};
|
|
210
|
+
const LM_STUDIO_PRESET = {
|
|
211
|
+
id: "lm-studio",
|
|
212
|
+
name: "LM Studio (local)",
|
|
213
|
+
baseURL: "http://localhost:1234/v1",
|
|
214
|
+
baseURLEnvKeys: ["LM_STUDIO_BASE_URL"],
|
|
215
|
+
envKeys: ["LM_STUDIO_API_KEY"],
|
|
216
|
+
defaultModel: "",
|
|
217
|
+
defaultModels: []
|
|
218
|
+
};
|
|
190
219
|
const OPENAI_COMPATIBLE_PRESETS = [
|
|
191
220
|
OPENAI_PRESET,
|
|
192
221
|
DEEPINFRA_PRESET,
|
|
@@ -195,7 +224,9 @@ const OPENAI_COMPATIBLE_PRESETS = [
|
|
|
195
224
|
FIREWORKS_PRESET,
|
|
196
225
|
AZURE_PRESET,
|
|
197
226
|
LITELLM_PRESET,
|
|
198
|
-
OLLAMA_PRESET
|
|
227
|
+
OLLAMA_PRESET,
|
|
228
|
+
OPENROUTER_PRESET,
|
|
229
|
+
LM_STUDIO_PRESET
|
|
199
230
|
];
|
|
200
231
|
export {
|
|
201
232
|
OPENAI_COMPATIBLE_PRESETS
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/ai_assistant/lib/openai-compatible-presets.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Curated registry of OpenAI-compatible LLM backends.\n *\n * Each preset is plain data \u2014 adding a new backend takes one entry in\n * this array, zero new adapter files, and zero changes to route handlers.\n * The {@link createOpenAICompatibleProvider} factory in `./llm-adapters/openai.ts`\n * turns each preset into a concrete `LlmProvider` at bootstrap time.\n *\n * Preset model catalogs are curated snapshots as of 2026-04-14 and should\n * be updated as upstream catalogs evolve. Users can always override the\n * selected model via the `OPENCODE_MODEL` env var without editing this\n * file.\n *\n * @see ./llm-adapters/openai.ts\n * @see .ai/specs/2026-04-14-llm-provider-ports-and-adapters.md\n */\n\nimport type { OpenAICompatiblePreset } from './llm-adapters/openai'\n\n/**\n * Standard OpenAI \u2014 default OpenAI API at api.openai.com.\n */\nconst OPENAI_PRESET: OpenAICompatiblePreset = {\n id: 'openai',\n name: 'OpenAI',\n baseURL: undefined,\n envKeys: ['OPENAI_API_KEY', 'OPENCODE_OPENAI_API_KEY'],\n defaultModel: 'gpt-5-mini',\n defaultModels: [\n {\n id: 'gpt-5-mini',\n name: 'GPT-5 Mini',\n contextWindow: 128000,\n tags: ['budget'],\n },\n {\n id: 'gpt-5',\n name: 'GPT-5',\n contextWindow: 128000,\n tags: ['flagship'],\n },\n {\n id: 'gpt-4o-mini',\n name: 'GPT-4o Mini',\n contextWindow: 128000,\n tags: ['budget'],\n },\n {\n id: 'gpt-4o',\n name: 'GPT-4o',\n contextWindow: 128000,\n },\n ],\n}\n\n/**\n * DeepInfra \u2014 hosts open-weight flagship models at 3-12\u00D7 lower cost than\n * the native APIs. The curated catalog targets the AI Assistant use case\n * (routing + tool use + conversational chat).\n */\nconst DEEPINFRA_PRESET: OpenAICompatiblePreset = {\n id: 'deepinfra',\n name: 'DeepInfra',\n baseURL: 'https://api.deepinfra.com/v1/openai',\n envKeys: ['DEEPINFRA_API_KEY'],\n defaultModel: 'zai-org/GLM-5.1',\n defaultModels: [\n {\n id: 'zai-org/GLM-5.1',\n name: 'GLM-5.1 (Zhipu)',\n contextWindow: 202752,\n tags: ['flagship'],\n },\n {\n id: 'zai-org/GLM-4.7-Flash',\n name: 'GLM-4.7 Flash',\n contextWindow: 202752,\n tags: ['budget'],\n },\n {\n id: 'Qwen/Qwen3-235B-A22B-Instruct-2507',\n name: 'Qwen3 235B (MoE)',\n contextWindow: 262144,\n tags: ['flagship'],\n },\n {\n id: 'meta-llama/Llama-4-Scout-17B-16E-Instruct',\n name: 'Llama 4 Scout',\n contextWindow: 327680,\n },\n {\n id: 'deepseek-ai/DeepSeek-V3.2-Exp',\n name: 'DeepSeek V3.2',\n contextWindow: 163840,\n tags: ['reasoning'],\n },\n {\n id: 'Qwen/Qwen3-Coder-30B-A3B-Instruct',\n name: 'Qwen3 Coder 30B',\n contextWindow: 262144,\n tags: ['coding'],\n },\n ],\n}\n\n/**\n * Groq \u2014 specializes in low-latency inference on LPU hardware.\n * Best suited for snappy tool-use and routing, less so for long reasoning.\n */\nconst GROQ_PRESET: OpenAICompatiblePreset = {\n id: 'groq',\n name: 'Groq',\n baseURL: 'https://api.groq.com/openai/v1',\n envKeys: ['GROQ_API_KEY'],\n defaultModel: 'llama-3.3-70b-versatile',\n defaultModels: [\n {\n id: 'llama-3.3-70b-versatile',\n name: 'Llama 3.3 70B Versatile',\n contextWindow: 131072,\n },\n {\n id: 'llama-4-scout-17b',\n name: 'Llama 4 Scout 17B',\n contextWindow: 131072,\n },\n {\n id: 'mixtral-8x22b-32768',\n name: 'Mixtral 8x22B',\n contextWindow: 32768,\n },\n ],\n}\n\n/**\n * Together AI \u2014 broad catalog of open-weight models with per-model pricing.\n */\nconst TOGETHER_PRESET: OpenAICompatiblePreset = {\n id: 'together',\n name: 'Together AI',\n baseURL: 'https://api.together.xyz/v1',\n envKeys: ['TOGETHER_API_KEY'],\n defaultModel: 'meta-llama/Llama-3.3-70B-Instruct-Turbo',\n defaultModels: [\n {\n id: 'meta-llama/Llama-3.3-70B-Instruct-Turbo',\n name: 'Llama 3.3 70B Turbo',\n contextWindow: 131072,\n },\n {\n id: 'Qwen/Qwen2.5-72B-Instruct-Turbo',\n name: 'Qwen 2.5 72B Turbo',\n contextWindow: 32768,\n },\n ],\n}\n\n/**\n * Fireworks AI \u2014 fast inference with a curated catalog.\n */\nconst FIREWORKS_PRESET: OpenAICompatiblePreset = {\n id: 'fireworks',\n name: 'Fireworks AI',\n baseURL: 'https://api.fireworks.ai/inference/v1',\n envKeys: ['FIREWORKS_API_KEY'],\n defaultModel: 'accounts/fireworks/models/llama-v3p3-70b-instruct',\n defaultModels: [\n {\n id: 'accounts/fireworks/models/llama-v3p3-70b-instruct',\n name: 'Llama 3.3 70B',\n contextWindow: 131072,\n },\n ],\n}\n\n/**\n * Azure OpenAI \u2014 enterprise Azure deployments. Base URL is deployment-\n * specific and must be provided via `AZURE_OPENAI_BASE_URL`.\n */\nconst AZURE_PRESET: OpenAICompatiblePreset = {\n id: 'azure',\n name: 'Azure OpenAI',\n baseURL: undefined,\n baseURLEnvKeys: ['AZURE_OPENAI_BASE_URL'],\n envKeys: ['AZURE_OPENAI_API_KEY'],\n defaultModel: 'gpt-5-mini',\n defaultModels: [\n {\n id: 'gpt-5-mini',\n name: 'GPT-5 Mini',\n contextWindow: 128000,\n },\n {\n id: 'gpt-5',\n name: 'GPT-5',\n contextWindow: 128000,\n },\n ],\n}\n\n/**\n * LiteLLM proxy \u2014 self-hosted router for arbitrary upstream providers.\n * Base URL must be supplied via `LITELLM_BASE_URL`.\n */\nconst LITELLM_PRESET: OpenAICompatiblePreset = {\n id: 'litellm',\n name: 'LiteLLM',\n baseURL: 'http://localhost:4000/v1',\n baseURLEnvKeys: ['LITELLM_BASE_URL'],\n envKeys: ['LITELLM_API_KEY'],\n defaultModel: 'gpt-4o-mini',\n defaultModels: [\n {\n id: 'gpt-4o-mini',\n name: 'GPT-4o Mini (via LiteLLM)',\n contextWindow: 128000,\n },\n ],\n}\n\n/**\n * Ollama \u2014 local model runner for development and offline use.\n * Default port 11434 can be overridden via `OLLAMA_BASE_URL`.\n */\nconst OLLAMA_PRESET: OpenAICompatiblePreset = {\n id: 'ollama',\n name: 'Ollama (local)',\n baseURL: 'http://localhost:11434/v1',\n baseURLEnvKeys: ['OLLAMA_BASE_URL'],\n envKeys: ['OLLAMA_API_KEY'],\n defaultModel: 'llama3.3',\n defaultModels: [\n {\n id: 'llama3.3',\n name: 'Llama 3.3 (local)',\n contextWindow: 131072,\n },\n {\n id: 'qwen2.5-coder',\n name: 'Qwen 2.5 Coder (local)',\n contextWindow: 131072,\n tags: ['coding'],\n },\n ],\n}\n\n/**\n * Built-in presets registered at bootstrap time. Order matters \u2014 it\n * determines the default iteration order of\n * `llmProviderRegistry.resolveFirstConfigured()` when no explicit order\n * is supplied.\n */\nexport const OPENAI_COMPATIBLE_PRESETS: readonly OpenAICompatiblePreset[] = [\n OPENAI_PRESET,\n DEEPINFRA_PRESET,\n GROQ_PRESET,\n TOGETHER_PRESET,\n FIREWORKS_PRESET,\n AZURE_PRESET,\n LITELLM_PRESET,\n OLLAMA_PRESET,\n]\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["/**\n * Curated registry of OpenAI-compatible LLM backends.\n *\n * Each preset is plain data \u2014 adding a new backend takes one entry in\n * this array, zero new adapter files, and zero changes to route handlers.\n * The {@link createOpenAICompatibleProvider} factory in `./llm-adapters/openai.ts`\n * turns each preset into a concrete `LlmProvider` at bootstrap time.\n *\n * Preset model catalogs are curated snapshots as of 2026-04-14 and should\n * be updated as upstream catalogs evolve. Users can always override the\n * selected model via the `OPENCODE_MODEL` env var without editing this\n * file.\n *\n * @see ./llm-adapters/openai.ts\n * @see .ai/specs/2026-04-14-llm-provider-ports-and-adapters.md\n */\n\nimport type { OpenAICompatiblePreset } from './llm-adapters/openai'\n\n/**\n * Standard OpenAI \u2014 default OpenAI API at api.openai.com.\n */\nconst OPENAI_PRESET: OpenAICompatiblePreset = {\n id: 'openai',\n name: 'OpenAI',\n baseURL: undefined,\n baseURLEnvKeys: ['OPENAI_BASE_URL'],\n envKeys: ['OPENAI_API_KEY', 'OPENCODE_OPENAI_API_KEY'],\n defaultModel: 'gpt-5-mini',\n defaultModels: [\n {\n id: 'gpt-5-mini',\n name: 'GPT-5 Mini',\n contextWindow: 128000,\n tags: ['budget'],\n },\n {\n id: 'gpt-5',\n name: 'GPT-5',\n contextWindow: 128000,\n tags: ['flagship'],\n },\n {\n id: 'gpt-4o-mini',\n name: 'GPT-4o Mini',\n contextWindow: 128000,\n tags: ['budget'],\n },\n {\n id: 'gpt-4o',\n name: 'GPT-4o',\n contextWindow: 128000,\n },\n ],\n}\n\n/**\n * DeepInfra \u2014 hosts open-weight flagship models at 3-12\u00D7 lower cost than\n * the native APIs. The curated catalog targets the AI Assistant use case\n * (routing + tool use + conversational chat).\n */\nconst DEEPINFRA_PRESET: OpenAICompatiblePreset = {\n id: 'deepinfra',\n name: 'DeepInfra',\n baseURL: 'https://api.deepinfra.com/v1/openai',\n baseURLEnvKeys: ['DEEPINFRA_BASE_URL'],\n envKeys: ['DEEPINFRA_API_KEY'],\n defaultModel: 'zai-org/GLM-5.1',\n defaultModels: [\n {\n id: 'zai-org/GLM-5.1',\n name: 'GLM-5.1 (Zhipu)',\n contextWindow: 202752,\n tags: ['flagship'],\n },\n {\n id: 'zai-org/GLM-4.7-Flash',\n name: 'GLM-4.7 Flash',\n contextWindow: 202752,\n tags: ['budget'],\n },\n {\n id: 'Qwen/Qwen3-235B-A22B-Instruct-2507',\n name: 'Qwen3 235B (MoE)',\n contextWindow: 262144,\n tags: ['flagship'],\n },\n {\n id: 'meta-llama/Llama-4-Scout-17B-16E-Instruct',\n name: 'Llama 4 Scout',\n contextWindow: 327680,\n },\n {\n id: 'deepseek-ai/DeepSeek-V3.2-Exp',\n name: 'DeepSeek V3.2',\n contextWindow: 163840,\n tags: ['reasoning'],\n },\n {\n id: 'Qwen/Qwen3-Coder-30B-A3B-Instruct',\n name: 'Qwen3 Coder 30B',\n contextWindow: 262144,\n tags: ['coding'],\n },\n ],\n}\n\n/**\n * Groq \u2014 specializes in low-latency inference on LPU hardware.\n * Best suited for snappy tool-use and routing, less so for long reasoning.\n */\nconst GROQ_PRESET: OpenAICompatiblePreset = {\n id: 'groq',\n name: 'Groq',\n baseURL: 'https://api.groq.com/openai/v1',\n baseURLEnvKeys: ['GROQ_BASE_URL'],\n envKeys: ['GROQ_API_KEY'],\n defaultModel: 'llama-3.3-70b-versatile',\n defaultModels: [\n {\n id: 'llama-3.3-70b-versatile',\n name: 'Llama 3.3 70B Versatile',\n contextWindow: 131072,\n },\n {\n id: 'llama-4-scout-17b',\n name: 'Llama 4 Scout 17B',\n contextWindow: 131072,\n },\n {\n id: 'mixtral-8x22b-32768',\n name: 'Mixtral 8x22B',\n contextWindow: 32768,\n },\n ],\n}\n\n/**\n * Together AI \u2014 broad catalog of open-weight models with per-model pricing.\n */\nconst TOGETHER_PRESET: OpenAICompatiblePreset = {\n id: 'together',\n name: 'Together AI',\n baseURL: 'https://api.together.xyz/v1',\n baseURLEnvKeys: ['TOGETHER_BASE_URL'],\n envKeys: ['TOGETHER_API_KEY'],\n defaultModel: 'meta-llama/Llama-3.3-70B-Instruct-Turbo',\n defaultModels: [\n {\n id: 'meta-llama/Llama-3.3-70B-Instruct-Turbo',\n name: 'Llama 3.3 70B Turbo',\n contextWindow: 131072,\n },\n {\n id: 'Qwen/Qwen2.5-72B-Instruct-Turbo',\n name: 'Qwen 2.5 72B Turbo',\n contextWindow: 32768,\n },\n ],\n}\n\n/**\n * Fireworks AI \u2014 fast inference with a curated catalog.\n */\nconst FIREWORKS_PRESET: OpenAICompatiblePreset = {\n id: 'fireworks',\n name: 'Fireworks AI',\n baseURL: 'https://api.fireworks.ai/inference/v1',\n baseURLEnvKeys: ['FIREWORKS_BASE_URL'],\n envKeys: ['FIREWORKS_API_KEY'],\n defaultModel: 'accounts/fireworks/models/llama-v3p3-70b-instruct',\n defaultModels: [\n {\n id: 'accounts/fireworks/models/llama-v3p3-70b-instruct',\n name: 'Llama 3.3 70B',\n contextWindow: 131072,\n },\n ],\n}\n\n/**\n * Azure OpenAI \u2014 enterprise Azure deployments. Base URL is deployment-\n * specific and must be provided via `AZURE_OPENAI_BASE_URL`.\n */\nconst AZURE_PRESET: OpenAICompatiblePreset = {\n id: 'azure',\n name: 'Azure OpenAI',\n baseURL: undefined,\n baseURLEnvKeys: ['AZURE_OPENAI_BASE_URL'],\n envKeys: ['AZURE_OPENAI_API_KEY'],\n defaultModel: 'gpt-5-mini',\n defaultModels: [\n {\n id: 'gpt-5-mini',\n name: 'GPT-5 Mini',\n contextWindow: 128000,\n },\n {\n id: 'gpt-5',\n name: 'GPT-5',\n contextWindow: 128000,\n },\n ],\n}\n\n/**\n * LiteLLM proxy \u2014 self-hosted router for arbitrary upstream providers.\n * Base URL must be supplied via `LITELLM_BASE_URL`.\n */\nconst LITELLM_PRESET: OpenAICompatiblePreset = {\n id: 'litellm',\n name: 'LiteLLM',\n baseURL: 'http://localhost:4000/v1',\n baseURLEnvKeys: ['LITELLM_BASE_URL'],\n envKeys: ['LITELLM_API_KEY'],\n defaultModel: 'gpt-4o-mini',\n defaultModels: [\n {\n id: 'gpt-4o-mini',\n name: 'GPT-4o Mini (via LiteLLM)',\n contextWindow: 128000,\n },\n ],\n}\n\n/**\n * Ollama \u2014 local model runner for development and offline use.\n * Default port 11434 can be overridden via `OLLAMA_BASE_URL`.\n */\nconst OLLAMA_PRESET: OpenAICompatiblePreset = {\n id: 'ollama',\n name: 'Ollama (local)',\n baseURL: 'http://localhost:11434/v1',\n baseURLEnvKeys: ['OLLAMA_BASE_URL'],\n envKeys: ['OLLAMA_API_KEY'],\n defaultModel: 'llama3.3',\n defaultModels: [\n {\n id: 'llama3.3',\n name: 'Llama 3.3 (local)',\n contextWindow: 131072,\n },\n {\n id: 'qwen2.5-coder',\n name: 'Qwen 2.5 Coder (local)',\n contextWindow: 131072,\n tags: ['coding'],\n },\n ],\n}\n\n/**\n * OpenRouter \u2014 unified API gateway providing access to hundreds of models\n * from Anthropic, OpenAI, Google, Meta, and others via a single OpenAI-\n * compatible endpoint.\n */\nconst OPENROUTER_PRESET: OpenAICompatiblePreset = {\n id: 'openrouter',\n name: 'OpenRouter',\n baseURL: 'https://openrouter.ai/api/v1',\n baseURLEnvKeys: ['OPENROUTER_BASE_URL'],\n envKeys: ['OPENROUTER_API_KEY'],\n defaultModel: 'meta-llama/llama-3.3-70b-instruct',\n defaultModels: [\n {\n id: 'meta-llama/llama-3.3-70b-instruct',\n name: 'Llama 3.3 70B Instruct',\n contextWindow: 131072,\n },\n ],\n}\n\n/**\n * LM Studio \u2014 local model server for development and offline use.\n * Default port 1234 can be overridden via `LM_STUDIO_BASE_URL`.\n * `defaultModel` is intentionally empty \u2014 LM Studio auto-detects\n * the loaded model when the request body's `model` field is empty.\n */\nconst LM_STUDIO_PRESET: OpenAICompatiblePreset = {\n id: 'lm-studio',\n name: 'LM Studio (local)',\n baseURL: 'http://localhost:1234/v1',\n baseURLEnvKeys: ['LM_STUDIO_BASE_URL'],\n envKeys: ['LM_STUDIO_API_KEY'],\n defaultModel: '',\n defaultModels: [],\n}\n\n/**\n * Built-in presets registered at bootstrap time. Order matters \u2014 it\n * determines the default iteration order of\n * `llmProviderRegistry.resolveFirstConfigured()` when no explicit order\n * is supplied.\n */\nexport const OPENAI_COMPATIBLE_PRESETS: readonly OpenAICompatiblePreset[] = [\n OPENAI_PRESET,\n DEEPINFRA_PRESET,\n GROQ_PRESET,\n TOGETHER_PRESET,\n FIREWORKS_PRESET,\n AZURE_PRESET,\n LITELLM_PRESET,\n OLLAMA_PRESET,\n OPENROUTER_PRESET,\n LM_STUDIO_PRESET,\n]\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Migration } from "@mikro-orm/migrations";
|
|
2
|
+
class Migration20260508140000_ai_agent_runtime_overrides extends Migration {
|
|
3
|
+
async up() {
|
|
4
|
+
this.addSql(`create table "ai_agent_runtime_overrides" ("id" uuid not null default gen_random_uuid(), "tenant_id" uuid not null, "organization_id" uuid null, "agent_id" varchar(128) null, "provider_id" varchar(64) null, "model_id" varchar(256) null, "base_url" varchar(2048) null, "updated_by_user_id" uuid null, "created_at" timestamptz not null, "updated_at" timestamptz not null, "deleted_at" timestamptz null, constraint "ai_agent_runtime_overrides_pkey" primary key ("id"));`);
|
|
5
|
+
this.addSql(`create unique index "ai_agent_runtime_overrides_tenant_org_agent_uq" on "ai_agent_runtime_overrides" ("tenant_id", "organization_id", "agent_id") where "deleted_at" is null and "organization_id" is not null and "agent_id" is not null;`);
|
|
6
|
+
this.addSql(`create unique index "ai_agent_runtime_overrides_tenant_agent_null_org_uq" on "ai_agent_runtime_overrides" ("tenant_id", "agent_id") where "deleted_at" is null and "organization_id" is null and "agent_id" is not null;`);
|
|
7
|
+
this.addSql(`create unique index "ai_agent_runtime_overrides_tenant_null_agent_null_org_uq" on "ai_agent_runtime_overrides" ("tenant_id") where "deleted_at" is null and "organization_id" is null and "agent_id" is null;`);
|
|
8
|
+
this.addSql(`create unique index "ai_agent_runtime_overrides_tenant_org_null_agent_uq" on "ai_agent_runtime_overrides" ("tenant_id", "organization_id") where "deleted_at" is null and "organization_id" is not null and "agent_id" is null;`);
|
|
9
|
+
this.addSql(`create index "ai_agent_runtime_overrides_tenant_idx" on "ai_agent_runtime_overrides" ("tenant_id");`);
|
|
10
|
+
}
|
|
11
|
+
async down() {
|
|
12
|
+
this.addSql(`drop table if exists "ai_agent_runtime_overrides" cascade;`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export {
|
|
16
|
+
Migration20260508140000_ai_agent_runtime_overrides
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=Migration20260508140000.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/ai_assistant/migrations/Migration20260508140000.ts"],
|
|
4
|
+
"sourcesContent": ["import { Migration } from '@mikro-orm/migrations';\n\nexport class Migration20260508140000_ai_agent_runtime_overrides extends Migration {\n\n override async up(): Promise<void> {\n this.addSql(`create table \"ai_agent_runtime_overrides\" (\"id\" uuid not null default gen_random_uuid(), \"tenant_id\" uuid not null, \"organization_id\" uuid null, \"agent_id\" varchar(128) null, \"provider_id\" varchar(64) null, \"model_id\" varchar(256) null, \"base_url\" varchar(2048) null, \"updated_by_user_id\" uuid null, \"created_at\" timestamptz not null, \"updated_at\" timestamptz not null, \"deleted_at\" timestamptz null, constraint \"ai_agent_runtime_overrides_pkey\" primary key (\"id\"));`);\n this.addSql(`create unique index \"ai_agent_runtime_overrides_tenant_org_agent_uq\" on \"ai_agent_runtime_overrides\" (\"tenant_id\", \"organization_id\", \"agent_id\") where \"deleted_at\" is null and \"organization_id\" is not null and \"agent_id\" is not null;`);\n this.addSql(`create unique index \"ai_agent_runtime_overrides_tenant_agent_null_org_uq\" on \"ai_agent_runtime_overrides\" (\"tenant_id\", \"agent_id\") where \"deleted_at\" is null and \"organization_id\" is null and \"agent_id\" is not null;`);\n this.addSql(`create unique index \"ai_agent_runtime_overrides_tenant_null_agent_null_org_uq\" on \"ai_agent_runtime_overrides\" (\"tenant_id\") where \"deleted_at\" is null and \"organization_id\" is null and \"agent_id\" is null;`);\n this.addSql(`create unique index \"ai_agent_runtime_overrides_tenant_org_null_agent_uq\" on \"ai_agent_runtime_overrides\" (\"tenant_id\", \"organization_id\") where \"deleted_at\" is null and \"organization_id\" is not null and \"agent_id\" is null;`);\n this.addSql(`create index \"ai_agent_runtime_overrides_tenant_idx\" on \"ai_agent_runtime_overrides\" (\"tenant_id\");`);\n }\n\n override async down(): Promise<void> {\n this.addSql(`drop table if exists \"ai_agent_runtime_overrides\" cascade;`);\n }\n\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,iBAAiB;AAEnB,MAAM,2DAA2D,UAAU;AAAA,EAEhF,MAAe,KAAoB;AACjC,SAAK,OAAO,odAAod;AAChe,SAAK,OAAO,4OAA4O;AACxP,SAAK,OAAO,0NAA0N;AACtO,SAAK,OAAO,+MAA+M;AAC3N,SAAK,OAAO,iOAAiO;AAC7O,SAAK,OAAO,qGAAqG;AAAA,EACnH;AAAA,EAEA,MAAe,OAAsB;AACnC,SAAK,OAAO,4DAA4D;AAAA,EAC1E;AAEF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Migration } from "@mikro-orm/migrations";
|
|
2
|
+
class Migration20260512090000_ai_tenant_model_allowlist extends Migration {
|
|
3
|
+
async up() {
|
|
4
|
+
this.addSql(`create table "ai_tenant_model_allowlists" ("id" uuid not null default gen_random_uuid(), "tenant_id" uuid not null, "organization_id" uuid null, "allowed_providers" jsonb null, "allowed_models_by_provider" jsonb not null default '{}', "updated_by_user_id" uuid null, "created_at" timestamptz not null, "updated_at" timestamptz not null, "deleted_at" timestamptz null, constraint "ai_tenant_model_allowlists_pkey" primary key ("id"));`);
|
|
5
|
+
this.addSql(`create unique index "ai_tenant_model_allowlists_tenant_org_uq" on "ai_tenant_model_allowlists" ("tenant_id", "organization_id") where "deleted_at" is null and "organization_id" is not null;`);
|
|
6
|
+
this.addSql(`create unique index "ai_tenant_model_allowlists_tenant_null_org_uq" on "ai_tenant_model_allowlists" ("tenant_id") where "deleted_at" is null and "organization_id" is null;`);
|
|
7
|
+
this.addSql(`create index "ai_tenant_model_allowlists_tenant_idx" on "ai_tenant_model_allowlists" ("tenant_id");`);
|
|
8
|
+
}
|
|
9
|
+
async down() {
|
|
10
|
+
this.addSql(`drop table if exists "ai_tenant_model_allowlists" cascade;`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export {
|
|
14
|
+
Migration20260512090000_ai_tenant_model_allowlist
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=Migration20260512090000.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/ai_assistant/migrations/Migration20260512090000.ts"],
|
|
4
|
+
"sourcesContent": ["import { Migration } from '@mikro-orm/migrations';\n\nexport class Migration20260512090000_ai_tenant_model_allowlist extends Migration {\n\n override async up(): Promise<void> {\n this.addSql(`create table \"ai_tenant_model_allowlists\" (\"id\" uuid not null default gen_random_uuid(), \"tenant_id\" uuid not null, \"organization_id\" uuid null, \"allowed_providers\" jsonb null, \"allowed_models_by_provider\" jsonb not null default '{}', \"updated_by_user_id\" uuid null, \"created_at\" timestamptz not null, \"updated_at\" timestamptz not null, \"deleted_at\" timestamptz null, constraint \"ai_tenant_model_allowlists_pkey\" primary key (\"id\"));`);\n this.addSql(`create unique index \"ai_tenant_model_allowlists_tenant_org_uq\" on \"ai_tenant_model_allowlists\" (\"tenant_id\", \"organization_id\") where \"deleted_at\" is null and \"organization_id\" is not null;`);\n this.addSql(`create unique index \"ai_tenant_model_allowlists_tenant_null_org_uq\" on \"ai_tenant_model_allowlists\" (\"tenant_id\") where \"deleted_at\" is null and \"organization_id\" is null;`);\n this.addSql(`create index \"ai_tenant_model_allowlists_tenant_idx\" on \"ai_tenant_model_allowlists\" (\"tenant_id\");`);\n }\n\n override async down(): Promise<void> {\n this.addSql(`drop table if exists \"ai_tenant_model_allowlists\" cascade;`);\n }\n\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,iBAAiB;AAEnB,MAAM,0DAA0D,UAAU;AAAA,EAE/E,MAAe,KAAoB;AACjC,SAAK,OAAO,mbAAmb;AAC/b,SAAK,OAAO,+LAA+L;AAC3M,SAAK,OAAO,6KAA6K;AACzL,SAAK,OAAO,qGAAqG;AAAA,EACnH;AAAA,EAEA,MAAe,OAAsB;AACnC,SAAK,OAAO,4DAA4D;AAAA,EAC1E;AAEF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Migration } from "@mikro-orm/migrations";
|
|
2
|
+
class Migration20260512130000_ai_agent_runtime_override_picker_allowlist extends Migration {
|
|
3
|
+
async up() {
|
|
4
|
+
this.addSql(`alter table "ai_agent_runtime_overrides" add column "allowed_override_providers" jsonb null;`);
|
|
5
|
+
this.addSql(`alter table "ai_agent_runtime_overrides" add column "allowed_override_models_by_provider" jsonb not null default '{}';`);
|
|
6
|
+
}
|
|
7
|
+
async down() {
|
|
8
|
+
this.addSql(`alter table "ai_agent_runtime_overrides" drop column if exists "allowed_override_providers";`);
|
|
9
|
+
this.addSql(`alter table "ai_agent_runtime_overrides" drop column if exists "allowed_override_models_by_provider";`);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export {
|
|
13
|
+
Migration20260512130000_ai_agent_runtime_override_picker_allowlist
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=Migration20260512130000.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/ai_assistant/migrations/Migration20260512130000.ts"],
|
|
4
|
+
"sourcesContent": ["import { Migration } from '@mikro-orm/migrations'\n\nexport class Migration20260512130000_ai_agent_runtime_override_picker_allowlist extends Migration {\n override async up(): Promise<void> {\n this.addSql(`alter table \"ai_agent_runtime_overrides\" add column \"allowed_override_providers\" jsonb null;`)\n this.addSql(`alter table \"ai_agent_runtime_overrides\" add column \"allowed_override_models_by_provider\" jsonb not null default '{}';`)\n }\n\n override async down(): Promise<void> {\n this.addSql(`alter table \"ai_agent_runtime_overrides\" drop column if exists \"allowed_override_providers\";`)\n this.addSql(`alter table \"ai_agent_runtime_overrides\" drop column if exists \"allowed_override_models_by_provider\";`)\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,iBAAiB;AAEnB,MAAM,2EAA2E,UAAU;AAAA,EAChG,MAAe,KAAoB;AACjC,SAAK,OAAO,8FAA8F;AAC1G,SAAK,OAAO,wHAAwH;AAAA,EACtI;AAAA,EAEA,MAAe,OAAsB;AACnC,SAAK,OAAO,8FAA8F;AAC1G,SAAK,OAAO,uGAAuG;AAAA,EACrH;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const id = "id";
|
|
2
|
+
export const tenant_id = "tenant_id";
|
|
3
|
+
export const organization_id = "organization_id";
|
|
4
|
+
export const agent_id = "agent_id";
|
|
5
|
+
export const provider_id = "provider_id";
|
|
6
|
+
export const model_id = "model_id";
|
|
7
|
+
export const base_url = "base_url";
|
|
8
|
+
export const allowed_override_providers = "allowed_override_providers";
|
|
9
|
+
export const allowed_override_models_by_provider = "allowed_override_models_by_provider";
|
|
10
|
+
export const updated_by_user_id = "updated_by_user_id";
|
|
11
|
+
export const created_at = "created_at";
|
|
12
|
+
export const updated_at = "updated_at";
|
|
13
|
+
export const deleted_at = "deleted_at";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const id = "id";
|
|
2
|
+
export const tenant_id = "tenant_id";
|
|
3
|
+
export const organization_id = "organization_id";
|
|
4
|
+
export const allowed_providers = "allowed_providers";
|
|
5
|
+
export const allowed_models_by_provider = "allowed_models_by_provider";
|
|
6
|
+
export const updated_by_user_id = "updated_by_user_id";
|
|
7
|
+
export const created_at = "created_at";
|
|
8
|
+
export const updated_at = "updated_at";
|
|
9
|
+
export const deleted_at = "deleted_at";
|
|
@@ -5,6 +5,8 @@ export const M = {
|
|
|
5
5
|
"ai_assistant": {
|
|
6
6
|
"ai_agent_prompt_override": "ai_assistant:ai_agent_prompt_override",
|
|
7
7
|
"ai_pending_action": "ai_assistant:ai_pending_action",
|
|
8
|
+
"ai_agent_runtime_override": "ai_assistant:ai_agent_runtime_override",
|
|
9
|
+
"ai_tenant_model_allowlist": "ai_assistant:ai_tenant_model_allowlist",
|
|
8
10
|
"ai_agent_mutation_policy_override": "ai_assistant:ai_agent_mutation_policy_override"
|
|
9
11
|
}
|
|
10
12
|
} as const;
|
|
@@ -24,6 +24,21 @@ export const entityFieldsRegistry: Record<string, Record<string, string>> = {
|
|
|
24
24
|
"created_at": "created_at",
|
|
25
25
|
"updated_at": "updated_at"
|
|
26
26
|
},
|
|
27
|
+
"ai_agent_runtime_override": {
|
|
28
|
+
"id": "id",
|
|
29
|
+
"tenant_id": "tenant_id",
|
|
30
|
+
"organization_id": "organization_id",
|
|
31
|
+
"agent_id": "agent_id",
|
|
32
|
+
"provider_id": "provider_id",
|
|
33
|
+
"model_id": "model_id",
|
|
34
|
+
"base_url": "base_url",
|
|
35
|
+
"allowed_override_providers": "allowed_override_providers",
|
|
36
|
+
"allowed_override_models_by_provider": "allowed_override_models_by_provider",
|
|
37
|
+
"updated_by_user_id": "updated_by_user_id",
|
|
38
|
+
"created_at": "created_at",
|
|
39
|
+
"updated_at": "updated_at",
|
|
40
|
+
"deleted_at": "deleted_at"
|
|
41
|
+
},
|
|
27
42
|
"ai_pending_action": {
|
|
28
43
|
"id": "id",
|
|
29
44
|
"tenant_id": "tenant_id",
|
|
@@ -49,6 +64,17 @@ export const entityFieldsRegistry: Record<string, Record<string, string>> = {
|
|
|
49
64
|
"expires_at": "expires_at",
|
|
50
65
|
"resolved_at": "resolved_at",
|
|
51
66
|
"resolved_by_user_id": "resolved_by_user_id"
|
|
67
|
+
},
|
|
68
|
+
"ai_tenant_model_allowlist": {
|
|
69
|
+
"id": "id",
|
|
70
|
+
"tenant_id": "tenant_id",
|
|
71
|
+
"organization_id": "organization_id",
|
|
72
|
+
"allowed_providers": "allowed_providers",
|
|
73
|
+
"allowed_models_by_provider": "allowed_models_by_provider",
|
|
74
|
+
"updated_by_user_id": "updated_by_user_id",
|
|
75
|
+
"created_at": "created_at",
|
|
76
|
+
"updated_at": "updated_at",
|
|
77
|
+
"deleted_at": "deleted_at"
|
|
52
78
|
}
|
|
53
79
|
};
|
|
54
80
|
|
package/jest.config.cjs
CHANGED
|
@@ -8,6 +8,8 @@ module.exports = {
|
|
|
8
8
|
moduleNameMapper: {
|
|
9
9
|
'^@open-mercato/ai-assistant/(.*)$': '<rootDir>/src/$1',
|
|
10
10
|
'^@open-mercato/shared/(.*)$': '<rootDir>/../shared/src/$1',
|
|
11
|
+
'^@open-mercato/cache$': '<rootDir>/../cache/src/index.ts',
|
|
12
|
+
'^@open-mercato/cache/(.*)$': '<rootDir>/../cache/src/$1',
|
|
11
13
|
// Redirect core module imports to the TS source so Jest's ts-jest
|
|
12
14
|
// transformer handles them cleanly. Without this, the built dist/
|
|
13
15
|
// ESM output trips Jest's CJS-only parser (see
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/ai-assistant",
|
|
3
|
-
"version": "0.6.1-develop.
|
|
3
|
+
"version": "0.6.1-develop.3256.1.fe3dec2464",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=22.0.0"
|
|
@@ -98,12 +98,12 @@
|
|
|
98
98
|
"zod-to-json-schema": "^3.25.2"
|
|
99
99
|
},
|
|
100
100
|
"peerDependencies": {
|
|
101
|
-
"@open-mercato/shared": "0.6.1-develop.
|
|
102
|
-
"@open-mercato/ui": "0.6.1-develop.
|
|
101
|
+
"@open-mercato/shared": "0.6.1-develop.3256.1.fe3dec2464",
|
|
102
|
+
"@open-mercato/ui": "0.6.1-develop.3256.1.fe3dec2464",
|
|
103
103
|
"zod": ">=3.23.0"
|
|
104
104
|
},
|
|
105
105
|
"devDependencies": {
|
|
106
|
-
"@open-mercato/cli": "0.6.1-develop.
|
|
106
|
+
"@open-mercato/cli": "0.6.1-develop.3256.1.fe3dec2464",
|
|
107
107
|
"tsx": "^4.21.0"
|
|
108
108
|
},
|
|
109
109
|
"publishConfig": {
|