@vellumai/cli 0.10.7-dev.202607072228.38de414 → 0.10.7-dev.202607072325.09ad030

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/cli",
3
- "version": "0.10.7-dev.202607072228.38de414",
3
+ "version": "0.10.7-dev.202607072325.09ad030",
4
4
  "description": "CLI tools for vellum-assistant",
5
5
  "type": "module",
6
6
  "exports": {
@@ -0,0 +1,57 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { describe, expect, test } from "bun:test";
4
+
5
+ import { inferProviderFromModel } from "../lib/provider-secrets.js";
6
+
7
+ /**
8
+ * Drift guard for the CLI's model → provider inference heuristic.
9
+ *
10
+ * `inferProviderFromModel` mirrors the assistant resolver's
11
+ * `getCatalogProviderForModel` semantics without importing from
12
+ * `assistant/src/`: a model ID listed by multiple catalog providers resolves
13
+ * to the FIRST provider in catalog order (e.g. `anthropic/*` IDs shared by
14
+ * OpenRouter and the Vercel AI Gateway resolve to openrouter), while an ID
15
+ * unique to one provider resolves to that provider (e.g. `openai/gpt-5.5` and
16
+ * `xai/grok-4.3` to vercel-ai-gateway). This test recomputes that expectation
17
+ * from `meta/llm-provider-catalog.json` for every vendor-prefixed
18
+ * (slash-containing) model ID and fails if a catalog change breaks the
19
+ * heuristic.
20
+ */
21
+
22
+ const REPO_ROOT = join(import.meta.dir, "..", "..", "..");
23
+
24
+ interface LlmCatalog {
25
+ providers: Array<{ id: string; models: Array<{ id: string }> }>;
26
+ }
27
+
28
+ function loadLlmCatalog(): LlmCatalog {
29
+ const path = join(REPO_ROOT, "meta", "llm-provider-catalog.json");
30
+ return JSON.parse(readFileSync(path, "utf-8"));
31
+ }
32
+
33
+ describe("CLI provider inference parity", () => {
34
+ test("inferProviderFromModel matches first-catalog-provider resolution for every vendor-prefixed model ID", () => {
35
+ const catalog = loadLlmCatalog();
36
+ const expected: Record<string, string> = {};
37
+ for (const provider of catalog.providers) {
38
+ for (const model of provider.models) {
39
+ if (!model.id.includes("/")) {
40
+ continue;
41
+ }
42
+ expected[model.id] ??= provider.id;
43
+ }
44
+ }
45
+
46
+ // Sanity: the catalog still exercises both the unique-ID and shared-ID
47
+ // paths this guard exists for.
48
+ expect(Object.values(expected)).toContain("vercel-ai-gateway");
49
+ expect(Object.values(expected)).toContain("openrouter");
50
+
51
+ const actual: Record<string, string | undefined> = {};
52
+ for (const modelId of Object.keys(expected)) {
53
+ actual[modelId] = inferProviderFromModel(modelId);
54
+ }
55
+ expect(actual).toEqual(expected);
56
+ });
57
+ });
@@ -71,6 +71,10 @@ const PROVIDER_LABELS: Record<LlmProviderId, string> = {
71
71
  gemini: "Gemini",
72
72
  fireworks: "Fireworks",
73
73
  openrouter: "OpenRouter",
74
+ "vercel-ai-gateway": "Vercel AI Gateway",
75
+ minimax: "MiniMax",
76
+ atlascloud: "Atlas Cloud",
77
+ together: "Together AI",
74
78
  };
75
79
 
76
80
  export function formatProviderName(provider: LlmProviderId): string {
@@ -151,13 +155,42 @@ function readConfigValue(
151
155
  return value && value.length > 0 ? value : undefined;
152
156
  }
153
157
 
154
- function inferProviderFromModel(model: string): string | undefined {
155
- if (model.startsWith("claude-")) return "anthropic";
156
- if (model.startsWith("gpt-")) return "openai";
157
- if (model.startsWith("gemini-")) return "gemini";
158
- if (model.startsWith("accounts/fireworks/models/")) return "fireworks";
159
- if (model.includes("/")) return "openrouter";
160
- if (model === "llama3.2" || model === "mistral") return "ollama";
158
+ /**
159
+ * Infer the provider a bare model ID implies, mirroring the assistant
160
+ * resolver's `getCatalogProviderForModel`: an ID listed by multiple catalog
161
+ * providers resolves to the FIRST one in catalog order. Vendor prefixes unique
162
+ * to one provider map to it; shared gateway IDs (e.g. `anthropic/*`) fall
163
+ * through to openrouter, the earlier catalog entry. Drift guard:
164
+ * `cli/src/__tests__/provider-inference-parity.test.ts`. Exported for tests.
165
+ */
166
+ export function inferProviderFromModel(model: string): string | undefined {
167
+ if (model.startsWith("claude-")) {
168
+ return "anthropic";
169
+ }
170
+ if (model.startsWith("gpt-")) {
171
+ return "openai";
172
+ }
173
+ if (model.startsWith("gemini-")) {
174
+ return "gemini";
175
+ }
176
+ if (model.startsWith("accounts/fireworks/models/")) {
177
+ return "fireworks";
178
+ }
179
+ if (model.startsWith("openai/") || model.startsWith("xai/")) {
180
+ return "vercel-ai-gateway";
181
+ }
182
+ if (model.startsWith("MiniMaxAI/")) {
183
+ return "together";
184
+ }
185
+ if (model.startsWith("deepseek-ai/")) {
186
+ return "atlascloud";
187
+ }
188
+ if (model.includes("/")) {
189
+ return "openrouter";
190
+ }
191
+ if (model === "llama3.2" || model === "mistral") {
192
+ return "ollama";
193
+ }
161
194
  return undefined;
162
195
  }
163
196
 
@@ -26,6 +26,7 @@ export const LLM_PROVIDER_ENV_VAR_NAMES: Record<string, string> = {
26
26
  gemini: "GEMINI_API_KEY",
27
27
  fireworks: "FIREWORKS_API_KEY",
28
28
  openrouter: "OPENROUTER_API_KEY",
29
+ "vercel-ai-gateway": "AI_GATEWAY_API_KEY",
29
30
  minimax: "MINIMAX_API_KEY",
30
31
  atlascloud: "ATLASCLOUD_API_KEY",
31
32
  together: "TOGETHER_API_KEY",