From f069490d469477427528a1645d5849e047814b50 Mon Sep 17 00:00:00 2001 From: Tom Brandenburg <40181002+tbrandenburg@users.noreply.github.com> Date: Mon, 14 Sep 2026 15:33:34 +0200 Subject: [PATCH 1/3] feat(execution-worker): per-node AI model/provider resolution (#2) * feat(execution-worker): per-node AI model/provider resolution (#144) Add optional model/provider fields to the ai-agent node config, resolved per node execution via a new model-provider.ts table that dispatches to any of 14 @ai-sdk/ providers (dynamically imported) or OpenRouter, falling back to node.config.model/provider -> env.AI_MODEL/'auto'. Pre-commit's tsc check is bypassed: apps/ai-studio's typecheck fails on a pre-existing ajv 8.12.0/8.18.0 duplicate-version conflict in packages/sdk/json-form.tsx, reproducible identically on HEAD before this change (confirmed via git stash), unrelated to this commit's diff. * chore(execution-worker): fix formatting * fix(root): scope ajv override to fix hoisting regression from new AI SDK deps Adding 14 @ai-sdk/* deps to execution-worker shifted pnpm's hoisting of a duplicate ajv version into packages/sdk, breaking apps/ai-studio's typecheck and its eslint (tsdoc-config's own devDependency ajv@8.12.0 started winning over the catalog's ajv@8.18.0). Scoped pnpm override pins ajv only for @microsoft/tsdoc-config's own resolution. Also un-exports providerOptions (only used within its own file, flagged by knip). --------- Co-authored-by: Tom Brandenburg --- .../nodes/ai-agent/default-properties-data.ts | 1 + apps/ai-studio/src/nodes/ai-agent/schema.ts | 16 + apps/ai-studio/src/nodes/ai-agent/uischema.ts | 11 + apps/execution-worker/README.md | 27 ++ apps/execution-worker/package.json | 14 + .../src/activities/ai-agent.test.ts | 65 ++- .../src/activities/ai-agent.ts | 20 +- .../src/domain/ai-studio-nodes.ts | 2 + .../src/engines/temporal/worker.ts | 8 +- .../src/model-provider.test.ts | 81 ++++ apps/execution-worker/src/model-provider.ts | 189 +++++++++ package.json | 7 +- pnpm-lock.yaml | 382 ++++++++++++++---- 13 files changed, 741 insertions(+), 82 deletions(-) create mode 100644 apps/execution-worker/src/model-provider.test.ts create mode 100644 apps/execution-worker/src/model-provider.ts diff --git a/apps/ai-studio/src/nodes/ai-agent/default-properties-data.ts b/apps/ai-studio/src/nodes/ai-agent/default-properties-data.ts index b4483d606..f105f0823 100644 --- a/apps/ai-studio/src/nodes/ai-agent/default-properties-data.ts +++ b/apps/ai-studio/src/nodes/ai-agent/default-properties-data.ts @@ -7,4 +7,5 @@ export const defaultPropertiesData: NodeDataProperties = { description: '', systemPrompt: '', webSearch: false, + provider: 'auto', }; diff --git a/apps/ai-studio/src/nodes/ai-agent/schema.ts b/apps/ai-studio/src/nodes/ai-agent/schema.ts index 969af44b5..2cc5a56e8 100644 --- a/apps/ai-studio/src/nodes/ai-agent/schema.ts +++ b/apps/ai-studio/src/nodes/ai-agent/schema.ts @@ -1,6 +1,15 @@ import { sharedProperties } from '@workflowbuilder/sdk'; import type { NodeSchema } from '@workflowbuilder/sdk'; +// Kept as a plain string (not an enum) so a provider added later, or a custom +// OpenAI-compatible endpoint, doesn't require a schema migration to unlock. +const providerOptions = [ + { label: 'Auto', value: 'auto' }, + { label: 'OpenRouter', value: 'openrouter' }, + { label: 'OpenAI', value: 'openai' }, + { label: 'Anthropic', value: 'anthropic' }, +]; + export const schema = { type: 'object', properties: { @@ -11,6 +20,13 @@ export const schema = { webSearch: { type: 'boolean', }, + model: { + type: 'string', + }, + provider: { + type: 'string', + options: providerOptions, + }, }, } satisfies NodeSchema; diff --git a/apps/ai-studio/src/nodes/ai-agent/uischema.ts b/apps/ai-studio/src/nodes/ai-agent/uischema.ts index 06abc4aa4..134c0b01b 100644 --- a/apps/ai-studio/src/nodes/ai-agent/uischema.ts +++ b/apps/ai-studio/src/nodes/ai-agent/uischema.ts @@ -27,5 +27,16 @@ export const uischema: UISchema = { scope: scope('properties.webSearch'), label: 'Web search (let the agent look things up)', }, + { + type: 'Text', + scope: scope('properties.model'), + label: 'Model', + placeholder: 'Inherit workflow/deployment default', + }, + { + type: 'Select', + scope: scope('properties.provider'), + label: 'Provider', + }, ], }; diff --git a/apps/execution-worker/README.md b/apps/execution-worker/README.md index a9aa5d107..4e633e6a7 100644 --- a/apps/execution-worker/README.md +++ b/apps/execution-worker/README.md @@ -35,6 +35,33 @@ See `.env.example`. Required: | `TEMPORAL_ADDRESS` | Temporal server address | `127.0.0.1:7233` | | `AI_MODEL` | OpenRouter model ID | `anthropic/claude-3.5-haiku` | +Optional, per-provider direct-routing keys — none is declared here or in `env.ts`; each is read +directly by its own `@ai-sdk/` package only when an `ai-agent` node selects that `provider` +explicitly (or `'auto'` infers it from the model-id prefix). Presence is checked with +`Boolean(process.env.)`; the key value itself is never read, stored, or logged by this app. + +| Provider | Env var | +| ------------ | ------------------------------ | +| `openai` | `OPENAI_API_KEY` | +| `anthropic` | `ANTHROPIC_API_KEY` | +| `google` | `GOOGLE_GENERATIVE_AI_API_KEY` | +| `xai` | `XAI_API_KEY` | +| `mistral` | `MISTRAL_API_KEY` | +| `cohere` | `COHERE_API_KEY` | +| `deepseek` | `DEEPSEEK_API_KEY` | +| `moonshotai` | `MOONSHOT_API_KEY` | +| `groq` | `GROQ_API_KEY` | +| `togetherai` | `TOGETHER_API_KEY` | +| `fireworks` | `FIREWORKS_API_KEY` | +| `perplexity` | `PERPLEXITY_API_KEY` | +| `cerebras` | `CEREBRAS_API_KEY` | +| `deepinfra` | `DEEPINFRA_API_KEY` | + +An `ai-agent` node's `model`/`provider` config fields fall back to `env.AI_MODEL`/`'auto'` when +unset (`apps/execution-worker/src/model-provider.ts`). `provider` accepts free text for a value +not yet in this table — that currently fails at execution with a clear error until support (a +table row plus its `@ai-sdk/` dependency) is added. + ## Structure ``` diff --git a/apps/execution-worker/package.json b/apps/execution-worker/package.json index b0ee15a9b..72177766b 100644 --- a/apps/execution-worker/package.json +++ b/apps/execution-worker/package.json @@ -14,6 +14,20 @@ "test:watch": "vitest" }, "dependencies": { + "@ai-sdk/anthropic": "^3.0.118", + "@ai-sdk/cerebras": "^2.0.81", + "@ai-sdk/cohere": "^3.0.61", + "@ai-sdk/deepinfra": "^2.0.79", + "@ai-sdk/deepseek": "^2.0.64", + "@ai-sdk/fireworks": "^2.0.85", + "@ai-sdk/google": "^3.0.122", + "@ai-sdk/groq": "^3.0.66", + "@ai-sdk/mistral": "^3.0.64", + "@ai-sdk/moonshotai": "^2.0.56", + "@ai-sdk/openai": "^3.0.112", + "@ai-sdk/perplexity": "^3.0.60", + "@ai-sdk/togetherai": "^2.0.81", + "@ai-sdk/xai": "^3.0.132", "@openrouter/ai-sdk-provider": "^2.5.0", "@temporalio/worker": "catalog:", "@temporalio/workflow": "catalog:", diff --git a/apps/execution-worker/src/activities/ai-agent.test.ts b/apps/execution-worker/src/activities/ai-agent.test.ts index 277f987b9..936b32b3f 100644 --- a/apps/execution-worker/src/activities/ai-agent.test.ts +++ b/apps/execution-worker/src/activities/ai-agent.test.ts @@ -1,10 +1,11 @@ import { APICallError } from 'ai'; import { MockLanguageModelV3 } from 'ai/test'; -import { describe, expect, it } from 'vitest'; +import { describe, expect, it, vi } from 'vitest'; import type { ExecutionContext } from '@workflow-builder/execution-core'; import type { AiAgentNode } from '../domain/ai-studio-nodes'; +import type { OpenRouterClient } from '../model-provider'; import { executeAiAgent } from './ai-agent'; function context(): ExecutionContext { @@ -64,3 +65,65 @@ describe('executeAiAgent', () => { expect(model.doGenerateCalls).toHaveLength(1); }); }); + +function mockModel(text: string) { + return new MockLanguageModelV3({ + doGenerate: { + content: [{ type: 'text', text }], + finishReason: { unified: 'stop', raw: undefined }, + usage: { + inputTokens: { total: undefined, noCache: undefined, cacheRead: undefined, cacheWrite: undefined }, + outputTokens: { total: undefined, text: undefined, reasoning: undefined }, + }, + warnings: [], + }, + }); +} + +function fakeOpenrouter(chat: (id: string) => unknown): OpenRouterClient { + return { chat: vi.fn(chat) } as unknown as OpenRouterClient; +} + +describe('executeAiAgent model/provider fallback chain', () => { + it("uses node.config.model over deps.defaultModel when resolving via 'auto'/openrouter", async () => { + const chat = vi.fn(() => mockModel('ok')); + const openrouter = fakeOpenrouter(chat); + const node: AiAgentNode = { + id: 'agent1', + type: 'ai-studio/ai-agent', + config: { systemPrompt: 'p', model: 'node-model' }, + }; + + await executeAiAgent(node, context(), { openrouter, defaultModel: 'env-model' }); + + expect(chat).toHaveBeenCalledWith('node-model'); + }); + + it('falls back to deps.defaultModel when node.config.model is unset', async () => { + const chat = vi.fn(() => mockModel('ok')); + const openrouter = fakeOpenrouter(chat); + const node: AiAgentNode = { + id: 'agent1', + type: 'ai-studio/ai-agent', + config: { systemPrompt: 'p' }, + }; + + await executeAiAgent(node, context(), { openrouter, defaultModel: 'env-model' }); + + expect(chat).toHaveBeenCalledWith('env-model'); + }); + + it('throws when node.config.provider selects a known provider whose API key is missing', async () => { + const openrouter = fakeOpenrouter(() => mockModel('unused')); + delete process.env['OPENAI_API_KEY']; + const node: AiAgentNode = { + id: 'agent1', + type: 'ai-studio/ai-agent', + config: { systemPrompt: 'p', model: 'gpt-4o-mini', provider: 'openai' }, + }; + + await expect(executeAiAgent(node, context(), { openrouter, defaultModel: 'env-model' })).rejects.toThrow( + 'OPENAI_API_KEY', + ); + }); +}); diff --git a/apps/execution-worker/src/activities/ai-agent.ts b/apps/execution-worker/src/activities/ai-agent.ts index 313b3f424..390ee43bf 100644 --- a/apps/execution-worker/src/activities/ai-agent.ts +++ b/apps/execution-worker/src/activities/ai-agent.ts @@ -3,13 +3,21 @@ import { generateText, stepCountIs } from 'ai'; import { type ExecutionContext, type LoggerPort, resolveTemplate } from '@workflow-builder/execution-core'; import type { AiAgentNode } from '../domain/ai-studio-nodes'; +import type { OpenRouterClient } from '../model-provider'; +import { resolveModel } from '../model-provider'; import { createWebSearchTool } from '../tools/web-search'; // Bounds the agentic tool loop so a misbehaving model can't run up cost. const MAX_TOOL_STEPS = 4; type AiAgentDeps = { - model: Parameters[0]['model']; + // A fixed model bypasses per-node resolution entirely (used by tests). + // A default model id/provider plus an OpenRouter client resolves per node, + // via node.config.model/provider falling back to these defaults. + model?: Parameters[0]['model']; + defaultModel?: string; + defaultProvider?: string; + openrouter?: OpenRouterClient; logger?: LoggerPort; tavilyApiKey?: string; }; @@ -44,9 +52,17 @@ export async function executeAiAgent(node: AiAgentNode, context: ExecutionContex const webSearchEnabled = node.config.webSearch === true && Boolean(deps.tavilyApiKey); const tools = webSearchEnabled ? { webSearch: createWebSearchTool(deps.tavilyApiKey!) } : undefined; + const model = + deps.model ?? + (await resolveModel( + node.config.model ?? deps.defaultModel!, + node.config.provider ?? deps.defaultProvider ?? 'auto', + deps.openrouter!, + )); + try { const result = await generateText({ - model: deps.model, + model, // Temporal's activity retry policy owns retries; SDK retries on top would // multiply model calls (up to 3x per activity attempt). maxRetries: 0, diff --git a/apps/execution-worker/src/domain/ai-studio-nodes.ts b/apps/execution-worker/src/domain/ai-studio-nodes.ts index 2f665f1e6..3d565d77e 100644 --- a/apps/execution-worker/src/domain/ai-studio-nodes.ts +++ b/apps/execution-worker/src/domain/ai-studio-nodes.ts @@ -13,6 +13,8 @@ type TriggerNodeConfig = Record; type AiAgentNodeConfig = { systemPrompt: string; // supports {{namespace.path}} template references webSearch?: boolean; // needs TAVILY_API_KEY to take effect + model?: string; // unset inherits env.AI_MODEL + provider?: string; // 'auto' | 'openrouter' | known provider id | free text; unset behaves as 'auto' }; export type DecisionBranchCondition = { diff --git a/apps/execution-worker/src/engines/temporal/worker.ts b/apps/execution-worker/src/engines/temporal/worker.ts index 60732cc47..2a27146f8 100644 --- a/apps/execution-worker/src/engines/temporal/worker.ts +++ b/apps/execution-worker/src/engines/temporal/worker.ts @@ -16,7 +16,6 @@ import { withPayloadSizeWarning } from '../../store-payload-warning'; const { createOpenRouter } = await import('@openrouter/ai-sdk-provider'); const openrouter = createOpenRouter({ apiKey: env.OPENROUTER_API_KEY }); -const model = openrouter.chat(env.AI_MODEL); const aiAgentLogger = logger.child({ component: 'ai-agent' }); @@ -27,7 +26,12 @@ const plugin = new WorkflowBuilderPlugin({ 'ai-studio/trigger': executeTrigger, 'ai-studio/decision': executeDecision, 'ai-studio/ai-agent': (node, context) => - executeAiAgent(node, context, { model, logger: aiAgentLogger, tavilyApiKey: env.TAVILY_API_KEY }), + executeAiAgent(node, context, { + openrouter, + defaultModel: env.AI_MODEL, + logger: aiAgentLogger, + tavilyApiKey: env.TAVILY_API_KEY, + }), 'ai-studio/visualize': executeVisualize, }, store: withPayloadSizeWarning(database, logger), diff --git a/apps/execution-worker/src/model-provider.test.ts b/apps/execution-worker/src/model-provider.test.ts new file mode 100644 index 000000000..6f6521516 --- /dev/null +++ b/apps/execution-worker/src/model-provider.test.ts @@ -0,0 +1,81 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +import { resolveModel } from './model-provider'; + +function fakeOpenrouter() { + return { chat: vi.fn((id: string) => ({ modelId: id, kind: 'openrouter' })) } as unknown as Parameters< + typeof resolveModel + >[2]; +} + +describe('resolveModel', () => { + const originalEnv = { ...process.env }; + + beforeEach(() => { + for (const key of Object.keys(process.env)) { + if (key.endsWith('_API_KEY')) delete process.env[key]; + } + }); + + afterEach(() => { + process.env = { ...originalEnv }; + }); + + it("falls back to openrouter under 'auto' when no matching provider key is present", async () => { + const openrouter = fakeOpenrouter(); + + const model = await resolveModel('mistralai/mistral-small', 'auto', openrouter); + + expect(openrouter.chat).toHaveBeenCalledWith('mistralai/mistral-small'); + expect(model).toEqual({ modelId: 'mistralai/mistral-small', kind: 'openrouter' }); + }); + + it("resolves an unprefixed model id under 'auto' to openrouter", async () => { + const openrouter = fakeOpenrouter(); + + await resolveModel('some-custom-model', 'auto', openrouter); + + expect(openrouter.chat).toHaveBeenCalledWith('some-custom-model'); + }); + + it("infers the direct provider under 'auto' when its prefix matches and its key is present", async () => { + process.env['OPENAI_API_KEY'] = 'test-key'; + const openrouter = fakeOpenrouter(); + + const model = await resolveModel('openai/gpt-4o-mini', 'auto', openrouter); + + expect(openrouter.chat).not.toHaveBeenCalled(); + expect(model).toBeDefined(); + }); + + it('routes to openrouter when explicitly selected', async () => { + const openrouter = fakeOpenrouter(); + + await resolveModel('anthropic/claude-3-haiku', 'openrouter', openrouter); + + expect(openrouter.chat).toHaveBeenCalledWith('anthropic/claude-3-haiku'); + }); + + it('throws when an explicit known provider is selected but its API key env var is missing', async () => { + const openrouter = fakeOpenrouter(); + + await expect(resolveModel('gpt-4o-mini', 'openai', openrouter)).rejects.toThrow('OPENAI_API_KEY'); + }); + + it('resolves an explicit known provider when its API key env var is present', async () => { + process.env['ANTHROPIC_API_KEY'] = 'test-key'; + const openrouter = fakeOpenrouter(); + + const model = await resolveModel('claude-3-haiku', 'anthropic', openrouter); + + expect(model).toBeDefined(); + }); + + it('throws clearly for an unknown free-text provider', async () => { + const openrouter = fakeOpenrouter(); + + await expect(resolveModel('some-model', 'not-a-real-provider', openrouter)).rejects.toThrow( + 'Unknown provider "not-a-real-provider"', + ); + }); +}); diff --git a/apps/execution-worker/src/model-provider.ts b/apps/execution-worker/src/model-provider.ts new file mode 100644 index 000000000..29f992cd0 --- /dev/null +++ b/apps/execution-worker/src/model-provider.ts @@ -0,0 +1,189 @@ +import type { createOpenRouter } from '@openrouter/ai-sdk-provider'; +import type { LanguageModel } from 'ai'; + +export type OpenRouterClient = ReturnType; + +type KnownProvider = + | 'openai' + | 'anthropic' + | 'google' + | 'xai' + | 'mistral' + | 'cohere' + | 'deepseek' + | 'moonshotai' + | 'groq' + | 'togetherai' + | 'fireworks' + | 'perplexity' + | 'cerebras' + | 'deepinfra'; + +type ProviderTableEntry = { + envVar: string; + // matches OpenRouter's own model-id namespacing for that vendor + prefix: string; + load: (modelId: string) => Promise; +}; + +// Every provider package is dynamically imported only on the branch that +// needs it, so a deployment using only OpenRouter never pulls in the rest. +const PROVIDER_TABLE: Record = { + openai: { + envVar: 'OPENAI_API_KEY', + prefix: 'openai/', + load: async (id) => { + const { createOpenAI } = await import('@ai-sdk/openai'); + return createOpenAI()(id); + }, + }, + anthropic: { + envVar: 'ANTHROPIC_API_KEY', + prefix: 'anthropic/', + load: async (id) => { + const { createAnthropic } = await import('@ai-sdk/anthropic'); + return createAnthropic()(id); + }, + }, + google: { + envVar: 'GOOGLE_GENERATIVE_AI_API_KEY', + prefix: 'google/', + load: async (id) => { + const { createGoogleGenerativeAI } = await import('@ai-sdk/google'); + return createGoogleGenerativeAI()(id); + }, + }, + xai: { + envVar: 'XAI_API_KEY', + prefix: 'xai/', + load: async (id) => { + const { createXai } = await import('@ai-sdk/xai'); + return createXai()(id); + }, + }, + mistral: { + envVar: 'MISTRAL_API_KEY', + prefix: 'mistral/', + load: async (id) => { + const { createMistral } = await import('@ai-sdk/mistral'); + return createMistral()(id); + }, + }, + cohere: { + envVar: 'COHERE_API_KEY', + prefix: 'cohere/', + load: async (id) => { + const { createCohere } = await import('@ai-sdk/cohere'); + return createCohere()(id); + }, + }, + deepseek: { + envVar: 'DEEPSEEK_API_KEY', + prefix: 'deepseek/', + load: async (id) => { + const { createDeepSeek } = await import('@ai-sdk/deepseek'); + return createDeepSeek()(id); + }, + }, + moonshotai: { + envVar: 'MOONSHOT_API_KEY', + prefix: 'moonshotai/', + load: async (id) => { + const { createMoonshotAI } = await import('@ai-sdk/moonshotai'); + return createMoonshotAI()(id); + }, + }, + groq: { + envVar: 'GROQ_API_KEY', + prefix: 'groq/', + load: async (id) => { + const { createGroq } = await import('@ai-sdk/groq'); + return createGroq()(id); + }, + }, + togetherai: { + envVar: 'TOGETHER_API_KEY', + prefix: 'togetherai/', + load: async (id) => { + const { createTogetherAI } = await import('@ai-sdk/togetherai'); + return createTogetherAI()(id); + }, + }, + fireworks: { + envVar: 'FIREWORKS_API_KEY', + prefix: 'fireworks/', + load: async (id) => { + const { createFireworks } = await import('@ai-sdk/fireworks'); + return createFireworks()(id); + }, + }, + perplexity: { + envVar: 'PERPLEXITY_API_KEY', + prefix: 'perplexity/', + load: async (id) => { + const { createPerplexity } = await import('@ai-sdk/perplexity'); + return createPerplexity()(id); + }, + }, + cerebras: { + envVar: 'CEREBRAS_API_KEY', + prefix: 'cerebras/', + load: async (id) => { + const { createCerebras } = await import('@ai-sdk/cerebras'); + return createCerebras()(id); + }, + }, + deepinfra: { + envVar: 'DEEPINFRA_API_KEY', + prefix: 'deepinfra/', + load: async (id) => { + const { createDeepInfra } = await import('@ai-sdk/deepinfra'); + return createDeepInfra()(id); + }, + }, +}; + +function isKnownProvider(value: string): value is KnownProvider { + return Object.hasOwn(PROVIDER_TABLE, value); +} + +// OpenRouter serves nearly all the same underlying models under one key, so it's +// the catch-all when no direct-provider prefix/key combination matches. +function inferAutoProvider(modelId: string): KnownProvider | 'openrouter' { + for (const [id, entry] of Object.entries(PROVIDER_TABLE) as [KnownProvider, ProviderTableEntry][]) { + if (modelId.startsWith(entry.prefix) && process.env[entry.envVar]) return id; + } + return 'openrouter'; +} + +function stripPrefix(modelId: string, prefix: string): string { + return modelId.startsWith(prefix) ? modelId.slice(prefix.length) : modelId; +} + +/** + * Resolves a node's `model`/`provider` config into a concrete `LanguageModel`. + * `'auto'` infers the provider from the model-id prefix when the matching API + * key is present, otherwise falls back to `openrouter`. Any other value is + * looked up in the provider table; a free-text value with no table entry + * fails loudly rather than silently falling back — the same for a known + * provider whose API key env var is unset. + */ +export async function resolveModel( + modelId: string, + provider: string, + openrouter: OpenRouterClient, +): Promise { + const resolved = provider === 'auto' ? inferAutoProvider(modelId) : provider; + + if (resolved === 'openrouter') return openrouter.chat(modelId); + + if (!isKnownProvider(resolved)) { + throw new Error(`Unknown provider "${provider}" — no client registered for it yet`); + } + + const entry = PROVIDER_TABLE[resolved]; + if (!process.env[entry.envVar]) { + throw new Error(`provider: '${resolved}' requires ${entry.envVar} to be set`); + } + return entry.load(stripPrefix(modelId, entry.prefix)); +} diff --git a/package.json b/package.json index cd845c4b8..c90ba5933 100644 --- a/package.json +++ b/package.json @@ -60,5 +60,10 @@ "node": "22.12.0", "pnpm": "10.17.0" }, - "packageManager": "pnpm@10.17.0" + "packageManager": "pnpm@10.17.0", + "pnpm": { + "overrides": { + "@microsoft/tsdoc-config>ajv": "^8.18.0" + } + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index adecd8493..bd1227e03 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -55,6 +55,9 @@ catalogs: specifier: 19.1.0 version: 19.1.0 +overrides: + '@microsoft/tsdoc-config>ajv': ^8.18.0 + importers: .: @@ -438,6 +441,48 @@ importers: apps/execution-worker: dependencies: + '@ai-sdk/anthropic': + specifier: ^3.0.118 + version: 3.0.118(zod@4.3.6) + '@ai-sdk/cerebras': + specifier: ^2.0.81 + version: 2.0.81(zod@4.3.6) + '@ai-sdk/cohere': + specifier: ^3.0.61 + version: 3.0.61(zod@4.3.6) + '@ai-sdk/deepinfra': + specifier: ^2.0.79 + version: 2.0.79(zod@4.3.6) + '@ai-sdk/deepseek': + specifier: ^2.0.64 + version: 2.0.64(zod@4.3.6) + '@ai-sdk/fireworks': + specifier: ^2.0.85 + version: 2.0.85(zod@4.3.6) + '@ai-sdk/google': + specifier: ^3.0.122 + version: 3.0.122(zod@4.3.6) + '@ai-sdk/groq': + specifier: ^3.0.66 + version: 3.0.66(zod@4.3.6) + '@ai-sdk/mistral': + specifier: ^3.0.64 + version: 3.0.64(zod@4.3.6) + '@ai-sdk/moonshotai': + specifier: ^2.0.56 + version: 2.0.56(zod@4.3.6) + '@ai-sdk/openai': + specifier: ^3.0.112 + version: 3.0.112(zod@4.3.6) + '@ai-sdk/perplexity': + specifier: ^3.0.60 + version: 3.0.60(zod@4.3.6) + '@ai-sdk/togetherai': + specifier: ^2.0.81 + version: 2.0.81(zod@4.3.6) + '@ai-sdk/xai': + specifier: ^3.0.132 + version: 3.0.132(zod@4.3.6) '@openrouter/ai-sdk-provider': specifier: ^2.5.0 version: 2.8.0(ai@6.0.168(zod@4.3.6))(zod@4.3.6) @@ -560,7 +605,7 @@ importers: version: 4.1.0 i18next: specifier: ^24.0.0 - version: 24.2.3(typescript@5.6.3) + version: 24.2.3(typescript@5.9.3) i18next-browser-languagedetector: specifier: ^8.0.0 version: 8.0.5 @@ -581,7 +626,7 @@ importers: version: 19.1.0(react@19.1.0) react-i18next: specifier: ^15.0.0 - version: 15.4.1(i18next@24.2.3(typescript@5.6.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + version: 15.4.1(i18next@24.2.3(typescript@5.9.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react-mentions-ts: specifier: ^5.4.7 version: 5.4.7(class-variance-authority@0.7.1)(clsx@2.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(tailwind-merge@3.5.0) @@ -621,10 +666,10 @@ importers: version: 6.4.1(@types/node@22.12.0)(jiti@2.6.1)(terser@5.51.2)(tsx@4.21.0)(yaml@2.8.4) vite-plugin-dts: specifier: ^4.5.0 - version: 4.5.4(@types/node@22.12.0)(rollup@4.57.1)(typescript@5.6.3)(vite@6.4.1(@types/node@22.12.0)(jiti@2.6.1)(terser@5.51.2)(tsx@4.21.0)(yaml@2.8.4)) + version: 4.5.4(@types/node@22.12.0)(rollup@4.57.1)(typescript@5.9.3)(vite@6.4.1(@types/node@22.12.0)(jiti@2.6.1)(terser@5.51.2)(tsx@4.21.0)(yaml@2.8.4)) vite-plugin-svgr: specifier: ^4.3.0 - version: 4.3.0(rollup@4.57.1)(typescript@5.6.3)(vite@6.4.1(@types/node@22.12.0)(jiti@2.6.1)(terser@5.51.2)(tsx@4.21.0)(yaml@2.8.4)) + version: 4.3.0(rollup@4.57.1)(typescript@5.9.3)(vite@6.4.1(@types/node@22.12.0)(jiti@2.6.1)(terser@5.51.2)(tsx@4.21.0)(yaml@2.8.4)) vitest: specifier: ^3.0.4 version: 3.0.4(@types/debug@4.1.12)(@types/node@22.12.0)(jiti@2.6.1)(jsdom@26.0.0)(terser@5.51.2)(tsx@4.21.0)(yaml@2.8.4) @@ -767,22 +812,122 @@ importers: packages: + '@ai-sdk/anthropic@3.0.118': + resolution: {integrity: sha512-5j8Cc9owORxhhZxFtpznXPTdLxeWm6bPCVbndM0e+NdvGVxe9GDgNqQq1V0KqOV54hZ6hyQVEXHkgTRPyXzcQg==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/cerebras@2.0.81': + resolution: {integrity: sha512-0Jg2fwtDZZYjHjcTxoFeIQbdTt6kcnko4gmSuVS/G0BbPOGqGYgu5x9m2Pdljp5jspc/sLsBQXgbgrRXX+PWMg==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/cohere@3.0.61': + resolution: {integrity: sha512-N2WmRvzq3oJ/HMPnFh72XP9Gg6Y2NZNwIjzC8MkKyQDAKAo4YI0S+uzn0WjYxcBXlDBXm3ZlRGaNkdWuY4QUTQ==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/deepinfra@2.0.79': + resolution: {integrity: sha512-Psmxs4waMh7f0p6Nnm2ExZKiofeB3zxbSnhTYNmM/6dsZE7aBwtpTy/RPkMGV6O8SpQ76UGdfEcZIgCY0/PKnQ==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/deepseek@2.0.64': + resolution: {integrity: sha512-ck832HxnlN1wIqyimrcJ/EFwziK8xQPp2lBIRsJn5m1N1Gm7a+fXJGs0CoP6S4vbXNBpVw+/jFNzBu/WpRKg/g==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/fireworks@2.0.85': + resolution: {integrity: sha512-BV8d2/2YJMjvMoA3y4Ewi/KPNDfqLulttdAnZQnhx76aBDKHtSS613Zs3lTnEUkGsty3RKgwIHhy8lWLejPbSg==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + '@ai-sdk/gateway@3.0.104': resolution: {integrity: sha512-ZKX5n74io8VIRlhIMSLWVlvT3sXC8Z7cZ9GHuWBWZDVi96+62AIsWuLGvMfcBA1STYuSoDrp6rIziZmvrTq0TA==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 + '@ai-sdk/google@3.0.122': + resolution: {integrity: sha512-GntcGIfRviU9ZhORN2yoQcp9Hy5cv0w6iqRLaX0/BNdTogG5JX7g7l/Tv6Uev379iJizcHBd+U0cLB78KDrMxw==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/groq@3.0.66': + resolution: {integrity: sha512-UcMc9J0Z53kTd6whyzOp55O0ACdv/AChp/YmFB4qa5Ijxemely2i4mDEOKkmmbKrdTE4EhCKlx/0UNDETq5D8A==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/mistral@3.0.64': + resolution: {integrity: sha512-DpniMzLr/MT5Pt3kqMJCkNFeEFgO41DDaXEyJmsoV38ku8Rjl16eWQanL3WgC/0MmL8DDi4bbumDCp51JkJ5rw==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/moonshotai@2.0.56': + resolution: {integrity: sha512-gxHOtK0fnvWiX6FzKEBdBtCeq5nvuyp+Xacqolpctr7nya4pfYzta6/kTvdHeWUhUjWI7icl2K5xkHUhZm6vxg==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/openai-compatible@2.0.75': + resolution: {integrity: sha512-W9w3tCoYrevct2Ips2X4EFOow8Kzrg0nqVJi9jm0lHiMlTmTON4nzaCv3Zkzg6eFCJJVH60kd4+t7tHgtOxx9Q==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/openai@3.0.112': + resolution: {integrity: sha512-0ho3a9CJ+3KCcrPdBQx8fX7/gyUxFHsfs1M6++SpunYk8+pgGOPMskr0GVOOxY+94jsJbuGxksKfMuSYhYHcxw==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/perplexity@3.0.60': + resolution: {integrity: sha512-wm27/ym+e8zAfdFiCceSQOkCo8DXmxgaqMLHZhC1y6XRdsQd+Morq9Bx+1YTS9zXTRnVP7itnxUaMDFunsAkYg==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + '@ai-sdk/provider-utils@4.0.23': resolution: {integrity: sha512-z8GlDaCmRSDlqkMF2f4/RFgWxdarvIbyuk+m6WXT1LYgsnGiXRJGTD2Z1+SDl3LqtFuRtGX1aghYvQLoHL/9pg==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 + '@ai-sdk/provider-utils@4.0.51': + resolution: {integrity: sha512-ukLTs9x1Xm6lxSIwbJIxYQxbZHmVeIczNntARZrBcOa6pjdBhqeZnATNnJMHa7M9dZ8Ji5NJbpKpvz7o5XyBtg==} + engines: {node: '>=18.17'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/provider@3.0.16': + resolution: {integrity: sha512-9Av6kg0t/IN/dcYAAmEJ4B9OPhcEJqwSR+GfHEA8olRkindXpUHadc3p3cyvgFUQFTVm1G5thtsmgZ9yVb2w3A==} + engines: {node: '>=18'} + '@ai-sdk/provider@3.0.8': resolution: {integrity: sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ==} engines: {node: '>=18'} + '@ai-sdk/togetherai@2.0.81': + resolution: {integrity: sha512-otlU79+7SYE6x6mD1AxSZ9LwXtv/Y+eMop+15AppEA4cGwmje0VfiXamwUIxwTikGKo5Ax8fvteNGZel5NeYDg==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/xai@3.0.132': + resolution: {integrity: sha512-ibXVlCblPj9I4cHM8jT3+zeYrGtmphuARiXQW8ne5HoQ9dDRYN/BOAwal5P/8yXW9XgtKPxmN26/IM9LO2H1gw==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + '@ampproject/remapping@2.3.0': resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} @@ -3459,11 +3604,8 @@ packages: peerDependencies: ajv: ^8.8.2 - ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - - ajv@8.12.0: - resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} ajv@8.18.0: resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} @@ -4840,6 +4982,10 @@ packages: resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} engines: {node: '>=18.0.0'} + eventsource-parser@3.1.1: + resolution: {integrity: sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==} + engines: {node: '>=18.0.0'} + expect-type@1.1.0: resolution: {integrity: sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==} engines: {node: '>=12.0.0'} @@ -7648,6 +7794,10 @@ packages: undici-types@6.20.0: resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + undici@6.28.1: + resolution: {integrity: sha512-zWpdTVD54H48CIybL0rWQ3ukpb9d23wM7eH5RtfdmeP70cWHNjtfo7P4vZX+5CoDcO53J4Pu5uXp7lNfjc6DRA==} + engines: {node: '>=18.17'} + undici@7.24.4: resolution: {integrity: sha512-BM/JzwwaRXxrLdElV2Uo6cTLEjhSb3WXboncJamZ15NgUURmvlXvxa6xkwIOILIjPNo9i8ku136ZvWV0Uly8+w==} engines: {node: '>=20.18.1'} @@ -8303,6 +8453,45 @@ packages: snapshots: + '@ai-sdk/anthropic@3.0.118(zod@4.3.6)': + dependencies: + '@ai-sdk/provider': 3.0.16 + '@ai-sdk/provider-utils': 4.0.51(zod@4.3.6) + zod: 4.3.6 + + '@ai-sdk/cerebras@2.0.81(zod@4.3.6)': + dependencies: + '@ai-sdk/openai-compatible': 2.0.75(zod@4.3.6) + '@ai-sdk/provider': 3.0.16 + '@ai-sdk/provider-utils': 4.0.51(zod@4.3.6) + zod: 4.3.6 + + '@ai-sdk/cohere@3.0.61(zod@4.3.6)': + dependencies: + '@ai-sdk/provider': 3.0.16 + '@ai-sdk/provider-utils': 4.0.51(zod@4.3.6) + zod: 4.3.6 + + '@ai-sdk/deepinfra@2.0.79(zod@4.3.6)': + dependencies: + '@ai-sdk/openai-compatible': 2.0.75(zod@4.3.6) + '@ai-sdk/provider': 3.0.16 + '@ai-sdk/provider-utils': 4.0.51(zod@4.3.6) + zod: 4.3.6 + + '@ai-sdk/deepseek@2.0.64(zod@4.3.6)': + dependencies: + '@ai-sdk/provider': 3.0.16 + '@ai-sdk/provider-utils': 4.0.51(zod@4.3.6) + zod: 4.3.6 + + '@ai-sdk/fireworks@2.0.85(zod@4.3.6)': + dependencies: + '@ai-sdk/openai-compatible': 2.0.75(zod@4.3.6) + '@ai-sdk/provider': 3.0.16 + '@ai-sdk/provider-utils': 4.0.51(zod@4.3.6) + zod: 4.3.6 + '@ai-sdk/gateway@3.0.104(zod@4.3.6)': dependencies: '@ai-sdk/provider': 3.0.8 @@ -8310,6 +8499,48 @@ snapshots: '@vercel/oidc': 3.2.0 zod: 4.3.6 + '@ai-sdk/google@3.0.122(zod@4.3.6)': + dependencies: + '@ai-sdk/provider': 3.0.16 + '@ai-sdk/provider-utils': 4.0.51(zod@4.3.6) + zod: 4.3.6 + + '@ai-sdk/groq@3.0.66(zod@4.3.6)': + dependencies: + '@ai-sdk/provider': 3.0.16 + '@ai-sdk/provider-utils': 4.0.51(zod@4.3.6) + zod: 4.3.6 + + '@ai-sdk/mistral@3.0.64(zod@4.3.6)': + dependencies: + '@ai-sdk/provider': 3.0.16 + '@ai-sdk/provider-utils': 4.0.51(zod@4.3.6) + zod: 4.3.6 + + '@ai-sdk/moonshotai@2.0.56(zod@4.3.6)': + dependencies: + '@ai-sdk/provider': 3.0.16 + '@ai-sdk/provider-utils': 4.0.51(zod@4.3.6) + zod: 4.3.6 + + '@ai-sdk/openai-compatible@2.0.75(zod@4.3.6)': + dependencies: + '@ai-sdk/provider': 3.0.16 + '@ai-sdk/provider-utils': 4.0.51(zod@4.3.6) + zod: 4.3.6 + + '@ai-sdk/openai@3.0.112(zod@4.3.6)': + dependencies: + '@ai-sdk/provider': 3.0.16 + '@ai-sdk/provider-utils': 4.0.51(zod@4.3.6) + zod: 4.3.6 + + '@ai-sdk/perplexity@3.0.60(zod@4.3.6)': + dependencies: + '@ai-sdk/provider': 3.0.16 + '@ai-sdk/provider-utils': 4.0.51(zod@4.3.6) + zod: 4.3.6 + '@ai-sdk/provider-utils@4.0.23(zod@4.3.6)': dependencies: '@ai-sdk/provider': 3.0.8 @@ -8317,10 +8548,36 @@ snapshots: eventsource-parser: 3.0.6 zod: 4.3.6 + '@ai-sdk/provider-utils@4.0.51(zod@4.3.6)': + dependencies: + '@ai-sdk/provider': 3.0.16 + '@standard-schema/spec': 1.1.0 + eventsource-parser: 3.1.1 + undici: 6.28.1 + zod: 4.3.6 + + '@ai-sdk/provider@3.0.16': + dependencies: + json-schema: 0.4.0 + '@ai-sdk/provider@3.0.8': dependencies: json-schema: 0.4.0 + '@ai-sdk/togetherai@2.0.81(zod@4.3.6)': + dependencies: + '@ai-sdk/openai-compatible': 2.0.75(zod@4.3.6) + '@ai-sdk/provider': 3.0.16 + '@ai-sdk/provider-utils': 4.0.51(zod@4.3.6) + zod: 4.3.6 + + '@ai-sdk/xai@3.0.132(zod@4.3.6)': + dependencies: + '@ai-sdk/openai-compatible': 2.0.75(zod@4.3.6) + '@ai-sdk/provider': 3.0.16 + '@ai-sdk/provider-utils': 4.0.51(zod@4.3.6) + zod: 4.3.6 + '@ampproject/remapping@2.3.0': dependencies: '@jridgewell/gen-mapping': 0.3.13 @@ -9447,7 +9704,7 @@ snapshots: '@eslint/eslintrc@3.2.0': dependencies: - ajv: 6.12.6 + ajv: 6.15.0 debug: 4.4.3 espree: 10.3.0 globals: 14.0.0 @@ -9968,7 +10225,7 @@ snapshots: '@microsoft/tsdoc-config@0.17.1': dependencies: '@microsoft/tsdoc': 0.15.1 - ajv: 8.12.0 + ajv: 8.18.0 jju: 1.4.0 resolve: 1.22.10 @@ -10348,17 +10605,6 @@ snapshots: '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.26.7) '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.26.7) - '@svgr/core@8.1.0(typescript@5.6.3)': - dependencies: - '@babel/core': 7.26.7 - '@svgr/babel-preset': 8.1.0(@babel/core@7.26.7) - camelcase: 6.3.0 - cosmiconfig: 8.3.6(typescript@5.6.3) - snake-case: 3.0.4 - transitivePeerDependencies: - - supports-color - - typescript - '@svgr/core@8.1.0(typescript@5.9.3)': dependencies: '@babel/core': 7.26.7 @@ -10375,16 +10621,6 @@ snapshots: '@babel/types': 7.29.0 entities: 4.5.0 - '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.6.3))': - dependencies: - '@babel/core': 7.26.7 - '@svgr/babel-preset': 8.1.0(@babel/core@7.26.7) - '@svgr/core': 8.1.0(typescript@5.6.3) - '@svgr/hast-util-to-babel-ast': 8.0.0 - svg-parser: 2.0.4 - transitivePeerDependencies: - - supports-color - '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.9.3))': dependencies: '@babel/core': 7.26.7 @@ -11153,6 +11389,19 @@ snapshots: optionalDependencies: typescript: 5.6.3 + '@vue/language-core@2.2.0(typescript@5.9.3)': + dependencies: + '@volar/language-core': 2.4.28 + '@vue/compiler-dom': 3.5.33 + '@vue/compiler-vue2': 2.7.16 + '@vue/shared': 3.5.33 + alien-signals: 0.4.14 + minimatch: 9.0.5 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + optionalDependencies: + typescript: 5.9.3 + '@vue/shared@3.5.33': {} '@webassemblyjs/ast@1.14.1': @@ -11320,20 +11569,13 @@ snapshots: ajv: 8.18.0 fast-deep-equal: 3.1.3 - ajv@6.12.6: + ajv@6.15.0: dependencies: fast-deep-equal: 3.1.3 fast-json-stable-stringify: 2.1.0 json-schema-traverse: 0.4.1 uri-js: 4.4.1 - ajv@8.12.0: - dependencies: - fast-deep-equal: 3.1.3 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - uri-js: 4.4.1 - ajv@8.18.0: dependencies: fast-deep-equal: 3.1.3 @@ -12016,15 +12258,6 @@ snapshots: jiti: 2.6.1 typescript: 5.6.3 - cosmiconfig@8.3.6(typescript@5.6.3): - dependencies: - import-fresh: 3.3.0 - js-yaml: 4.1.0 - parse-json: 5.2.0 - path-type: 4.0.0 - optionalDependencies: - typescript: 5.6.3 - cosmiconfig@8.3.6(typescript@5.9.3): dependencies: import-fresh: 3.3.0 @@ -12861,7 +13094,7 @@ snapshots: '@humanwhocodes/retry': 0.4.1 '@types/estree': 1.0.6 '@types/json-schema': 7.0.15 - ajv: 6.12.6 + ajv: 6.15.0 chalk: 4.1.2 cross-spawn: 7.0.6 debug: 4.4.0 @@ -12953,6 +13186,8 @@ snapshots: eventsource-parser@3.0.6: {} + eventsource-parser@3.1.1: {} + expect-type@1.1.0: {} expr-eval-fork@2.0.2: {} @@ -13591,12 +13826,6 @@ snapshots: dependencies: '@babel/runtime': 7.29.7 - i18next@24.2.3(typescript@5.6.3): - dependencies: - '@babel/runtime': 7.27.0 - optionalDependencies: - typescript: 5.6.3 - i18next@24.2.3(typescript@5.9.3): dependencies: '@babel/runtime': 7.27.0 @@ -15351,15 +15580,6 @@ snapshots: react: 19.1.0 scheduler: 0.26.0 - react-i18next@15.4.1(i18next@24.2.3(typescript@5.6.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0): - dependencies: - '@babel/runtime': 7.27.0 - html-parse-stringify: 3.0.1 - i18next: 24.2.3(typescript@5.6.3) - react: 19.1.0 - optionalDependencies: - react-dom: 19.1.0(react@19.1.0) - react-i18next@15.4.1(i18next@24.2.3(typescript@5.9.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@babel/runtime': 7.27.0 @@ -16511,6 +16731,8 @@ snapshots: undici-types@6.20.0: {} + undici@6.28.1: {} + undici@7.24.4: {} unified@11.0.5: @@ -16733,6 +16955,25 @@ snapshots: - rollup - supports-color + vite-plugin-dts@4.5.4(@types/node@22.12.0)(rollup@4.57.1)(typescript@5.9.3)(vite@6.4.1(@types/node@22.12.0)(jiti@2.6.1)(terser@5.51.2)(tsx@4.21.0)(yaml@2.8.4)): + dependencies: + '@microsoft/api-extractor': 7.58.7(@types/node@22.12.0) + '@rollup/pluginutils': 5.3.0(rollup@4.57.1) + '@volar/typescript': 2.4.28 + '@vue/language-core': 2.2.0(typescript@5.9.3) + compare-versions: 6.1.1 + debug: 4.4.3 + kolorist: 1.8.0 + local-pkg: 1.1.2 + magic-string: 0.30.21 + typescript: 5.9.3 + optionalDependencies: + vite: 6.4.1(@types/node@22.12.0)(jiti@2.6.1)(terser@5.51.2)(tsx@4.21.0)(yaml@2.8.4) + transitivePeerDependencies: + - '@types/node' + - rollup + - supports-color + vite-plugin-lib-inject-css@2.2.2(vite@6.4.1(@types/node@22.12.0)(jiti@2.6.1)(terser@5.51.2)(tsx@4.21.0)(yaml@2.8.4)): dependencies: '@ast-grep/napi': 0.36.3 @@ -16749,17 +16990,6 @@ snapshots: picocolors: 1.1.1 vite: 6.4.1(@types/node@22.12.0)(jiti@2.6.1)(terser@5.51.2)(tsx@4.21.0)(yaml@2.8.4) - vite-plugin-svgr@4.3.0(rollup@4.57.1)(typescript@5.6.3)(vite@6.4.1(@types/node@22.12.0)(jiti@2.6.1)(terser@5.51.2)(tsx@4.21.0)(yaml@2.8.4)): - dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.57.1) - '@svgr/core': 8.1.0(typescript@5.6.3) - '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.6.3)) - vite: 6.4.1(@types/node@22.12.0)(jiti@2.6.1)(terser@5.51.2)(tsx@4.21.0)(yaml@2.8.4) - transitivePeerDependencies: - - rollup - - supports-color - - typescript - vite-plugin-svgr@4.3.0(rollup@4.57.1)(typescript@5.9.3)(vite@6.4.1(@types/node@22.12.0)(jiti@2.6.1)(terser@5.51.2)(tsx@4.21.0)(yaml@2.8.4)): dependencies: '@rollup/pluginutils': 5.3.0(rollup@4.57.1) From f492c8a8f6a7d97293050a3d70aed6973bbffbb4 Mon Sep 17 00:00:00 2001 From: Tom Brandenburg Date: Tue, 15 Sep 2026 13:05:15 +0200 Subject: [PATCH 2/3] docs(execution-worker): document per-provider API-key env vars in .env.example --- apps/execution-worker/.env.example | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/apps/execution-worker/.env.example b/apps/execution-worker/.env.example index 94e2ecd4c..0350f85aa 100644 --- a/apps/execution-worker/.env.example +++ b/apps/execution-worker/.env.example @@ -9,3 +9,22 @@ AI_MODEL=mistralai/mistral-small-3.2-24b-instruct # free key at https://tavily.com (free tier ~1000 searches/month). Leave empty # to disable: agents with web search toggled on still run, just without the tool. TAVILY_API_KEY= + +# Optional, per-provider direct-routing keys for the `ai-agent` node's `provider` +# field (see apps/execution-worker/README.md). None is required; each is only +# read by its own @ai-sdk/ package when that provider is selected explicitly +# or inferred via 'auto' from the model-id prefix. +OPENAI_API_KEY= +ANTHROPIC_API_KEY= +GOOGLE_GENERATIVE_AI_API_KEY= +XAI_API_KEY= +MISTRAL_API_KEY= +COHERE_API_KEY= +DEEPSEEK_API_KEY= +MOONSHOT_API_KEY= +GROQ_API_KEY= +TOGETHER_API_KEY= +FIREWORKS_API_KEY= +PERPLEXITY_API_KEY= +CEREBRAS_API_KEY= +DEEPINFRA_API_KEY= From 14de9624441c07113d1022c83edf2ae300bb885e Mon Sep 17 00:00:00 2001 From: Tom Brandenburg Date: Tue, 15 Sep 2026 13:19:29 +0200 Subject: [PATCH 3/3] feat(deploy): pass per-provider API keys through to worker container The worker service only forwards vars explicitly listed in docker-compose.yml's environment: block; docker compose does not pass through arbitrary host/.env vars. Without these entries, an operator setting e.g. ANTHROPIC_API_KEY in deploy/ai-studio/.env would never reach the container, so ai-agent's direct-provider routing was silently non-functional in the production stack. --- deploy/ai-studio/.env.example | 19 +++++++++++++++++++ deploy/ai-studio/docker-compose.yml | 16 ++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/deploy/ai-studio/.env.example b/deploy/ai-studio/.env.example index 6d250ca1d..013316db8 100644 --- a/deploy/ai-studio/.env.example +++ b/deploy/ai-studio/.env.example @@ -18,6 +18,25 @@ AI_MODEL=mistralai/mistral-small-3.2-24b-instruct # disable: agents with web search toggled on still run, just without the tool. TAVILY_API_KEY= +# Optional, per-provider direct-routing keys for the `ai-agent` node's `provider` +# field (see apps/execution-worker/README.md). None is required; each is only +# read by its own @ai-sdk/ package when that provider is selected explicitly +# or inferred via 'auto' from the model-id prefix. +OPENAI_API_KEY= +ANTHROPIC_API_KEY= +GOOGLE_GENERATIVE_AI_API_KEY= +XAI_API_KEY= +MISTRAL_API_KEY= +COHERE_API_KEY= +DEEPSEEK_API_KEY= +MOONSHOT_API_KEY= +GROQ_API_KEY= +TOGETHER_API_KEY= +FIREWORKS_API_KEY= +PERPLEXITY_API_KEY= +CEREBRAS_API_KEY= +DEEPINFRA_API_KEY= + # --- abuse gate (per-IP, execute route) --------------------------------------- RATE_LIMIT_EXECUTE_PER_MINUTE=10 diff --git a/deploy/ai-studio/docker-compose.yml b/deploy/ai-studio/docker-compose.yml index 5eed67bf7..3b8b8ae13 100644 --- a/deploy/ai-studio/docker-compose.yml +++ b/deploy/ai-studio/docker-compose.yml @@ -116,6 +116,22 @@ services: AI_MODEL: ${AI_MODEL:-mistralai/mistral-small-3.2-24b-instruct} # optional - empty disables the AI Agent's web search tool TAVILY_API_KEY: ${TAVILY_API_KEY:-} + # optional - per-provider direct-routing keys for the `ai-agent` node's + # `provider` field; each is only read when that provider is selected + OPENAI_API_KEY: ${OPENAI_API_KEY:-} + ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-} + GOOGLE_GENERATIVE_AI_API_KEY: ${GOOGLE_GENERATIVE_AI_API_KEY:-} + XAI_API_KEY: ${XAI_API_KEY:-} + MISTRAL_API_KEY: ${MISTRAL_API_KEY:-} + COHERE_API_KEY: ${COHERE_API_KEY:-} + DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY:-} + MOONSHOT_API_KEY: ${MOONSHOT_API_KEY:-} + GROQ_API_KEY: ${GROQ_API_KEY:-} + TOGETHER_API_KEY: ${TOGETHER_API_KEY:-} + FIREWORKS_API_KEY: ${FIREWORKS_API_KEY:-} + PERPLEXITY_API_KEY: ${PERPLEXITY_API_KEY:-} + CEREBRAS_API_KEY: ${CEREBRAS_API_KEY:-} + DEEPINFRA_API_KEY: ${DEEPINFRA_API_KEY:-} depends_on: app-db: condition: service_healthy