Skip to content

Commit e94f069

Browse files
authored
feat(providers): refresh model catalogs and support custom routing (#7851)
* feat(providers): refresh model catalogs and support custom routing * fix(providers): add verified Qwen 3.8 release dates
1 parent e92de57 commit e94f069

61 files changed

Lines changed: 2378 additions & 282 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/docs/content/docs/workflows/blocks/agent.mdx

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ Answer in two sentences, cite the doc you used, and never guess a price.
2727

2828
The model that runs the step. Defaults to `claude-sonnet-4-6`. Type or pick any model from OpenAI, Anthropic, Google, xAI, Groq, Cerebras, DeepSeek, Azure, AWS Bedrock, Google Vertex, or OpenRouter, or a local model through Ollama or VLLM.
2929

30+
For a custom cloud deployment, enter its provider prefix and model ID: `azure/my-deployment`, `azure-anthropic/my-deployment`, `bedrock/my-inference-profile`, or `vertex/my-gemini-model`. The prefix selects the provider and shows its credential fields even when the ID is absent from the catalog. Bedrock accepts full inference profile ARNs after `bedrock/`; Vertex uses the Gemini API and accepts Google model resource names. The deployment must support the selected provider's API. Custom IDs have no catalog pricing or token limits.
31+
32+
Ollama Cloud, OpenRouter, Fireworks, Together AI, Baseten, Ollama, vLLM, and LiteLLM load their available models from the configured provider. New models appear through that discovery without a Sim catalog release. You can also enter a namespaced ID directly, such as `ollama-cloud/deepseek-v4.1-flash`, `openrouter/provider/model`, or `ollama/my-local-model`. Provider prefixes are case-insensitive; the model ID after the prefix keeps its original casing.
33+
3034
### Files
3135

3236
Files for the model to read: images for a vision-capable model, or documents for text. Upload them on the block, or pass a file from an earlier block, such as an upload trigger or an [API](/workflows/blocks/api) response, with a connection tag.
@@ -110,18 +114,19 @@ Live tool-call chips stream for **OpenAI, Anthropic, Azure Anthropic, Google, Ve
110114

111115
| Provider | Streamed thinking | Models |
112116
|----------|-------------------|--------|
113-
| OpenAI | Summaries only — Requires OpenAI organization verification; falls back to no summaries. | `gpt-6-astra`, `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna`, `gpt-5.5-pro`, `gpt-5.5`, `gpt-5.4-pro`, `gpt-5.4`, `gpt-5.4-mini`, `gpt-5.4-nano`, `gpt-5.2-pro`, `gpt-5.2`, `gpt-5.1`, `gpt-5-pro`, `gpt-5`, `gpt-5-mini`, `gpt-5-nano`, `o4-mini`, `o3`, `o3-mini`, `o1` |
117+
| OpenAI | Summaries only — Requires OpenAI organization verification; falls back to no summaries. | `gpt-6-astra`, `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna`, `gpt-5.5-pro`, `gpt-5.5`, `gpt-5.4-pro`, `gpt-5.4`, `gpt-5.4-mini`, `gpt-5.4-nano`, `gpt-5.3-codex`, `gpt-5.2-pro`, `gpt-5.2`, `gpt-5.1`, `gpt-5-pro`, `gpt-5`, `gpt-5-mini`, `gpt-5-nano`, `o4-mini`, `o3`, `o3-mini`, `o1` |
114118
| Anthropic | Summaries only — These generations omit full thinking; Sim requests summarized thinking on streaming runs. | `claude-fable-5-1`, `claude-fable-5`, `claude-sonnet-5`, `claude-opus-5`, `claude-opus-4-8`, `claude-opus-4-7`, `claude-opus-4-6`, `claude-sonnet-4-6`, `claude-opus-4-5`, `claude-sonnet-4-5`, `claude-haiku-4-5` |
115-
| Azure OpenAI | Summaries only — Requires OpenAI organization verification; falls back to no summaries. | `azure/gpt-5.4`, `azure/gpt-5.4-mini`, `azure/gpt-5.4-nano`, `azure/gpt-5.2`, `azure/gpt-5.1`, `azure/gpt-5.1-codex`, `azure/gpt-5`, `azure/gpt-5-mini`, `azure/gpt-5-nano`, `azure/o3`, `azure/o4-mini` |
116-
| Azure Anthropic | Summaries only — These generations omit full thinking; Sim requests summarized thinking on streaming runs. | `azure-anthropic/claude-opus-4-6`, `azure-anthropic/claude-opus-4-5`, `azure-anthropic/claude-sonnet-4-5`, `azure-anthropic/claude-opus-4-1`, `azure-anthropic/claude-haiku-4-5` |
117-
| Google | Summaries only | `gemini-3.8-flash`, `gemini-3.6-flash`, `gemini-3.5-flash-lite`, `gemini-3.5-flash`, `gemini-3.1-pro-preview`, `gemini-3.1-flash-lite`, `gemini-3-flash-preview`, `gemini-2.5-pro`, `gemini-2.5-flash`, `gemini-2.5-flash-lite` |
118-
| Vertex AI | Summaries only | `vertex/gemini-3.5-flash`, `vertex/gemini-3.1-pro-preview`, `vertex/gemini-3.1-flash-lite`, `vertex/gemini-3-flash-preview`, `vertex/gemini-2.5-pro`, `vertex/gemini-2.5-flash`, `vertex/gemini-2.5-flash-lite` |
119-
| DeepSeek | Full thinking deltas | `deepseek-v4-pro`, `deepseek-v4-flash`, `deepseek-reasoner` |
119+
| Azure OpenAI | Summaries only — Requires OpenAI organization verification; falls back to no summaries. | `azure/gpt-6-astra`, `azure/gpt-5.6-sol`, `azure/gpt-5.6-terra`, `azure/gpt-5.6-luna`, `azure/gpt-5.5`, `azure/gpt-5.4-pro`, `azure/gpt-5.4`, `azure/gpt-5.4-mini`, `azure/gpt-5.4-nano`, `azure/gpt-5.2`, `azure/gpt-5.1`, `azure/gpt-5.1-codex`, `azure/gpt-5`, `azure/gpt-5-mini`, `azure/gpt-5-nano`, `azure/o3`, `azure/o4-mini` |
120+
| Azure Anthropic | Summaries only — These generations omit full thinking; Sim requests summarized thinking on streaming runs. | `azure-anthropic/claude-fable-5-1`, `azure-anthropic/claude-opus-5`, `azure-anthropic/claude-opus-4-8`, `azure-anthropic/claude-opus-4-7`, `azure-anthropic/claude-opus-4-6`, `azure-anthropic/claude-opus-4-5`, `azure-anthropic/claude-sonnet-5`, `azure-anthropic/claude-sonnet-4-6`, `azure-anthropic/claude-sonnet-4-5`, `azure-anthropic/claude-opus-4-1`, `azure-anthropic/claude-haiku-4-5` |
121+
| Google | Summaries only | `gemini-3.8-flash`, `gemini-3.7-flash`, `gemini-3.6-flash`, `gemini-3.5-flash-lite`, `gemini-3.5-flash`, `gemini-3.1-pro-preview`, `gemini-3.1-flash-lite`, `gemini-3-flash-preview`, `gemini-2.5-pro`, `gemini-2.5-flash`, `gemini-2.5-flash-lite` |
122+
| Vertex AI | Summaries only | `vertex/gemini-3.8-flash`, `vertex/gemini-3.7-flash`, `vertex/gemini-3.6-flash`, `vertex/gemini-3.5-flash-lite`, `vertex/gemini-3.5-flash`, `vertex/gemini-3.1-pro-preview`, `vertex/gemini-3.1-flash-lite`, `vertex/gemini-3-flash-preview`, `vertex/gemini-2.5-pro`, `vertex/gemini-2.5-flash`, `vertex/gemini-2.5-flash-lite` |
123+
| DeepSeek | Full thinking deltas | `deepseek-v4-pro`, `deepseek-flash`, `deepseek-v4-flash`, `deepseek-reasoner` |
120124
| xAI | Full thinking deltas | `grok-4.6`, `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-0309` |
121-
| Groq | Full thinking deltas | `groq/openai/gpt-oss-120b`, `groq/openai/gpt-oss-20b`, `groq/openai/gpt-oss-safeguard-20b`, `groq/qwen/qwen3.6-27b` |
125+
| Cerebras | Full thinking deltas | `cerebras/qwen-3.8-27b` |
126+
| Groq | Full thinking deltas | `groq/openai/gpt-oss-120b`, `groq/openai/gpt-oss-20b`, `groq/openai/gpt-oss-safeguard-20b`, `groq/qwen/qwen3.8-27b`, `groq/qwen/qwen3.6-27b` |
122127
| Meta | Not streamed | `muse-spark-1.3`, `muse-spark-1.1` |
123-
| Kimi | Full thinking deltas | `kimi-k2.6` |
124-
| Z.ai | Full thinking deltas | `glm-5.3`, `glm-5.2`, `glm-5.1`, `glm-5`, `glm-5-turbo`, `glm-4.7`, `glm-4.6`, `glm-4.5`, `glm-4.5-air` |
128+
| Kimi | Full thinking deltas | `kimi-k3`, `kimi-k2.6` |
129+
| Z.ai | Full thinking deltas | `glm-5.3`, `glm-5.3-flash`, `glm-5.2`, `glm-5.1`, `glm-5`, `glm-5-turbo`, `glm-4.7`, `glm-4.6`, `glm-4.5`, `glm-4.5-air` |
125130

126131
{/* agent-stream-capabilities:end */}
127132

apps/sim/app/api/providers/ollama-cloud/models/route.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,29 @@ describe('GET /api/providers/ollama-cloud/models', () => {
144144
expect(fetchAuthHeader()).toBe('Bearer byok-ollama-key')
145145
})
146146

147+
it('discovers newly available models on subsequent requests without a static catalog update', async () => {
148+
grantWorkspaceAccess()
149+
mockGetBYOKKey.mockResolvedValue({ apiKey: 'byok-ollama-key' })
150+
mockFetch
151+
.mockResolvedValueOnce(okResponse({ models: [{ name: 'kimi-k3' }] }))
152+
.mockResolvedValueOnce(
153+
okResponse({
154+
models: [{ name: 'kimi-k3' }, { name: 'deepseek-v4.1-flash' }, { name: 'glm-5.3' }],
155+
})
156+
)
157+
158+
const first = await GET(requestWithWorkspace('ws-1'))
159+
expect(await first.json()).toEqual({ models: ['ollama-cloud/kimi-k3'] })
160+
const refreshed = await GET(requestWithWorkspace('ws-1'))
161+
expect(await refreshed.json()).toEqual({
162+
models: ['ollama-cloud/kimi-k3', 'ollama-cloud/deepseek-v4.1-flash', 'ollama-cloud/glm-5.3'],
163+
})
164+
expect(mockFetch).toHaveBeenLastCalledWith(
165+
OLLAMA_CLOUD_TAGS_URL,
166+
expect.objectContaining({ cache: 'no-store' })
167+
)
168+
})
169+
147170
it('does not call getBYOKKey when there is a workspaceId but no session', async () => {
148171
mockGetSession.mockResolvedValue(null)
149172

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* @vitest-environment node
3+
*/
4+
import { describe, expect, it } from 'vitest'
5+
import { evaluateSubBlockCondition } from '@/lib/workflows/subblocks/visibility'
6+
import { getProviderCredentialSubBlocks } from '@/blocks/utils'
7+
8+
describe('provider credential visibility', () => {
9+
const subBlocks = getProviderCredentialSubBlocks().filter(({ id }) => id !== 'apiKey')
10+
11+
it.each([
12+
['azure/MyDeployment', ['azureEndpoint', 'azureApiVersion']],
13+
['AZURE/MyDeployment', ['azureEndpoint', 'azureApiVersion']],
14+
['azure-anthropic/MyDeployment', ['azureEndpoint', 'azureApiVersion']],
15+
['bedrock/custom-profile', ['bedrockAccessKeyId', 'bedrockSecretKey', 'bedrockRegion']],
16+
[
17+
'vertex/publishers/google/models/custom-gemini',
18+
['vertexCredential', 'vertexManualCredential', 'vertexProject', 'vertexLocation'],
19+
],
20+
[
21+
'VERTEX/CustomModel',
22+
['vertexCredential', 'vertexManualCredential', 'vertexProject', 'vertexLocation'],
23+
],
24+
['gpt-4o', []],
25+
['unknown/model', []],
26+
['', []],
27+
])('shows only the routed provider credentials for %s', (model, expected) => {
28+
const visible = subBlocks
29+
.filter((subBlock) => evaluateSubBlockCondition(subBlock.condition, { model }))
30+
.map(({ id }) => id)
31+
32+
expect(visible).toEqual(expected)
33+
})
34+
})

apps/sim/blocks/utils.test.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ const { mockProviders } = vi.hoisted(() => ({
3131
},
3232
}))
3333

34-
vi.mock('@/providers/models', () => ({
34+
vi.mock('@/providers/models', async (importOriginal) => ({
35+
...(await importOriginal<typeof import('@/providers/models')>()),
3536
getProviderFileAttachment: vi
3637
.fn()
3738
.mockReturnValue({ maxBytes: 10 * 1024 * 1024, strategy: 'inline' }),
@@ -180,6 +181,16 @@ describe('getApiKeyCondition / shouldRequireApiKeyForModel', () => {
180181
})
181182

182183
describe('provider store lookup (client-side)', () => {
184+
it('requires the cloud key even when a local discovered name uses its namespace', () => {
185+
mockProviders.value.ollama.models = ['azure/MyDeployment', 'ollama-cloud/MyModel']
186+
expect(evaluateCondition('azure/MyDeployment')).toBe(true)
187+
expect(evaluateCondition('ollama-cloud/MyModel')).toBe(true)
188+
})
189+
190+
it('does not require an API key for an undiscovered namespaced Ollama model', () => {
191+
expect(evaluateCondition('OLLAMA/Org/CustomModel')).toBe(false)
192+
})
193+
183194
it('does not require API key when model is in the Ollama store bucket', () => {
184195
mockProviders.value.ollama.models = ['llama3:latest', 'mistral:latest']
185196
expect(evaluateCondition('llama3:latest')).toBe(false)

apps/sim/blocks/utils.ts

Lines changed: 26 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ import { getScopesForService } from '@/lib/oauth/utils'
66
import { containsReference } from '@/lib/workflows/sanitization/references'
77
import type { SubBlockConfig } from '@/blocks/types'
88
import {
9+
findProviderFromModel,
910
getBaseModelProviders,
1011
getHostedModels,
1112
getModelSunsetStatus,
1213
getProviderIcon,
1314
getProviderModels,
1415
isAutoModel,
16+
isCustomModelId,
1517
orderModelIdsByReleaseDate,
1618
SIM_AUTO_MODEL_ID,
1719
} from '@/providers/models'
@@ -20,8 +22,6 @@ import type { ProviderId } from '@/providers/types'
2022
import { getProviderFromModel } from '@/providers/utils'
2123
import { useProvidersStore } from '@/stores/providers/store'
2224

23-
export const VERTEX_MODELS = getProviderModels('vertex')
24-
export const BEDROCK_MODELS = getProviderModels('bedrock')
2525
export const AZURE_MODELS = [
2626
...getProviderModels('azure-openai'),
2727
...getProviderModels('azure-anthropic'),
@@ -164,10 +164,16 @@ function shouldRequireApiKeyForModel(model: string): boolean {
164164
) {
165165
return false
166166
}
167-
if (normalizedModel.startsWith('vllm/') || normalizedModel.startsWith('litellm/')) {
167+
if (
168+
normalizedModel.startsWith('ollama/') ||
169+
normalizedModel.startsWith('vllm/') ||
170+
normalizedModel.startsWith('litellm/')
171+
) {
168172
return false
169173
}
170174

175+
if (isCustomModelId(normalizedModel)) return true
176+
171177
const storeProvider = getProviderFromStore(normalizedModel)
172178
if (storeProvider === 'ollama' || storeProvider === 'vllm' || storeProvider === 'litellm')
173179
return false
@@ -272,6 +278,14 @@ export function getCohereRerankerApiKeyCondition() {
272278
}
273279
}
274280

281+
function getModelProviderCondition(...providerIds: ProviderId[]) {
282+
return (values?: Record<string, unknown>) => {
283+
const model = typeof values?.model === 'string' ? values.model : ''
284+
const provider = findProviderFromModel(model.trim())
285+
return buildModelVisibilityCondition(model, provider !== null && providerIds.includes(provider))
286+
}
287+
}
288+
275289
/**
276290
* Returns the standard provider credential subblocks used by LLM-based blocks.
277291
* This includes: Vertex AI OAuth, API Key, Azure (OpenAI + Anthropic), Vertex AI config, and Bedrock config.
@@ -290,10 +304,7 @@ export function getProviderCredentialSubBlocks(): SubBlockConfig[] {
290304
requiredScopes: getScopesForService('vertex-ai'),
291305
placeholder: 'Select Google Cloud account',
292306
required: true,
293-
condition: {
294-
field: 'model',
295-
value: VERTEX_MODELS,
296-
},
307+
condition: getModelProviderCondition('vertex'),
297308
},
298309
{
299310
id: 'vertexManualCredential',
@@ -303,10 +314,7 @@ export function getProviderCredentialSubBlocks(): SubBlockConfig[] {
303314
mode: 'advanced',
304315
placeholder: 'Enter credential ID',
305316
required: true,
306-
condition: {
307-
field: 'model',
308-
value: VERTEX_MODELS,
309-
},
317+
condition: getModelProviderCondition('vertex'),
310318
},
311319
{
312320
id: 'apiKey',
@@ -326,10 +334,7 @@ export function getProviderCredentialSubBlocks(): SubBlockConfig[] {
326334
placeholder: 'https://your-resource.services.ai.azure.com',
327335
connectionDroppable: false,
328336
hideWhenEnvSet: 'NEXT_PUBLIC_AZURE_CONFIGURED',
329-
condition: {
330-
field: 'model',
331-
value: AZURE_MODELS,
332-
},
337+
condition: getModelProviderCondition('azure-openai', 'azure-anthropic'),
333338
},
334339
{
335340
id: 'azureApiVersion',
@@ -338,10 +343,7 @@ export function getProviderCredentialSubBlocks(): SubBlockConfig[] {
338343
placeholder: 'Enter API version',
339344
connectionDroppable: false,
340345
hideWhenEnvSet: 'NEXT_PUBLIC_AZURE_CONFIGURED',
341-
condition: {
342-
field: 'model',
343-
value: AZURE_MODELS,
344-
},
346+
condition: getModelProviderCondition('azure-openai', 'azure-anthropic'),
345347
},
346348
{
347349
id: 'vertexProject',
@@ -351,10 +353,7 @@ export function getProviderCredentialSubBlocks(): SubBlockConfig[] {
351353
placeholder: 'your-gcp-project-id',
352354
connectionDroppable: false,
353355
required: true,
354-
condition: {
355-
field: 'model',
356-
value: VERTEX_MODELS,
357-
},
356+
condition: getModelProviderCondition('vertex'),
358357
},
359358
{
360359
id: 'vertexLocation',
@@ -363,10 +362,7 @@ export function getProviderCredentialSubBlocks(): SubBlockConfig[] {
363362
placeholder: 'us-central1',
364363
connectionDroppable: false,
365364
required: true,
366-
condition: {
367-
field: 'model',
368-
value: VERTEX_MODELS,
369-
},
365+
condition: getModelProviderCondition('vertex'),
370366
},
371367
{
372368
id: 'bedrockAccessKeyId',
@@ -377,10 +373,7 @@ export function getProviderCredentialSubBlocks(): SubBlockConfig[] {
377373
connectionDroppable: false,
378374
required: true,
379375
hideWhenEnvSet: 'NEXT_PUBLIC_BEDROCK_DEFAULT_CREDENTIALS',
380-
condition: {
381-
field: 'model',
382-
value: BEDROCK_MODELS,
383-
},
376+
condition: getModelProviderCondition('bedrock'),
384377
},
385378
{
386379
id: 'bedrockSecretKey',
@@ -391,21 +384,15 @@ export function getProviderCredentialSubBlocks(): SubBlockConfig[] {
391384
connectionDroppable: false,
392385
required: true,
393386
hideWhenEnvSet: 'NEXT_PUBLIC_BEDROCK_DEFAULT_CREDENTIALS',
394-
condition: {
395-
field: 'model',
396-
value: BEDROCK_MODELS,
397-
},
387+
condition: getModelProviderCondition('bedrock'),
398388
},
399389
{
400390
id: 'bedrockRegion',
401391
title: 'AWS Region',
402392
type: 'short-input',
403393
placeholder: 'us-east-1',
404394
connectionDroppable: false,
405-
condition: {
406-
field: 'model',
407-
value: BEDROCK_MODELS,
408-
},
395+
condition: getModelProviderCondition('bedrock'),
409396
},
410397
]
411398
}

apps/sim/lib/api-key/byok.test.ts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,99 @@ describe('getBYOKKey', () => {
451451
})
452452
})
453453

454+
describe('getApiKeyWithBYOK provider classification', () => {
455+
const dynamicProviders = [
456+
'ollama',
457+
'vllm',
458+
'litellm',
459+
'fireworks',
460+
'together',
461+
'baseten',
462+
'ollama-cloud',
463+
] as const
464+
465+
beforeEach(() => {
466+
vi.clearAllMocks()
467+
resetDbChainMock()
468+
mockIsHosted.value = true
469+
mockEnv.AZURE_OPENAI_API_KEY = 'azure-env-key'
470+
mockEnv.AZURE_ANTHROPIC_API_KEY = 'azure-anthropic-env-key'
471+
mockEnv.VLLM_API_KEY = 'vllm-env-key'
472+
mockEnv.LITELLM_API_KEY = 'litellm-env-key'
473+
dbChainMockFns.orderBy.mockResolvedValue([storedKey('other-provider-key')])
474+
mockDecryptSecret.mockImplementation(async (encrypted: string) => ({
475+
decrypted: encrypted.replace('encrypted-', 'decrypted-'),
476+
}))
477+
})
478+
479+
it.each(dynamicProviders)(
480+
'keeps Azure credentials when %s discovery contains the same model ID',
481+
async (discoveredProvider) => {
482+
const model = 'AZURE/CustomDeployment'
483+
vi.mocked(useProvidersStore.getState).mockReturnValue({
484+
providers: Object.fromEntries(
485+
dynamicProviders.map((provider) => [
486+
provider,
487+
{ models: provider === discoveredProvider ? [model] : [] },
488+
])
489+
),
490+
} as ReturnType<typeof useProvidersStore.getState>)
491+
492+
const result = await getApiKeyWithBYOK('azure-openai', model, uniqueWorkspaceId())
493+
494+
expect(result).toEqual({ apiKey: 'azure-env-key', isBYOK: false })
495+
expect(dbChainMockFns.where).not.toHaveBeenCalled()
496+
expect(mockGetRotatingApiKey).not.toHaveBeenCalled()
497+
}
498+
)
499+
500+
it.each([
501+
['vertex', 'vertex/CustomDeployment', 'vertex-access-token'],
502+
['azure-anthropic', 'azure-anthropic/CustomDeployment', 'azure-anthropic-user-key'],
503+
])(
504+
'retains caller credentials for %s despite a local model name collision',
505+
async (provider, model, apiKey) => {
506+
vi.mocked(useProvidersStore.getState).mockReturnValue({
507+
providers: Object.fromEntries(dynamicProviders.map((name) => [name, { models: [model] }])),
508+
} as ReturnType<typeof useProvidersStore.getState>)
509+
510+
expect(await getApiKeyWithBYOK(provider, model, uniqueWorkspaceId(), apiKey)).toEqual({
511+
apiKey,
512+
isBYOK: false,
513+
})
514+
expect(dbChainMockFns.where).not.toHaveBeenCalled()
515+
}
516+
)
517+
518+
it.each([
519+
['ollama', 'empty'],
520+
['vllm', 'vllm-env-key'],
521+
['litellm', 'litellm-env-key'],
522+
])('preserves %s authentication for a custom unprefixed model', async (provider, apiKey) => {
523+
expect(await getApiKeyWithBYOK(provider, 'MyCustomModel', uniqueWorkspaceId())).toEqual({
524+
apiKey,
525+
isBYOK: false,
526+
})
527+
expect(dbChainMockFns.where).not.toHaveBeenCalled()
528+
})
529+
530+
it.each(['vllm', 'litellm'])(
531+
'prefers a caller key to the configured %s key for a local model',
532+
async (provider) => {
533+
expect(
534+
await getApiKeyWithBYOK(provider, 'MyCustomModel', uniqueWorkspaceId(), 'caller-key')
535+
).toEqual({ apiKey: 'caller-key', isBYOK: false })
536+
}
537+
)
538+
539+
it('uses Bedrock credentials for an uncataloged inference profile', async () => {
540+
expect(
541+
await getApiKeyWithBYOK('bedrock', 'BEDROCK/MyInferenceProfile', uniqueWorkspaceId())
542+
).toEqual({ apiKey: 'placeholder', isBYOK: false })
543+
expect(dbChainMockFns.where).not.toHaveBeenCalled()
544+
})
545+
})
546+
454547
describe('getApiKeyWithBYOK for Fireworks', () => {
455548
const HOSTED_POOL_MODEL = 'fireworks/glm-5.2'
456549

0 commit comments

Comments
 (0)