@jacobbd/relay-ai 0.7.6 → 0.9.0

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.
@@ -1,5 +1,5 @@
1
1
  // src/config.ts
2
- import { dirname, join as join2 } from "path";
2
+ import { dirname, join as join3 } from "path";
3
3
  import { copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
4
4
 
5
5
  // src/paths.ts
@@ -43,83 +43,14 @@ function getLegacyConfPath(env = process.env, platform = process.platform) {
43
43
  return join(env.XDG_CONFIG_HOME ?? join(home, ".config"), appName, "config.json");
44
44
  }
45
45
 
46
- // src/config.ts
47
- function readJsonFile(path) {
48
- try {
49
- const parsed = JSON.parse(readFileSync(path, "utf8"));
50
- return parsed && typeof parsed === "object" ? parsed : null;
51
- } catch {
52
- return null;
53
- }
54
- }
55
- function ensureAppHomeMigrated() {
56
- const configPath = getConfigPath();
57
- if (existsSync(configPath)) return;
58
- const legacyConfig = join2(getLegacyAppHome(), "config.json");
59
- if (!existsSync(legacyConfig)) return;
60
- mkdirSync(getAppHome(), { recursive: true, mode: 448 });
61
- copyFileSync(legacyConfig, configPath);
62
- const legacyVertex = join2(getLegacyAppHome(), "vertex-models.json");
63
- const vertexPath = join2(getAppHome(), "vertex-models.json");
64
- if (existsSync(legacyVertex) && !existsSync(vertexPath)) {
65
- copyFileSync(legacyVertex, vertexPath);
66
- }
67
- }
68
- function ensureConfigMigrated() {
69
- ensureAppHomeMigrated();
70
- const configPath = getConfigPath();
71
- if (existsSync(configPath)) return;
72
- const legacyPath = getLegacyConfPath();
73
- if (!existsSync(legacyPath)) return;
74
- const legacy = readJsonFile(legacyPath);
75
- if (!legacy) return;
76
- mkdirSync(dirname(configPath), { recursive: true, mode: 448 });
77
- writeFileSync(configPath, `${JSON.stringify(legacy, null, 2)}
78
- `, { encoding: "utf8", mode: 384 });
79
- try {
80
- renameSync(legacyPath, `${legacyPath}.migrated`);
81
- } catch {
82
- }
83
- }
84
- function readConfig() {
85
- ensureConfigMigrated();
86
- return readJsonFile(getConfigPath()) ?? {};
87
- }
88
- function loadPreferences() {
89
- const config = readConfig();
90
- const lastProvider = config.lastProvider === "opencode" ? "zen" : config.lastProvider;
91
- return {
92
- lastBackend: config.lastBackend,
93
- lastModel: config.lastModel,
94
- lastProvider,
95
- lastCodexProvider: config.lastCodexProvider,
96
- lastCodexModel: config.lastCodexModel,
97
- lastGeminiProvider: config.lastGeminiProvider,
98
- lastGeminiModel: config.lastGeminiModel,
99
- lastAntigravityProvider: config.lastAntigravityProvider,
100
- lastAntigravityModel: config.lastAntigravityModel,
101
- lastClaudeTransparentMode: config.lastClaudeTransparentMode,
102
- recentModelsByProvider: config.recentModelsByProvider,
103
- favoriteModels: config.favoriteModels,
104
- antigravityCliFavoriteModels: config.antigravityCliFavoriteModels,
105
- antigravityCliFavoritesHintShown: config.antigravityCliFavoritesHintShown,
106
- appPathOverrides: config.appPathOverrides,
107
- recentLaunchFolders: config.recentLaunchFolders,
108
- server: config.server
109
- };
110
- }
111
-
112
- // src/provider-factory.ts
113
- import { wrapLanguageModel, extractReasoningMiddleware } from "ai";
114
-
115
46
  // src/constants.ts
116
47
  import { homedir as homedir2 } from "os";
117
- import { join as join3 } from "path";
48
+ import { join as join2 } from "path";
118
49
 
119
50
  // package.json
120
51
  var package_default = {
121
52
  name: "@jacobbd/relay-ai",
122
- version: "0.7.6",
53
+ version: "0.9.0",
123
54
  publishConfig: {
124
55
  access: "public"
125
56
  },
@@ -202,10 +133,11 @@ var package_default = {
202
133
  "@types/node": "^22.0.0",
203
134
  "@types/node-forge": "^1.3.14",
204
135
  "@types/ws": "^8.18.1",
205
- "@vitest/coverage-v8": "^2.1.9",
136
+ "@vitest/coverage-v8": "^4.1.10",
206
137
  tsup: "^8.0.0",
207
138
  typescript: "^5.5.0",
208
- vitest: "^2.0.0"
139
+ "vite-node": "^6.0.0",
140
+ vitest: "^4.1.10"
209
141
  },
210
142
  optionalDependencies: {
211
143
  "@napi-rs/keyring": "^1.3.0"
@@ -227,10 +159,81 @@ var package_default = {
227
159
  var CODEX_RESPONSES_LITE_WS_URL = "wss://chatgpt.com/backend-api/codex/responses";
228
160
  var CODEX_RESPONSES_LITE_VERSION = "0.144.1";
229
161
  var CODEX_RESPONSES_WEBSOCKETS_BETA = "responses_websockets=2026-02-06";
230
- var OPENCODE_CACHE_PATH = join3(homedir2(), ".cache", "opencode", "models.json");
162
+ var OPENCODE_CACHE_PATH = join2(homedir2(), ".cache", "opencode", "models.json");
163
+ var CODEX_SUBAGENT_MODEL_CAP = 1;
231
164
  var VERTEX_ANTHROPIC_NPM = "@ai-sdk/google-vertex/anthropic";
232
165
  var VERSION = package_default.version;
233
166
 
167
+ // src/config.ts
168
+ function readJsonFile(path) {
169
+ try {
170
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
171
+ return parsed && typeof parsed === "object" ? parsed : null;
172
+ } catch {
173
+ return null;
174
+ }
175
+ }
176
+ function ensureAppHomeMigrated() {
177
+ const configPath = getConfigPath();
178
+ if (existsSync(configPath)) return;
179
+ const legacyConfig = join3(getLegacyAppHome(), "config.json");
180
+ if (!existsSync(legacyConfig)) return;
181
+ mkdirSync(getAppHome(), { recursive: true, mode: 448 });
182
+ copyFileSync(legacyConfig, configPath);
183
+ const legacyVertex = join3(getLegacyAppHome(), "vertex-models.json");
184
+ const vertexPath = join3(getAppHome(), "vertex-models.json");
185
+ if (existsSync(legacyVertex) && !existsSync(vertexPath)) {
186
+ copyFileSync(legacyVertex, vertexPath);
187
+ }
188
+ }
189
+ function ensureConfigMigrated() {
190
+ ensureAppHomeMigrated();
191
+ const configPath = getConfigPath();
192
+ if (existsSync(configPath)) return;
193
+ const legacyPath = getLegacyConfPath();
194
+ if (!existsSync(legacyPath)) return;
195
+ const legacy = readJsonFile(legacyPath);
196
+ if (!legacy) return;
197
+ mkdirSync(dirname(configPath), { recursive: true, mode: 448 });
198
+ writeFileSync(configPath, `${JSON.stringify(legacy, null, 2)}
199
+ `, { encoding: "utf8", mode: 384 });
200
+ try {
201
+ renameSync(legacyPath, `${legacyPath}.migrated`);
202
+ } catch {
203
+ }
204
+ }
205
+ function readConfig() {
206
+ ensureConfigMigrated();
207
+ return readJsonFile(getConfigPath()) ?? {};
208
+ }
209
+ function loadPreferences() {
210
+ const config = readConfig();
211
+ const lastProvider = config.lastProvider === "opencode" ? "zen" : config.lastProvider;
212
+ return {
213
+ lastBackend: config.lastBackend,
214
+ lastModel: config.lastModel,
215
+ lastProvider,
216
+ lastCodexProvider: config.lastCodexProvider,
217
+ lastCodexModel: config.lastCodexModel,
218
+ lastGeminiProvider: config.lastGeminiProvider,
219
+ lastGeminiModel: config.lastGeminiModel,
220
+ lastAntigravityProvider: config.lastAntigravityProvider,
221
+ lastAntigravityModel: config.lastAntigravityModel,
222
+ lastClaudeTransparentMode: config.lastClaudeTransparentMode,
223
+ recentModelsByProvider: config.recentModelsByProvider,
224
+ favoriteModels: config.favoriteModels,
225
+ codexSubagentModels: Array.isArray(config.codexSubagentModels) ? config.codexSubagentModels.slice(0, CODEX_SUBAGENT_MODEL_CAP) : void 0,
226
+ antigravityCliFavoriteModels: config.antigravityCliFavoriteModels,
227
+ antigravityCliFavoritesHintShown: config.antigravityCliFavoritesHintShown,
228
+ appPathOverrides: config.appPathOverrides,
229
+ recentLaunchFolders: config.recentLaunchFolders,
230
+ server: config.server
231
+ };
232
+ }
233
+
234
+ // src/provider-factory.ts
235
+ import { wrapLanguageModel, extractReasoningMiddleware } from "ai";
236
+
234
237
  // src/oauth/refresh-http.ts
235
238
  async function postOAuthRefresh(url, body, options) {
236
239
  const isJson = options.contentType === "json";
@@ -487,6 +490,43 @@ function injectClaudeIdentity(body, providerData, seed) {
487
490
  return { sessionId, userId };
488
491
  }
489
492
 
493
+ // src/cline-pass.ts
494
+ var CLINE_PASS_HOST = "https://api.cline.bot";
495
+ var CLINE_PASS_SDK_BASE_URL = `${CLINE_PASS_HOST}/api/v1`;
496
+ var CLINE_PASS_CATALOG_URL = `${CLINE_PASS_HOST}/api/v1/ai/cline/recommended-models`;
497
+ var CLINE_PASS_VALIDATION_URL = `${CLINE_PASS_HOST}/api/v1/users/me`;
498
+ var CLINE_PASS_REGISTER_URL = `${CLINE_PASS_HOST}/api/v1/auth/register`;
499
+ var CLINE_PASS_REFRESH_URL = `${CLINE_PASS_HOST}/api/v1/auth/refresh`;
500
+ var CLINE_PASS_WORKOS_PREFIX = "workos:";
501
+ function isClinePassOAuth(providerId, authType) {
502
+ return providerId === "cline-pass" && authType === "oauth";
503
+ }
504
+ function formatClineRuntimeCredential(providerId, authType, key) {
505
+ if (!isClinePassOAuth(providerId, authType)) return key;
506
+ return key.toLowerCase().startsWith(CLINE_PASS_WORKOS_PREFIX) ? key : `${CLINE_PASS_WORKOS_PREFIX}${key}`;
507
+ }
508
+ function createClinePassOAuthFetch(initialRuntimeCredential, refreshToken, onTokenRefreshed, fetchImpl = globalThis.fetch) {
509
+ let currentRuntimeCredential = initialRuntimeCredential;
510
+ return async (input, init) => {
511
+ const request = new Request(input, init);
512
+ const send = (runtimeCredential) => {
513
+ const headers = new Headers(request.headers);
514
+ headers.set("Authorization", `Bearer ${runtimeCredential}`);
515
+ return fetchImpl(request.clone(), { headers });
516
+ };
517
+ const response = await send(currentRuntimeCredential);
518
+ if (response.status !== 401) return response;
519
+ const refreshedRawToken = await refreshToken().catch(() => null);
520
+ const refreshedRuntimeCredential = refreshedRawToken ? formatClineRuntimeCredential("cline-pass", "oauth", refreshedRawToken) : null;
521
+ if (!refreshedRawToken || !refreshedRuntimeCredential || refreshedRuntimeCredential === currentRuntimeCredential) {
522
+ return response;
523
+ }
524
+ currentRuntimeCredential = refreshedRuntimeCredential;
525
+ onTokenRefreshed?.(refreshedRawToken);
526
+ return send(currentRuntimeCredential);
527
+ };
528
+ }
529
+
490
530
  // src/provider-factory.ts
491
531
  var RESPONSES_ONLY_PREFIXES = [
492
532
  "gpt-5-codex",
@@ -615,11 +655,19 @@ async function createLanguageModel(spec) {
615
655
  let model;
616
656
  if (npm === "@ai-sdk/openai-compatible") {
617
657
  const { createOpenAICompatible } = await import("@ai-sdk/openai-compatible");
658
+ const runtimeApiKey = formatClineRuntimeCredential(spec.providerId, spec.authType, apiKey);
618
659
  const options = {
619
660
  name: spec.providerId ?? "openai-compatible",
620
661
  baseURL: baseURL ?? "",
621
- ...apiKey.trim() ? { apiKey } : {},
622
- ...spec.headers ? { headers: spec.headers } : {}
662
+ ...runtimeApiKey.trim() ? { apiKey: runtimeApiKey } : {},
663
+ ...spec.headers ? { headers: spec.headers } : {},
664
+ ...isClinePassOAuth(spec.providerId, spec.authType) && spec.refreshToken ? {
665
+ fetch: createClinePassOAuthFetch(
666
+ runtimeApiKey,
667
+ spec.refreshToken,
668
+ spec.onTokenRefreshed
669
+ )
670
+ } : {}
623
671
  };
624
672
  model = createOpenAICompatible({
625
673
  ...options
@@ -1296,7 +1344,7 @@ function accessTokenIsExpiring(token, skewMs = OAUTH_REFRESH_SKEW_MS) {
1296
1344
  return false;
1297
1345
  }
1298
1346
  }
1299
- var NATIVE_OAUTH_PROVIDER_IDS = ["xai", "xai-oauth", "openai", "openai-oauth", "github-copilot", "claude-code", "antigravity"];
1347
+ var NATIVE_OAUTH_PROVIDER_IDS = ["xai", "xai-oauth", "openai", "openai-oauth", "github-copilot", "claude-code", "antigravity", "cline-pass"];
1300
1348
 
1301
1349
  // src/oauth/github.ts
1302
1350
  var COPILOT_TOKEN_URL = "https://api.github.com/copilot_internal/v2/token";
@@ -1482,6 +1530,66 @@ async function refreshAntigravityToken(refreshToken) {
1482
1530
  );
1483
1531
  }
1484
1532
 
1533
+ // src/oauth/cline-pass.ts
1534
+ var DEFAULT_EXPIRES_MS = 10 * 60 * 1e3;
1535
+ function jsonHeaders() {
1536
+ return {
1537
+ Accept: "application/json",
1538
+ "Content-Type": "application/json"
1539
+ };
1540
+ }
1541
+ function expiresInFromIso(expiresAt) {
1542
+ if (typeof expiresAt !== "string") throw new Error("ClinePass response is missing a valid expiresAt");
1543
+ const timestamp = Date.parse(expiresAt);
1544
+ if (!Number.isFinite(timestamp)) throw new Error("ClinePass response is missing a valid expiresAt");
1545
+ return Math.max(1, Math.floor((timestamp - Date.now()) / 1e3));
1546
+ }
1547
+ function toOAuthResult(data) {
1548
+ if (typeof data.accessToken !== "string" || !data.accessToken) {
1549
+ throw new Error("ClinePass response is missing accessToken");
1550
+ }
1551
+ const userInfo = data.userInfo && typeof data.userInfo === "object" && !Array.isArray(data.userInfo) ? data.userInfo : void 0;
1552
+ const accountId = typeof userInfo?.clineUserId === "string" ? userInfo.clineUserId : void 0;
1553
+ return {
1554
+ tokens: {
1555
+ access_token: data.accessToken,
1556
+ ...typeof data.refreshToken === "string" ? { refresh_token: data.refreshToken } : {},
1557
+ expires_in: expiresInFromIso(data.expiresAt),
1558
+ ...userInfo ? { providerData: userInfo } : {}
1559
+ },
1560
+ ...accountId ? { accountId } : {},
1561
+ ...userInfo ? { providerData: userInfo } : {}
1562
+ };
1563
+ }
1564
+ async function readError(response) {
1565
+ const text = await response.text().catch(() => "");
1566
+ if (!text) return `HTTP ${response.status}`;
1567
+ try {
1568
+ const parsed = JSON.parse(text);
1569
+ const detail = typeof parsed.error === "string" ? parsed.error : typeof parsed.message === "string" ? parsed.message : "";
1570
+ return detail || `HTTP ${response.status}`;
1571
+ } catch {
1572
+ return text.slice(0, 120);
1573
+ }
1574
+ }
1575
+ async function refreshClinePassAccessToken(refreshToken) {
1576
+ const response = await fetch(CLINE_PASS_REFRESH_URL, {
1577
+ method: "POST",
1578
+ headers: jsonHeaders(),
1579
+ body: JSON.stringify({ refreshToken, grantType: "refresh_token" })
1580
+ });
1581
+ if (!response.ok) {
1582
+ const detail = await readError(response);
1583
+ throw new Error(`ClinePass token refresh failed (${response.status}): ${detail}`);
1584
+ }
1585
+ const body = await response.json();
1586
+ if (body.success !== true || !body.data) {
1587
+ const detail = typeof body.error === "string" ? body.error : typeof body.message === "string" ? body.message : "unsuccessful response";
1588
+ throw new Error(`ClinePass token refresh failed: ${detail}`);
1589
+ }
1590
+ return toOAuthResult(body.data).tokens;
1591
+ }
1592
+
1485
1593
  // src/oauth/refresh.ts
1486
1594
  function oauthCredentialShouldRefresh(cred, providerId) {
1487
1595
  if (oauthCredentialNeedsRefresh(cred)) return true;
@@ -1503,10 +1611,13 @@ async function refreshStoredOAuthCredential(providerId, cred) {
1503
1611
  tokens = await refreshClaudeCodeToken(cred.refresh);
1504
1612
  } else if (providerId === "antigravity") {
1505
1613
  tokens = await refreshAntigravityToken(cred.refresh);
1614
+ } else if (providerId === "cline-pass") {
1615
+ tokens = await refreshClinePassAccessToken(cred.refresh);
1506
1616
  } else {
1507
1617
  throw new Error(`OAuth refresh not implemented for provider "${providerId}"`);
1508
1618
  }
1509
- return tokensToStoredCredential(tokens, cred.refresh, cred.accountId, cred.providerData);
1619
+ const accountId = providerId === "cline-pass" && typeof tokens.providerData?.clineUserId === "string" ? tokens.providerData.clineUserId : cred.accountId;
1620
+ return tokensToStoredCredential(tokens, cred.refresh, accountId, cred.providerData);
1510
1621
  }
1511
1622
 
1512
1623
  // src/secrets-file.ts
@@ -1602,6 +1713,9 @@ var KEYRING_CHUNK_SIZE = 1200;
1602
1713
  var LEGACY_KEYRING_SERVICE = "opencode-starter";
1603
1714
  var LEGACY_KEYRING_ACCOUNT = "opencode-starter";
1604
1715
  var GLOBAL_OPENCODE_KEYRING_ACCOUNT = "global:opencode";
1716
+ function providerKeyringAccount(providerId) {
1717
+ return `provider:${providerId}`;
1718
+ }
1605
1719
  function oauthProviderIdFromAccount(account) {
1606
1720
  const prefix = "oauth:provider:";
1607
1721
  return account.startsWith(prefix) ? account.slice(prefix.length) : null;
@@ -1693,10 +1807,14 @@ async function readGlobalOpencodeCredential(diag) {
1693
1807
  }
1694
1808
  }
1695
1809
  async function resolveProviderCredential(providerId, authRef, diag) {
1696
- const namespaced = readEnvCredential(relayAiKeyEnvVar(providerId));
1697
- if (namespaced) return namespaced;
1698
1810
  const parsed = parseAuthRef(authRef);
1699
1811
  if (!parsed) return null;
1812
+ if (parsed.kind === "keyring" && parsed.account === GLOBAL_OPENCODE_KEYRING_ACCOUNT) {
1813
+ const relayOverride = await readProviderSecret(providerKeyringAccount("opencode"), diag);
1814
+ if (relayOverride) return relayOverride;
1815
+ }
1816
+ const namespaced = readEnvCredential(relayAiKeyEnvVar(providerId));
1817
+ if (namespaced) return namespaced;
1700
1818
  if (parsed.kind === "env") {
1701
1819
  return readEnvCredential(parsed.varName);
1702
1820
  }
@@ -1705,6 +1823,22 @@ async function resolveProviderCredential(providerId, authRef, diag) {
1705
1823
  }
1706
1824
  return readProviderSecret(parsed.account, diag);
1707
1825
  }
1826
+ async function forceRefreshProviderCredential(providerId, authRef, diag) {
1827
+ const parsed = parseAuthRef(authRef);
1828
+ if (!parsed || parsed.kind !== "keyring") {
1829
+ return resolveProviderCredential(providerId, authRef, diag);
1830
+ }
1831
+ if (parsed.account === GLOBAL_OPENCODE_KEYRING_ACCOUNT) {
1832
+ const relayOverride = await readProviderSecret(providerKeyringAccount("opencode"), diag);
1833
+ if (relayOverride) return relayOverride;
1834
+ }
1835
+ const namespaced = readEnvCredential(relayAiKeyEnvVar(providerId));
1836
+ if (namespaced) return namespaced;
1837
+ const oauthProviderId = oauthProviderIdFromAccount(parsed.account);
1838
+ const raw = await readKeyringAccount(parsed.account, diag);
1839
+ if (!raw || !oauthProviderId) return decodeProviderSecret(raw);
1840
+ return refreshOAuthKeyringAccount(parsed.account, oauthProviderId, raw, diag, true);
1841
+ }
1708
1842
  async function resolveProviderOAuthAccountId(authRef, diag) {
1709
1843
  const parsed = parseAuthRef(authRef);
1710
1844
  if (!parsed || parsed.kind !== "keyring" || !oauthProviderIdFromAccount(parsed.account)) return void 0;
@@ -1731,12 +1865,12 @@ function decodeProviderSecret(raw) {
1731
1865
  }
1732
1866
  return trimmed;
1733
1867
  }
1734
- async function refreshOAuthKeyringAccount(account, providerId, raw, diag) {
1868
+ async function refreshOAuthKeyringAccount(account, providerId, raw, diag, force = false) {
1735
1869
  const existing = oauthRefreshInflight.get(account);
1736
1870
  if (existing) return existing;
1737
1871
  const work = (async () => {
1738
1872
  const cred = parseStoredOAuthCredential(raw);
1739
- if (!cred || !oauthCredentialShouldRefresh(cred, providerId)) {
1873
+ if (!cred || !force && !oauthCredentialShouldRefresh(cred, providerId)) {
1740
1874
  return decodeProviderSecret(raw);
1741
1875
  }
1742
1876
  try {
@@ -1767,6 +1901,506 @@ async function readProviderSecret(account, diag) {
1767
1901
  return decodeProviderSecret(raw);
1768
1902
  }
1769
1903
 
1904
+ // src/data/model-incompatible.json
1905
+ var model_incompatible_default = {
1906
+ schema_version: "1",
1907
+ entries: [
1908
+ {
1909
+ provider: "google",
1910
+ modelId: "antigravity-preview-05-2026",
1911
+ category: "managed_agent",
1912
+ reason: "Interactions API only; coding agents send multiturn chat via @ai-sdk/google streamGenerateContent",
1913
+ sources: [
1914
+ "https://ai.google.dev/gemini-api/docs/models",
1915
+ "manual UAT 2026-06-10"
1916
+ ],
1917
+ verifiedAt: "2026-06-10"
1918
+ },
1919
+ {
1920
+ provider: "*",
1921
+ modelId: "z-ai/glm4.7",
1922
+ category: "gated_access",
1923
+ reason: "NVIDIA NIM requires separate access approval (HTTP 410)",
1924
+ sources: [
1925
+ "manual probe 2026-06"
1926
+ ],
1927
+ verifiedAt: "2026-06-10"
1928
+ },
1929
+ {
1930
+ provider: "*",
1931
+ modelId: "qwen3.6-plus-free",
1932
+ category: "stale_promotion",
1933
+ reason: "Free promotion ended; API returns 401",
1934
+ sources: [
1935
+ "OpenCode Zen catalog"
1936
+ ],
1937
+ verifiedAt: "2026-06-10"
1938
+ },
1939
+ {
1940
+ provider: "*",
1941
+ modelId: "mimo-v2-pro",
1942
+ category: "deprecated",
1943
+ reason: "Deprecated; API returns 400 \u2014 use mimo-v2.5-pro",
1944
+ sources: [
1945
+ "OpenCode Zen catalog"
1946
+ ],
1947
+ verifiedAt: "2026-06-10"
1948
+ },
1949
+ {
1950
+ provider: "*",
1951
+ modelId: "mimo-v2-omni",
1952
+ category: "deprecated",
1953
+ reason: "Deprecated; API returns 400 \u2014 use mimo-v2.5",
1954
+ sources: [
1955
+ "OpenCode Zen catalog"
1956
+ ],
1957
+ verifiedAt: "2026-06-10"
1958
+ },
1959
+ {
1960
+ provider: "google",
1961
+ modelId: "aqa",
1962
+ category: "managed_agent",
1963
+ reason: "Attributed QA model \u2014 not for coding agents",
1964
+ sources: [
1965
+ "https://ai.google.dev/gemini-api/docs/models",
1966
+ "Google GET /v1/models vs models.dev gap \u2014 UAT 2026-06-11"
1967
+ ],
1968
+ verifiedAt: "2026-06-11"
1969
+ },
1970
+ {
1971
+ provider: "google",
1972
+ modelId: "deep-research-max-preview-04-2026",
1973
+ category: "managed_agent",
1974
+ reason: "Specialized agent API \u2014 not standard coding chat",
1975
+ sources: [
1976
+ "https://ai.google.dev/gemini-api/docs/models",
1977
+ "Google GET /v1/models vs models.dev gap \u2014 UAT 2026-06-11"
1978
+ ],
1979
+ verifiedAt: "2026-06-11"
1980
+ },
1981
+ {
1982
+ provider: "google",
1983
+ modelId: "deep-research-preview-04-2026",
1984
+ category: "managed_agent",
1985
+ reason: "Specialized agent API \u2014 not standard coding chat",
1986
+ sources: [
1987
+ "https://ai.google.dev/gemini-api/docs/models",
1988
+ "Google GET /v1/models vs models.dev gap \u2014 UAT 2026-06-11"
1989
+ ],
1990
+ verifiedAt: "2026-06-11"
1991
+ },
1992
+ {
1993
+ provider: "google",
1994
+ modelId: "deep-research-pro-preview-12-2025",
1995
+ category: "managed_agent",
1996
+ reason: "Specialized agent API \u2014 not standard coding chat",
1997
+ sources: [
1998
+ "https://ai.google.dev/gemini-api/docs/models",
1999
+ "Google GET /v1/models vs models.dev gap \u2014 UAT 2026-06-11"
2000
+ ],
2001
+ verifiedAt: "2026-06-11"
2002
+ },
2003
+ {
2004
+ provider: "google",
2005
+ modelId: "gemini-2.5-computer-use-preview-10-2025",
2006
+ category: "managed_agent",
2007
+ reason: "Specialized agent API \u2014 not standard coding chat",
2008
+ sources: [
2009
+ "https://ai.google.dev/gemini-api/docs/models",
2010
+ "Google GET /v1/models vs models.dev gap \u2014 UAT 2026-06-11"
2011
+ ],
2012
+ verifiedAt: "2026-06-11"
2013
+ },
2014
+ {
2015
+ provider: "google",
2016
+ modelId: "gemini-2.5-flash-image",
2017
+ category: "image_generation",
2018
+ reason: "Image-output model \u2014 not for coding chat",
2019
+ sources: [
2020
+ "https://ai.google.dev/gemini-api/docs/models",
2021
+ "Google GET /v1/models vs models.dev gap \u2014 UAT 2026-06-11"
2022
+ ],
2023
+ verifiedAt: "2026-06-11"
2024
+ },
2025
+ {
2026
+ provider: "google",
2027
+ modelId: "gemini-2.5-flash-native-audio-latest",
2028
+ category: "audio_only",
2029
+ reason: "Audio/music output \u2014 not for coding chat",
2030
+ sources: [
2031
+ "https://ai.google.dev/gemini-api/docs/models",
2032
+ "Google GET /v1/models vs models.dev gap \u2014 UAT 2026-06-11"
2033
+ ],
2034
+ verifiedAt: "2026-06-11"
2035
+ },
2036
+ {
2037
+ provider: "google",
2038
+ modelId: "gemini-2.5-flash-native-audio-preview-09-2025",
2039
+ category: "audio_only",
2040
+ reason: "Audio/music output \u2014 not for coding chat",
2041
+ sources: [
2042
+ "https://ai.google.dev/gemini-api/docs/models",
2043
+ "Google GET /v1/models vs models.dev gap \u2014 UAT 2026-06-11"
2044
+ ],
2045
+ verifiedAt: "2026-06-11"
2046
+ },
2047
+ {
2048
+ provider: "google",
2049
+ modelId: "gemini-2.5-flash-native-audio-preview-12-2025",
2050
+ category: "audio_only",
2051
+ reason: "Audio/music output \u2014 not for coding chat",
2052
+ sources: [
2053
+ "https://ai.google.dev/gemini-api/docs/models",
2054
+ "Google GET /v1/models vs models.dev gap \u2014 UAT 2026-06-11"
2055
+ ],
2056
+ verifiedAt: "2026-06-11"
2057
+ },
2058
+ {
2059
+ provider: "google",
2060
+ modelId: "gemini-2.5-flash-preview-tts",
2061
+ category: "audio_only",
2062
+ reason: "Audio/music output \u2014 not for coding chat",
2063
+ sources: [
2064
+ "https://ai.google.dev/gemini-api/docs/models",
2065
+ "Google GET /v1/models vs models.dev gap \u2014 UAT 2026-06-11"
2066
+ ],
2067
+ verifiedAt: "2026-06-11"
2068
+ },
2069
+ {
2070
+ provider: "google",
2071
+ modelId: "gemini-2.5-pro-preview-tts",
2072
+ category: "audio_only",
2073
+ reason: "Audio/music output \u2014 not for coding chat",
2074
+ sources: [
2075
+ "https://ai.google.dev/gemini-api/docs/models",
2076
+ "Google GET /v1/models vs models.dev gap \u2014 UAT 2026-06-11"
2077
+ ],
2078
+ verifiedAt: "2026-06-11"
2079
+ },
2080
+ {
2081
+ provider: "google",
2082
+ modelId: "gemini-3-pro-preview",
2083
+ category: "deprecated",
2084
+ reason: "Retired preview; API returns 404 \u2014 use gemini-3.1-pro-preview or newer",
2085
+ sources: [
2086
+ "https://generativelanguage.googleapis.com/v1beta/models/gemini-3-pro-preview",
2087
+ "manual UAT 2026-06-11"
2088
+ ],
2089
+ verifiedAt: "2026-06-11"
2090
+ },
2091
+ {
2092
+ provider: "google",
2093
+ modelId: "gemini-3-pro-image",
2094
+ category: "image_generation",
2095
+ reason: "Image-output model \u2014 not for coding chat",
2096
+ sources: [
2097
+ "https://ai.google.dev/gemini-api/docs/models",
2098
+ "Google GET /v1/models vs models.dev gap \u2014 UAT 2026-06-11"
2099
+ ],
2100
+ verifiedAt: "2026-06-11"
2101
+ },
2102
+ {
2103
+ provider: "google",
2104
+ modelId: "gemini-3-pro-image-preview",
2105
+ category: "image_generation",
2106
+ reason: "Image-output model \u2014 not for coding chat",
2107
+ sources: [
2108
+ "https://ai.google.dev/gemini-api/docs/models",
2109
+ "Google GET /v1/models vs models.dev gap \u2014 UAT 2026-06-11"
2110
+ ],
2111
+ verifiedAt: "2026-06-11"
2112
+ },
2113
+ {
2114
+ provider: "google",
2115
+ modelId: "gemini-3.1-flash-image",
2116
+ category: "image_generation",
2117
+ reason: "Image-output model \u2014 not for coding chat",
2118
+ sources: [
2119
+ "https://ai.google.dev/gemini-api/docs/models",
2120
+ "Google GET /v1/models vs models.dev gap \u2014 UAT 2026-06-11"
2121
+ ],
2122
+ verifiedAt: "2026-06-11"
2123
+ },
2124
+ {
2125
+ provider: "google",
2126
+ modelId: "gemini-3.1-flash-image-preview",
2127
+ category: "image_generation",
2128
+ reason: "Image-output model \u2014 not for coding chat",
2129
+ sources: [
2130
+ "https://ai.google.dev/gemini-api/docs/models",
2131
+ "Google GET /v1/models vs models.dev gap \u2014 UAT 2026-06-11"
2132
+ ],
2133
+ verifiedAt: "2026-06-11"
2134
+ },
2135
+ {
2136
+ provider: "google",
2137
+ modelId: "gemini-3.1-flash-live-preview",
2138
+ category: "managed_agent",
2139
+ reason: "Live/session API \u2014 not for Codex multiturn chat",
2140
+ sources: [
2141
+ "https://ai.google.dev/gemini-api/docs/models",
2142
+ "Google GET /v1/models vs models.dev gap \u2014 UAT 2026-06-11"
2143
+ ],
2144
+ verifiedAt: "2026-06-11"
2145
+ },
2146
+ {
2147
+ provider: "google",
2148
+ modelId: "gemini-3.1-flash-tts-preview",
2149
+ category: "audio_only",
2150
+ reason: "Audio/music output \u2014 not for coding chat",
2151
+ sources: [
2152
+ "https://ai.google.dev/gemini-api/docs/models",
2153
+ "Google GET /v1/models vs models.dev gap \u2014 UAT 2026-06-11"
2154
+ ],
2155
+ verifiedAt: "2026-06-11"
2156
+ },
2157
+ {
2158
+ provider: "google",
2159
+ modelId: "gemini-3.5-live-translate-preview",
2160
+ category: "managed_agent",
2161
+ reason: "Live/session API \u2014 not for Codex multiturn chat",
2162
+ sources: [
2163
+ "https://ai.google.dev/gemini-api/docs/models",
2164
+ "Google GET /v1/models vs models.dev gap \u2014 UAT 2026-06-11"
2165
+ ],
2166
+ verifiedAt: "2026-06-11"
2167
+ },
2168
+ {
2169
+ provider: "google",
2170
+ modelId: "gemini-embedding-001",
2171
+ category: "embedding",
2172
+ reason: "Embedding model \u2014 not for chat or tools",
2173
+ sources: [
2174
+ "https://ai.google.dev/gemini-api/docs/models",
2175
+ "Google GET /v1/models vs models.dev gap \u2014 UAT 2026-06-11"
2176
+ ],
2177
+ verifiedAt: "2026-06-11"
2178
+ },
2179
+ {
2180
+ provider: "google",
2181
+ modelId: "gemini-embedding-2",
2182
+ category: "embedding",
2183
+ reason: "Embedding model \u2014 not for chat or tools",
2184
+ sources: [
2185
+ "https://ai.google.dev/gemini-api/docs/models",
2186
+ "Google GET /v1/models vs models.dev gap \u2014 UAT 2026-06-11"
2187
+ ],
2188
+ verifiedAt: "2026-06-11"
2189
+ },
2190
+ {
2191
+ provider: "google",
2192
+ modelId: "gemini-embedding-2-preview",
2193
+ category: "embedding",
2194
+ reason: "Embedding model \u2014 not for chat or tools",
2195
+ sources: [
2196
+ "https://ai.google.dev/gemini-api/docs/models",
2197
+ "Google GET /v1/models vs models.dev gap \u2014 UAT 2026-06-11"
2198
+ ],
2199
+ verifiedAt: "2026-06-11"
2200
+ },
2201
+ {
2202
+ provider: "google",
2203
+ modelId: "gemini-robotics-er-1.5-preview",
2204
+ category: "managed_agent",
2205
+ reason: "Specialized agent API \u2014 not standard coding chat",
2206
+ sources: [
2207
+ "https://ai.google.dev/gemini-api/docs/models",
2208
+ "Google GET /v1/models vs models.dev gap \u2014 UAT 2026-06-11"
2209
+ ],
2210
+ verifiedAt: "2026-06-11"
2211
+ },
2212
+ {
2213
+ provider: "google",
2214
+ modelId: "gemini-robotics-er-1.6-preview",
2215
+ category: "managed_agent",
2216
+ reason: "Specialized agent API \u2014 not standard coding chat",
2217
+ sources: [
2218
+ "https://ai.google.dev/gemini-api/docs/models",
2219
+ "Google GET /v1/models vs models.dev gap \u2014 UAT 2026-06-11"
2220
+ ],
2221
+ verifiedAt: "2026-06-11"
2222
+ },
2223
+ {
2224
+ provider: "google",
2225
+ modelId: "imagen-4.0-fast-generate-001",
2226
+ category: "image_generation",
2227
+ reason: "Image generation \u2014 not for coding chat",
2228
+ sources: [
2229
+ "https://ai.google.dev/gemini-api/docs/models",
2230
+ "Google GET /v1/models vs models.dev gap \u2014 UAT 2026-06-11"
2231
+ ],
2232
+ verifiedAt: "2026-06-11"
2233
+ },
2234
+ {
2235
+ provider: "google",
2236
+ modelId: "imagen-4.0-generate-001",
2237
+ category: "image_generation",
2238
+ reason: "Image generation \u2014 not for coding chat",
2239
+ sources: [
2240
+ "https://ai.google.dev/gemini-api/docs/models",
2241
+ "Google GET /v1/models vs models.dev gap \u2014 UAT 2026-06-11"
2242
+ ],
2243
+ verifiedAt: "2026-06-11"
2244
+ },
2245
+ {
2246
+ provider: "google",
2247
+ modelId: "imagen-4.0-ultra-generate-001",
2248
+ category: "image_generation",
2249
+ reason: "Image generation \u2014 not for coding chat",
2250
+ sources: [
2251
+ "https://ai.google.dev/gemini-api/docs/models",
2252
+ "Google GET /v1/models vs models.dev gap \u2014 UAT 2026-06-11"
2253
+ ],
2254
+ verifiedAt: "2026-06-11"
2255
+ },
2256
+ {
2257
+ provider: "google",
2258
+ modelId: "lyria-3-clip-preview",
2259
+ category: "audio_only",
2260
+ reason: "Audio/music output \u2014 not for coding chat",
2261
+ sources: [
2262
+ "https://ai.google.dev/gemini-api/docs/models",
2263
+ "Google GET /v1/models vs models.dev gap \u2014 UAT 2026-06-11"
2264
+ ],
2265
+ verifiedAt: "2026-06-11"
2266
+ },
2267
+ {
2268
+ provider: "google",
2269
+ modelId: "lyria-3-pro-preview",
2270
+ category: "audio_only",
2271
+ reason: "Audio/music output \u2014 not for coding chat",
2272
+ sources: [
2273
+ "https://ai.google.dev/gemini-api/docs/models",
2274
+ "Google GET /v1/models vs models.dev gap \u2014 UAT 2026-06-11"
2275
+ ],
2276
+ verifiedAt: "2026-06-11"
2277
+ },
2278
+ {
2279
+ provider: "google",
2280
+ modelId: "lyria-realtime-exp",
2281
+ category: "audio_only",
2282
+ reason: "Audio/music output \u2014 not for coding chat",
2283
+ sources: [
2284
+ "https://ai.google.dev/gemini-api/docs/models",
2285
+ "Google GET /v1/models vs models.dev gap \u2014 UAT 2026-06-11"
2286
+ ],
2287
+ verifiedAt: "2026-06-11"
2288
+ },
2289
+ {
2290
+ provider: "google",
2291
+ modelId: "nano-banana-pro-preview",
2292
+ category: "image_generation",
2293
+ reason: "Image generation \u2014 not for coding chat",
2294
+ sources: [
2295
+ "https://ai.google.dev/gemini-api/docs/models",
2296
+ "Google GET /v1/models vs models.dev gap \u2014 UAT 2026-06-11"
2297
+ ],
2298
+ verifiedAt: "2026-06-11"
2299
+ },
2300
+ {
2301
+ provider: "google",
2302
+ modelId: "veo-2.0-generate-001",
2303
+ category: "video_generation",
2304
+ reason: "Video generation \u2014 not for coding chat",
2305
+ sources: [
2306
+ "https://ai.google.dev/gemini-api/docs/models",
2307
+ "Google GET /v1/models vs models.dev gap \u2014 UAT 2026-06-11"
2308
+ ],
2309
+ verifiedAt: "2026-06-11"
2310
+ },
2311
+ {
2312
+ provider: "google",
2313
+ modelId: "veo-3.0-fast-generate-001",
2314
+ category: "video_generation",
2315
+ reason: "Video generation \u2014 not for coding chat",
2316
+ sources: [
2317
+ "https://ai.google.dev/gemini-api/docs/models",
2318
+ "Google GET /v1/models vs models.dev gap \u2014 UAT 2026-06-11"
2319
+ ],
2320
+ verifiedAt: "2026-06-11"
2321
+ },
2322
+ {
2323
+ provider: "google",
2324
+ modelId: "veo-3.0-generate-001",
2325
+ category: "video_generation",
2326
+ reason: "Video generation \u2014 not for coding chat",
2327
+ sources: [
2328
+ "https://ai.google.dev/gemini-api/docs/models",
2329
+ "Google GET /v1/models vs models.dev gap \u2014 UAT 2026-06-11"
2330
+ ],
2331
+ verifiedAt: "2026-06-11"
2332
+ },
2333
+ {
2334
+ provider: "google",
2335
+ modelId: "veo-3.1-fast-generate-preview",
2336
+ category: "video_generation",
2337
+ reason: "Video generation \u2014 not for coding chat",
2338
+ sources: [
2339
+ "https://ai.google.dev/gemini-api/docs/models",
2340
+ "Google GET /v1/models vs models.dev gap \u2014 UAT 2026-06-11"
2341
+ ],
2342
+ verifiedAt: "2026-06-11"
2343
+ },
2344
+ {
2345
+ provider: "google",
2346
+ modelId: "veo-3.1-generate-preview",
2347
+ category: "video_generation",
2348
+ reason: "Video generation \u2014 not for coding chat",
2349
+ sources: [
2350
+ "https://ai.google.dev/gemini-api/docs/models",
2351
+ "Google GET /v1/models vs models.dev gap \u2014 UAT 2026-06-11"
2352
+ ],
2353
+ verifiedAt: "2026-06-11"
2354
+ },
2355
+ {
2356
+ provider: "google",
2357
+ modelId: "veo-3.1-lite-generate-preview",
2358
+ category: "video_generation",
2359
+ reason: "Video generation \u2014 not for coding chat",
2360
+ sources: [
2361
+ "https://ai.google.dev/gemini-api/docs/models",
2362
+ "Google GET /v1/models vs models.dev gap \u2014 UAT 2026-06-11"
2363
+ ],
2364
+ verifiedAt: "2026-06-11"
2365
+ }
2366
+ ]
2367
+ };
2368
+
2369
+ // src/registry/models-dev.ts
2370
+ import {
2371
+ chmodSync as chmodSync4,
2372
+ existsSync as existsSync6,
2373
+ mkdirSync as mkdirSync5,
2374
+ readFileSync as readFileSync8,
2375
+ statSync as statSync2,
2376
+ writeFileSync as writeFileSync4
2377
+ } from "fs";
2378
+ import { dirname as dirname4, join as join6 } from "path";
2379
+
2380
+ // src/registry/pricing.ts
2381
+ import {
2382
+ chmodSync as chmodSync3,
2383
+ existsSync as existsSync5,
2384
+ mkdirSync as mkdirSync4,
2385
+ readFileSync as readFileSync7,
2386
+ writeFileSync as writeFileSync3
2387
+ } from "fs";
2388
+ import { dirname as dirname3, join as join5 } from "path";
2389
+
2390
+ // src/model-compatibility.ts
2391
+ var BLACKLIST_ENTRIES = model_incompatible_default.entries ?? [];
2392
+
2393
+ // src/registry/import-build.ts
2394
+ function oauthAuthRef(providerId) {
2395
+ return `keyring:oauth:provider:${providerId}`;
2396
+ }
2397
+
2398
+ // src/provider-runtime.ts
2399
+ function providerRefreshToken(providerId, authType, authRef) {
2400
+ if (authType !== "oauth" || !providerId) return void 0;
2401
+ return () => forceRefreshProviderCredential(providerId, authRef ?? oauthAuthRef(providerId));
2402
+ }
2403
+
1770
2404
  // src/core/model.ts
1771
2405
  function findRoute(registry, providerId, modelId, routeId) {
1772
2406
  const provider = registry.providers.find((p) => p.id === providerId);
@@ -1833,6 +2467,7 @@ async function createRelayModel(routeId) {
1833
2467
  oauthAccountId,
1834
2468
  providerData,
1835
2469
  headers: provider.api.headers,
2470
+ refreshToken: providerRefreshToken(provider.id, provider.authType, provider.authRef),
1836
2471
  useResponsesLite: model.useResponsesLite,
1837
2472
  preferWebSockets: model.preferWebSockets
1838
2473
  };