Skip to content

Commit e94eb20

Browse files
sahrizviHaiderclaude
authored
feat(snowflake-cortex): close model-coverage gap vs Snowflake docs (#851) (#866)
* feat(snowflake-cortex): close model-coverage gap vs Snowflake docs (#851) Closes #851. altimate-code hardcodes the Snowflake Cortex model list in `provider.ts` (it doesn't auto-discover via `/v1/models` — Snowflake's endpoint has non-standard semantics that break standard clients). The list had drifted behind Snowflake's regional availability matrix, so several models that work on Cortex couldn't be selected from the picker — most visibly `claude-opus-4-7`, which prompted the report. Adds the 8 missing models per Snowflake's current regional availability matrix: - Claude: `claude-opus-4-7` (toolcall: true) - OpenAI: `openai-gpt-5.1`, `openai-gpt-5.2` (toolcall: true) - Llama: `llama4-scout`, `llama3.3-70b`, `snowflake-llama-3.1-405b` (toolcall: false) - Mistral: `mixtral-8x7b` (toolcall: false) - Gemini: `gemini-3.1-pro` (toolcall: false — tool calling unverified on Cortex; conservative default per the rule that only Claude+OpenAI reliably support tools today) Also updates `TOOLCALL_MODELS` in `snowflake.ts` to include the new Claude and OpenAI entries so request transform doesn't strip tools. Documents the config-extension escape hatch for the next drift cycle: users can add a model under `provider['snowflake-cortex'].models` in `opencode.json` and it merges into the built-in list (mechanism wired at provider.ts:1320-1399). New section in `docs/docs/configure/providers.md` shows the pattern so users don't need to fork or wait for a release when Snowflake adds a model. Tests: - `models added per Snowflake regional availability docs (issue #851)` asserts every newly-added model is defined with the right toolcall capability. - `user can register a model not in the hardcoded list via opencode.json` exercises the config-extension path end-to-end. 50 → 52 tests pass in `test/provider/snowflake.test.ts`; typecheck clean. Longer-term, true auto-discovery (a Snowflake-specific shim around `POST /api/v2/cortex/v1/models` or the GET-with-body variant) would eliminate drift entirely — flagged on the issue for maintainer input. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(snowflake-cortex): consensus review fixes — limits + escape hatch Addresses the consensus code review on PR #866. Two critical bugs found by independent verification against Snowflake's regional availability docs (https://docs.snowflake.com/en/user-guide/snowflake-cortex/aisql-regional-availability): 1. **Correct context/output limits on every newly-added model.** The initial commit inferred limits from sibling models; cross-check against the doc showed most were wrong, and `snowflake-llama-3.1-405b` was off by 16× on context (would silently fail any prompt above 8k tokens). Updated values: - `claude-opus-4-7`: 200000/32000 → 1000000/128000 - `openai-gpt-5.1`: 1047576/32768 → 272000/8192 - `openai-gpt-5.2`: 1047576/32768 → 272000/8192 (not in restrictions table; using gpt-5 family defaults with a comment) - `llama4-scout`: 1048576/4096 → 128000/8192 - `llama3.3-70b`: 128000/4096 → 128000/8192 - `snowflake-llama-3.1-405b`: 128000/4096 → 8000/8192 - `mixtral-8x7b`: 32000/4096 → 32000/8192 - `gemini-3.1-pro`: 1048576/8192 → 1000000/64000 2. **Fix the documented opencode.json escape hatch for tool-capable user-added models.** The `TOOLCALL_MODELS` hardcoded set in `snowflake.ts` was a second source of truth that the picker capability had to be kept in sync with. Worse, the docs change in the first commit told users they could mark a custom model `"tool_call": true` — but the request transform consulted only the static set, so user-added models had `tools` silently stripped at request time even though the picker advertised them as tool-capable. Replaced the hardcoded set with `buildToolCapableSet(models)` — the loader walks `provider.models` (which already includes any models the user registered via `opencode.json`) and derives the allowlist from `capabilities.toolcall`. Single source of truth; the picker and the transform can't drift; the escape hatch works. `transformSnowflakeBody` now takes an explicit allowlist parameter; the loader passes the dynamically-built set. Existing call sites updated to pass a shared `TOOLCAPABLE_FIXTURE`. Also folded into this commit: - Comments distinguishing `llama3.3-70b` (upstream Meta) from `snowflake-llama-3.3-70b` (Snowflake-hosted variant). - Doc note clarifying the `tool_call` snake_case field maps to `capabilities.toolcall`. - The "models added per Snowflake regional availability docs (issue #851)" regression test now asserts `limit.context` and `limit.output` per model in addition to identity and toolcall capability. - New tests: - `buildToolCapableSet derives the allowlist from provider model capabilities` — parity between picker and transform. - `escape-hatch model with tool_call: true retains tools through transformSnowflakeBody` — end-to-end check the escape hatch actually works for tool-capable user models. - `escape-hatch model with tool_call: false has tools stripped through transformSnowflakeBody` — the inverse, mirroring the built-in Llama/Mistral behavior for user-registered non-tool models. Deferred (per maintainer guidance pending @anandgupta42's input on the long-term auto-discovery question): - Removing the legacy `claude-3-7-sonnet`, `claude-3-5-sonnet`, `openai-gpt-5-chat` entries — kept per the original PR description. - Auto-discovery shim around Snowflake's `/api/v2/cortex/v1/models` — out of scope until maintainer green. Tests: typecheck clean across all 5 packages; 55/55 in `test/provider/snowflake.test.ts` (was 52, +3 net for new coverage). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: use canonical `altimate-code.json` config filename Reviewer caught my providers.md addition referring to `opencode.json` (and `.opencode/opencode.json`). Both names are accepted by the config loader (config.ts:1541 candidates: `altimate-code.json`, `opencode.jsonc`, `opencode.json`, `config.json`) but the rest of the docs standardize on `altimate-code.json` — `docs/docs/configure/config.md` uses the canonical name 9× and `opencode.json` 0×. My section was the inconsistent one. Root cause: I used `opencode.json` because that's the upstream project's name and what the snowflake tests happen to use. But this is the altimate-code fork; user-facing docs use the fork's canonical name. Updates `providers.md` "Adding a model not in the list" section and the three `altimate_change`-introduced comments in `snowflake.ts` to use `altimate-code.json` consistently. Tests left using `opencode.json` literals — they're exercising the loader (which accepts both) and swapping them would be churn without behavior change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test: use tmpdir({ config }) helper instead of manual Bun.write in init Bot review nit from CodeRabbit on the second review pass: tests added in this PR were calling `tmpdir({ init: async (dir) => Bun.write(...) })` to seed a project config, but the fixture already exposes a dedicated `config` option that does exactly that. Switching to the helper is shorter, matches the codebase guideline, and centralizes the `$schema` boilerplate. Affects the 5 tests added by this PR: - models added per Snowflake regional availability docs (issue #851) - buildToolCapableSet derives the allowlist from provider model capabilities - user can register a model not in the hardcoded list via altimate-code.json (also renamed from `via opencode.json` for canonical consistency with the docs) - escape-hatch model with tool_call: true retains tools through transformSnowflakeBody - escape-hatch model with tool_call: false has tools stripped through transformSnowflakeBody Pre-existing tests in the same file follow the old pattern; left out of scope to avoid churn in unrelated code. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: bot-review findings on snowflake-cortex PR Three valid findings from cubic + a human reviewer's auto-discovery documentation request, addressed in one commit: 1. **`snowflake-llama-3.1-405b` output > context (cubic P2 on provider.ts:1110).** Snowflake's docs list output=8192 for this model, but its context is 8000. Output can never physically exceed the total token budget, so a max_tokens=8192 request would either be rejected or implicitly capped by the gateway. Capped at 4096 (the original sibling default), with a comment explaining the apparent doc inconsistency. 2. **Aliased models bypass the tool-call allowlist (cubic P2 on snowflake.ts:19).** `buildToolCapableSet` indexed by the picker map key, but the request body sends `model: <api.id>`. A user registering an alias like: "my-claude-alias": { "id": "claude-opus-4-7", "tool_call": true } would have tools silently stripped because "my-claude-alias" != "claude-opus-4-7" in the set. Updated the helper to also include `m.id` and `m.api?.id` so the lookup matches whichever form the transform sees. Added a regression test that exercises the alias path end-to-end (config registration → loader-built set → request transform with the api.id form). 3. **Document why auto-discovery isn't used (web-researcher suggestion on provider.ts:1050).** Added a comment near the snowflake-cortex registration explaining that Cortex's models endpoint (`GET /api/v2/cortex/v1/models`) has non-standard semantics (requires JSON body on GET) that break standard OpenAI-compatible clients — so the hardcoded list is the source of truth until a probe-able shim exists. Skipped: - "CVE-2026-12345" comment from the web-researcher reviewer — the CVE id is a placeholder/fake (no such advisory exists), suggestion body is vague. Not actioned. - Schema-validation hardening for user-provided model configs — reasonable but broader than this PR's scope. CI status: the one TypeScript failure on this branch (`S27: sql_analyze with parse error`) is pre-existing on `main` HEAD `6ad8b47` — unrelated to snowflake-cortex. Tests: 55 → 56 in `test/provider/snowflake.test.ts`; typecheck clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Haider <haider@altimate.ai> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent e12cac3 commit e94eb20

5 files changed

Lines changed: 441 additions & 49 deletions

File tree

docs/docs/configure/providers.md

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -280,15 +280,39 @@ Billing flows through your Snowflake credits — no per-token costs.
280280

281281
| Model | Tool Calling |
282282
|-------|-------------|
283-
| `claude-sonnet-4-6`, `claude-opus-4-6`, `claude-sonnet-4-5`, `claude-opus-4-5`, `claude-haiku-4-5`, `claude-4-sonnet`, `claude-3-7-sonnet`, `claude-3-5-sonnet` | Yes |
284-
| `openai-gpt-4.1`, `openai-gpt-5`, `openai-gpt-5-mini`, `openai-gpt-5-nano`, `openai-gpt-5-chat` | Yes |
285-
| `llama4-maverick`, `snowflake-llama-3.3-70b`, `llama3.1-70b`, `llama3.1-405b`, `llama3.1-8b` | No |
286-
| `mistral-large`, `mistral-large2`, `mistral-7b` | No |
287-
| `deepseek-r1` | No |
283+
| `claude-opus-4-7`, `claude-sonnet-4-6`, `claude-opus-4-6`, `claude-sonnet-4-5`, `claude-opus-4-5`, `claude-haiku-4-5`, `claude-4-sonnet`, `claude-3-7-sonnet`, `claude-3-5-sonnet` | Yes |
284+
| `openai-gpt-4.1`, `openai-gpt-5`, `openai-gpt-5.1`, `openai-gpt-5.2`, `openai-gpt-5-mini`, `openai-gpt-5-nano`, `openai-gpt-5-chat` | Yes |
285+
| `llama4-maverick`, `llama4-scout`, `llama3.3-70b`, `snowflake-llama-3.3-70b`, `snowflake-llama-3.1-405b`, `llama3.1-70b`, `llama3.1-405b`, `llama3.1-8b` | No |
286+
| `mistral-large`, `mistral-large2`, `mistral-7b`, `mixtral-8x7b` | No |
287+
| `deepseek-r1`, `gemini-3.1-pro` | No |
288288

289289
!!! note
290290
Model availability depends on your Snowflake region. Enable cross-region inference with `ALTER ACCOUNT SET CORTEX_ENABLED_CROSS_REGION = 'ANY_REGION'` for full model access.
291291

292+
### Adding a model not in the list
293+
294+
Snowflake Cortex adds models faster than this list can be updated. If a model is available on your Cortex account but not yet listed above, you can register it locally without forking the CLI — add it under `provider["snowflake-cortex"].models` in your `altimate-code.json` (or `.altimate-code/altimate-code.json`):
295+
296+
```json
297+
{
298+
"provider": {
299+
"snowflake-cortex": {
300+
"models": {
301+
"your-new-model-id": {
302+
"name": "Your New Model",
303+
"limit": { "context": 200000, "output": 32000 },
304+
"tool_call": true
305+
}
306+
}
307+
}
308+
}
309+
}
310+
```
311+
312+
The entry merges with the built-in list, so the model appears in the picker and can be selected as `snowflake-cortex/your-new-model-id`. Set `"tool_call": false` for models that don't support tools on Cortex (Llama, Mistral, DeepSeek, Gemini today) — otherwise requests with tools will fail.
313+
314+
The `tool_call` field uses snake_case (matching the rest of the `altimate-code.json` schema) and maps to the picker's `capabilities.toolcall`. The request transform reads the same value, so a user-added model marked `tool_call: true` keeps `tools` and `tool_choice` in outgoing requests — and one marked `tool_call: false` has them stripped, the same as the built-in non-tool entries.
315+
292316
## Databricks AI Gateway
293317

294318
Connect to Databricks serving endpoints (Foundation Model APIs) via your workspace PAT. Use Databricks-hosted Llama, Claude, GPT, Gemini, DBRX, or Mixtral for agent reasoning — billing flows through your Databricks account.

packages/opencode/src/altimate/plugin/snowflake.ts

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,41 @@
11
import type { Hooks, PluginInput } from "@opencode-ai/plugin"
22
import { Auth, OAUTH_DUMMY_KEY } from "@/auth"
33

4-
// Only OpenAI and Claude models support tool calling on Snowflake Cortex.
5-
// All other models reject tools with "tool calling is not supported".
6-
const TOOLCALL_MODELS = new Set([
7-
// Claude
8-
"claude-sonnet-4-6", "claude-opus-4-6", "claude-sonnet-4-5", "claude-opus-4-5",
9-
"claude-haiku-4-5", "claude-4-sonnet", "claude-4-opus", "claude-3-7-sonnet", "claude-3-5-sonnet",
10-
// OpenAI
11-
"openai-gpt-4.1", "openai-gpt-5", "openai-gpt-5-mini", "openai-gpt-5-nano",
12-
"openai-gpt-5-chat", "openai-gpt-oss-120b", "openai-o4-mini",
13-
])
4+
/**
5+
* Build the set of Snowflake Cortex model IDs that support tool calling.
6+
* Derived from `provider.models[*].capabilities.toolcall` so the picker's
7+
* advertised capability and the request-transform's behavior cannot drift —
8+
* adding a tool-capable model to the provider definition (or registering one
9+
* via `altimate-code.json`) is the single source of truth.
10+
*
11+
* Indexes both the picker key and the model's `api.id` (and `id`) so that
12+
* aliased models — where a user registers e.g. `"my-alias": { "id":
13+
* "claude-opus-4-7" }` in their config — match correctly. The transform
14+
* compares against `parsed.model` which is the API id sent in the request
15+
* body, not the picker map key.
16+
*
17+
* Snowflake's documented constraint: only Claude and OpenAI models accept
18+
* tools on Cortex; everything else rejects with "tool calling is not supported."
19+
*/
20+
export function buildToolCapableSet(
21+
models: Record<
22+
string,
23+
{
24+
id?: string
25+
api?: { id?: string }
26+
capabilities: { toolcall: boolean }
27+
}
28+
>,
29+
): ReadonlySet<string> {
30+
const set = new Set<string>()
31+
for (const [key, m] of Object.entries(models)) {
32+
if (!m.capabilities.toolcall) continue
33+
set.add(key)
34+
if (m.id) set.add(m.id)
35+
if (m.api?.id) set.add(m.api.id)
36+
}
37+
return set
38+
}
1439

1540
/** Snowflake account identifiers contain only alphanumeric, hyphen, underscore, and dot characters. */
1641
export const VALID_ACCOUNT_RE = /^[a-zA-Z0-9._-]+$/
@@ -29,8 +54,15 @@ export function parseSnowflakePAT(code: string): { account: string; token: strin
2954
/**
3055
* Transform a Snowflake Cortex request body string.
3156
* Returns a Response to short-circuit the fetch (synthetic stop), or undefined to continue normally.
57+
*
58+
* @param toolCapable Model IDs that should retain `tools` / `tool_choice` / tool messages.
59+
* Build via `buildToolCapableSet(provider.models)` at loader time so
60+
* user-added models with `tool_call: true` in `altimate-code.json` are honored.
3261
*/
33-
export function transformSnowflakeBody(bodyText: string): { body: string; syntheticStop?: Response } {
62+
export function transformSnowflakeBody(
63+
bodyText: string,
64+
toolCapable: ReadonlySet<string>,
65+
): { body: string; syntheticStop?: Response } {
3466
const parsed = JSON.parse(bodyText)
3567

3668
// Snowflake uses max_completion_tokens instead of max_tokens
@@ -41,7 +73,7 @@ export function transformSnowflakeBody(bodyText: string): { body: string; synthe
4173

4274
// Strip tools for models that don't support tool calling on Snowflake Cortex.
4375
// Also remove orphaned tool_calls from messages to avoid Snowflake API errors.
44-
if (!TOOLCALL_MODELS.has(parsed.model)) {
76+
if (!toolCapable.has(parsed.model)) {
4577
delete parsed.tools
4678
delete parsed.tool_choice
4779
if (Array.isArray(parsed.messages)) {
@@ -97,6 +129,14 @@ export async function SnowflakeCortexAuthPlugin(_input: PluginInput): Promise<Ho
97129
model.cost = { input: 0, output: 0, cache: { read: 0, write: 0 } }
98130
}
99131

132+
// Build the tool-capable allowlist from the live provider definition.
133+
// This includes both hardcoded entries in provider.ts AND any models the
134+
// user registered via `altimate-code.json` with `"tool_call": true`.
135+
// Without this, the documented escape hatch is silently broken — picker
136+
// shows the model as tool-capable, but the transform strips tools at
137+
// request time because a static hardcoded set never sees user additions.
138+
const toolCapable = buildToolCapableSet(provider.models)
139+
100140
return {
101141
apiKey: OAUTH_DUMMY_KEY,
102142
async fetch(requestInput: RequestInfo | URL, init?: RequestInit) {
@@ -134,7 +174,7 @@ export async function SnowflakeCortexAuthPlugin(_input: PluginInput): Promise<Ho
134174
text = ""
135175
}
136176
if (text) {
137-
const result = transformSnowflakeBody(text)
177+
const result = transformSnowflakeBody(text, toolCapable)
138178
if (result.syntheticStop) return result.syntheticStop
139179
body = result.body
140180
headers.delete("content-length")

packages/opencode/src/provider/provider.ts

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1015,6 +1015,14 @@ export namespace Provider {
10151015
return m
10161016
}
10171017

1018+
// Snowflake Cortex models are hardcoded rather than auto-discovered.
1019+
// Cortex's models endpoint (`GET /api/v2/cortex/v1/models`) has
1020+
// non-standard semantics — it requires a JSON body on GET, which breaks
1021+
// standard OpenAI-compatible clients. Until that gets a probe-able shim,
1022+
// the source of truth for "what's selectable" is this map. New models
1023+
// surfaced on Snowflake's regional availability docs should be added here
1024+
// (or registered via `altimate-code.json` per the
1025+
// `docs/docs/configure/providers.md` escape-hatch section).
10181026
database["snowflake-cortex"] = {
10191027
id: ProviderID.snowflakeCortex,
10201028
source: "custom",
@@ -1023,6 +1031,7 @@ export namespace Provider {
10231031
options: {},
10241032
models: {
10251033
// Claude models — tool calling supported
1034+
"claude-opus-4-7": makeSnowflakeModel("claude-opus-4-7", "Claude Opus 4.7", { context: 1000000, output: 128000 }),
10261035
"claude-sonnet-4-6": makeSnowflakeModel("claude-sonnet-4-6", "Claude Sonnet 4.6", {
10271036
context: 200000,
10281037
output: 64000,
@@ -1049,6 +1058,16 @@ export namespace Provider {
10491058
}),
10501059
// OpenAI models — tool calling supported
10511060
"openai-gpt-4.1": makeSnowflakeModel("openai-gpt-4.1", "OpenAI GPT-4.1", { context: 1047576, output: 32768 }),
1061+
// openai-gpt-5.2: not in Snowflake's per-model restrictions table; using
1062+
// gpt-5 family defaults as best-effort until docs publish exact limits.
1063+
"openai-gpt-5.2": makeSnowflakeModel("openai-gpt-5.2", "OpenAI GPT-5.2", {
1064+
context: 272000,
1065+
output: 8192,
1066+
}),
1067+
"openai-gpt-5.1": makeSnowflakeModel("openai-gpt-5.1", "OpenAI GPT-5.1", {
1068+
context: 272000,
1069+
output: 8192,
1070+
}),
10521071
"openai-gpt-5": makeSnowflakeModel("openai-gpt-5", "OpenAI GPT-5", { context: 1047576, output: 32768 }),
10531072
"openai-gpt-5-mini": makeSnowflakeModel("openai-gpt-5-mini", "OpenAI GPT-5 Mini", {
10541073
context: 1047576,
@@ -1070,10 +1089,35 @@ export namespace Provider {
10701089
{ context: 1048576, output: 4096 },
10711090
{ toolcall: false },
10721091
),
1092+
"llama4-scout": makeSnowflakeModel(
1093+
"llama4-scout",
1094+
"Llama 4 Scout",
1095+
{ context: 128000, output: 8192 },
1096+
{ toolcall: false },
1097+
),
1098+
// llama3.3-70b: upstream Meta-hosted variant.
1099+
"llama3.3-70b": makeSnowflakeModel(
1100+
"llama3.3-70b",
1101+
"Llama 3.3 70B",
1102+
{ context: 128000, output: 8192 },
1103+
{ toolcall: false },
1104+
),
1105+
// snowflake-llama-3.3-70b: Snowflake-hosted variant (different routing /
1106+
// region pinning vs the upstream `llama3.3-70b` above).
10731107
"snowflake-llama-3.3-70b": makeSnowflakeModel(
10741108
"snowflake-llama-3.3-70b",
10751109
"Snowflake Llama 3.3 70B",
1076-
{ context: 128000, output: 4096 },
1110+
{ context: 128000, output: 8192 },
1111+
{ toolcall: false },
1112+
),
1113+
// snowflake-llama-3.1-405b: 8k context per Snowflake docs (much smaller
1114+
// than the upstream Meta model's window). Snowflake's table lists
1115+
// output=8192, but output cannot exceed the total token budget — cap
1116+
// at 4096 (the original sibling default) so prompt+output always fit.
1117+
"snowflake-llama-3.1-405b": makeSnowflakeModel(
1118+
"snowflake-llama-3.1-405b",
1119+
"Snowflake Llama 3.1 405B",
1120+
{ context: 8000, output: 4096 },
10771121
{ toolcall: false },
10781122
),
10791123
"llama3.1-70b": makeSnowflakeModel(
@@ -1113,13 +1157,26 @@ export namespace Provider {
11131157
{ context: 32000, output: 4096 },
11141158
{ toolcall: false },
11151159
),
1160+
"mixtral-8x7b": makeSnowflakeModel(
1161+
"mixtral-8x7b",
1162+
"Mixtral 8x7B",
1163+
{ context: 32000, output: 8192 },
1164+
{ toolcall: false },
1165+
),
11161166
// DeepSeek — no tool calling
11171167
"deepseek-r1": makeSnowflakeModel(
11181168
"deepseek-r1",
11191169
"DeepSeek R1",
11201170
{ context: 64000, output: 32000 },
11211171
{ reasoning: true, toolcall: false },
11221172
),
1173+
// Gemini — tool calling not verified on Cortex; default to off until confirmed
1174+
"gemini-3.1-pro": makeSnowflakeModel(
1175+
"gemini-3.1-pro",
1176+
"Gemini 3.1 Pro",
1177+
{ context: 1000000, output: 64000 },
1178+
{ toolcall: false },
1179+
),
11231180
},
11241181
}
11251182
// altimate_change end

packages/opencode/test/altimate/cortex-snowflake-e2e.test.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ import {
3030
VALID_ACCOUNT_RE,
3131
} from "../../src/altimate/plugin/snowflake"
3232

33+
// Production builds this set from `provider.models` at loader time; mirror
34+
// the same shape here for the transform-only e2e checks.
35+
const TOOLCAPABLE_E2E_FIXTURE: ReadonlySet<string> = new Set([
36+
"claude-3-5-sonnet", "claude-sonnet-4-6", "claude-opus-4-7",
37+
"openai-gpt-4.1", "openai-gpt-5",
38+
])
39+
3340
// ---------------------------------------------------------------------------
3441
// Auth configuration
3542
// ---------------------------------------------------------------------------
@@ -375,7 +382,7 @@ describe.skipIf(!HAS_CORTEX)("Snowflake Cortex E2E", () => {
375382
max_tokens: 100,
376383
stream: true,
377384
})
378-
const { body } = transformSnowflakeBody(input)
385+
const { body } = transformSnowflakeBody(input, TOOLCAPABLE_E2E_FIXTURE)
379386
const parsed = JSON.parse(body)
380387
expect(parsed.max_completion_tokens).toBe(100)
381388
expect(parsed.max_tokens).toBeUndefined()
@@ -388,7 +395,7 @@ describe.skipIf(!HAS_CORTEX)("Snowflake Cortex E2E", () => {
388395
tools: [{ type: "function", function: { name: "read_file" } }],
389396
tool_choice: "auto",
390397
})
391-
const { body } = transformSnowflakeBody(input)
398+
const { body } = transformSnowflakeBody(input, TOOLCAPABLE_E2E_FIXTURE)
392399
const parsed = JSON.parse(body)
393400
expect(parsed.tools).toBeUndefined()
394401
expect(parsed.tool_choice).toBeUndefined()
@@ -403,7 +410,7 @@ describe.skipIf(!HAS_CORTEX)("Snowflake Cortex E2E", () => {
403410
{ role: "assistant", content: "response" },
404411
],
405412
})
406-
const { syntheticStop } = transformSnowflakeBody(input)
413+
const { syntheticStop } = transformSnowflakeBody(input, TOOLCAPABLE_E2E_FIXTURE)
407414
expect(syntheticStop).toBeUndefined()
408415
})
409416

@@ -416,7 +423,7 @@ describe.skipIf(!HAS_CORTEX)("Snowflake Cortex E2E", () => {
416423
{ role: "assistant", content: "response" },
417424
],
418425
})
419-
const { syntheticStop } = transformSnowflakeBody(input)
426+
const { syntheticStop } = transformSnowflakeBody(input, TOOLCAPABLE_E2E_FIXTURE)
420427
expect(syntheticStop).toBeDefined()
421428
expect(syntheticStop!.status).toBe(200)
422429
})

0 commit comments

Comments
 (0)