@jacobbd/relay-ai 0.11.1 → 0.12.1

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.
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  getTemplateById,
4
4
  init_provider_templates
5
- } from "./chunk-TRM2WGI6.js";
5
+ } from "./chunk-3R25QO5X.js";
6
6
  import {
7
7
  ANTIGRAVITY_BASE_URLS,
8
8
  BACKENDS,
@@ -24,6 +24,7 @@ import {
24
24
  classifyModelFormat,
25
25
  claudeCodeClientModelId,
26
26
  claudeModelFamily,
27
+ contextWindowFromHeuristics,
27
28
  createLanguageModel,
28
29
  customProviderId,
29
30
  deleteProviderCredential,
@@ -49,7 +50,6 @@ import {
49
50
  isOpencodeOAuth,
50
51
  isSdkMigratedNpm,
51
52
  isValidProviderId,
52
- loadOpencodeCache,
53
53
  loadRegistry,
54
54
  maxToolsForNpm,
55
55
  modelPrefersResponsesApi,
@@ -85,7 +85,7 @@ import {
85
85
  translateRequest,
86
86
  upstreamHttpStatus,
87
87
  validateCustomEndpointUrl
88
- } from "./chunk-O2XZFXHG.js";
88
+ } from "./chunk-7ASGPECN.js";
89
89
 
90
90
  // src/registry/google-model-id.ts
91
91
  var GOOGLE_MODEL_PREFIX = "models/";
@@ -1285,6 +1285,8 @@ var memoryCache = null;
1285
1285
  var memoryCachePath = null;
1286
1286
  var memoryCacheMtime = 0;
1287
1287
  var REGISTRY_TO_MODELS_DEV = {
1288
+ zen: "opencode",
1289
+ go: "opencode-go",
1288
1290
  google: "google",
1289
1291
  openai: "openai",
1290
1292
  groq: "groq",
@@ -3583,27 +3585,26 @@ function deriveBrand(family) {
3583
3585
  }
3584
3586
  return "Other";
3585
3587
  }
3586
- function readModelsFromCache(backendId) {
3587
- const cache = loadOpencodeCache();
3588
- if (!cache) return null;
3589
- const providerKey = backendId === "zen" ? "opencode" : "opencode-go";
3588
+ function readModelsFromModelsDev(backendId, cache = loadModelsDevCache()) {
3589
+ const providerKey = resolveModelsDevSlug(backendId);
3590
3590
  const providerData = cache[providerKey];
3591
3591
  if (!providerData?.models) return null;
3592
3592
  const result = /* @__PURE__ */ new Map();
3593
- for (const entry of Object.values(providerData.models)) {
3594
- if (!entry.id || entry.status === "deprecated") continue;
3593
+ for (const [modelKey, entry] of Object.entries(providerData.models)) {
3594
+ const id = entry.id ?? modelKey;
3595
+ if (entry.status === "deprecated") continue;
3595
3596
  const isFree = entry.cost !== void 0 && entry.cost.input === 0 && entry.cost.output === 0;
3596
- const rawFormat = classifyModelFormat(entry.id, entry.provider?.npm);
3597
+ const rawFormat = classifyModelFormat(id, entry.provider?.npm);
3597
3598
  const modelFormat = backendId === "go" && rawFormat === "anthropic" ? "openai" : rawFormat;
3598
- result.set(entry.id, {
3599
- id: entry.id,
3600
- name: entry.name ?? entry.id,
3599
+ result.set(id, {
3600
+ id,
3601
+ name: entry.name ?? id,
3601
3602
  isFree,
3602
- brand: deriveBrand(entry.family ?? ""),
3603
+ brand: deriveBrand(entry.family ?? id),
3603
3604
  sourceBackend: backendId,
3604
3605
  modelFormat,
3605
3606
  cost: entry.cost,
3606
- contextWindow: resolveContextWindow(entry.id, entry.limit?.context),
3607
+ contextWindow: entry.limit?.context ?? contextWindowFromHeuristics(id),
3607
3608
  reasoning: entry.reasoning,
3608
3609
  interleavedReasoningField: entry.interleaved?.field
3609
3610
  });
@@ -3641,12 +3642,12 @@ function mergeModels(apiIds, cache, backendId) {
3641
3642
  brand: "Other",
3642
3643
  sourceBackend: backendId,
3643
3644
  modelFormat,
3644
- contextWindow: resolveContextWindow(id)
3645
+ contextWindow: contextWindowFromHeuristics(id)
3645
3646
  };
3646
3647
  });
3647
3648
  }
3648
3649
  async function getModels(backend, fallbackModels) {
3649
- const cache = readModelsFromCache(backend.id);
3650
+ const cache = readModelsFromModelsDev(backend.id);
3650
3651
  try {
3651
3652
  const apiIds = await fetchModelsFromApi(backend);
3652
3653
  return { models: mergeModels(apiIds, cache, backend.id), fromCache: false };
@@ -5714,6 +5715,132 @@ async function validateClinePassApiKey(apiKey) {
5714
5715
  }
5715
5716
  }
5716
5717
 
5718
+ // src/registry/fetch-commandcode-models.ts
5719
+ var COMMANDCODE_BASE_URL = "https://api.commandcode.ai/provider/v1";
5720
+ var REQUEST_TIMEOUT_MS2 = 1e4;
5721
+ var PROBE_TIMEOUT_MS = 25e3;
5722
+ var PROBE_CONCURRENCY = 6;
5723
+ function isAnthropicSchemaModel(id) {
5724
+ return id.startsWith("claude-");
5725
+ }
5726
+ function positiveNumber2(value) {
5727
+ const number = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : void 0;
5728
+ return typeof number === "number" && Number.isFinite(number) && number > 0 ? number : void 0;
5729
+ }
5730
+ function toCachedModel2(entry, baseUrl) {
5731
+ const id = typeof entry.id === "string" ? entry.id.trim() : "";
5732
+ if (!id) return null;
5733
+ const anthropicSchema = isAnthropicSchemaModel(id);
5734
+ const displayName = typeof entry.name === "string" && entry.name.trim() ? entry.name.trim() : id;
5735
+ const contextWindow2 = positiveNumber2(entry.context_length);
5736
+ const family = id.split("/").pop()?.split(/[-:]/)[0] ?? id;
5737
+ return {
5738
+ id,
5739
+ name: displayName,
5740
+ upstreamModelId: id,
5741
+ family,
5742
+ brand: deriveBrand(family),
5743
+ contextWindow: contextWindow2,
5744
+ contextWindowSource: contextWindow2 === void 0 ? void 0 : "provider",
5745
+ modelFormat: anthropicSchema ? "anthropic" : "openai",
5746
+ npm: anthropicSchema ? "@ai-sdk/anthropic" : "@ai-sdk/openai-compatible",
5747
+ apiUrl: baseUrl
5748
+ };
5749
+ }
5750
+ function parseCommandCodeModels(payload, baseUrl) {
5751
+ if (!payload || typeof payload !== "object") return [];
5752
+ const rows = payload.data;
5753
+ if (!Array.isArray(rows)) return [];
5754
+ const models = [];
5755
+ for (const row of rows) {
5756
+ if (!row || typeof row !== "object") continue;
5757
+ const model = toCachedModel2(row, baseUrl);
5758
+ if (model) models.push(model);
5759
+ }
5760
+ return models;
5761
+ }
5762
+ function classifyProbeResponse(status, body) {
5763
+ const message = typeof body === "object" && body !== null ? String(body.error?.message ?? "") : "";
5764
+ if (status === 403 && message.includes("MODEL_NOT_IN_PLAN")) return "not-in-plan";
5765
+ if (status >= 500) return "unknown";
5766
+ if (status === 429) return "unknown";
5767
+ if (status === 401 || status === 403) return "unknown";
5768
+ if (status >= 200 && status < 500) return "available";
5769
+ return "unknown";
5770
+ }
5771
+ async function probeModel(model, baseUrl, apiKey) {
5772
+ const anthropicSchema = model.modelFormat === "anthropic";
5773
+ const controller = new AbortController();
5774
+ const timer = setTimeout(() => controller.abort(), PROBE_TIMEOUT_MS);
5775
+ try {
5776
+ const response = await fetch(`${baseUrl}/${anthropicSchema ? "messages" : "chat/completions"}`, {
5777
+ method: "POST",
5778
+ headers: {
5779
+ "Content-Type": "application/json",
5780
+ ...anthropicSchema ? { "x-api-key": apiKey, "anthropic-version": "2023-06-01" } : { Authorization: `Bearer ${apiKey}` }
5781
+ },
5782
+ body: JSON.stringify({
5783
+ model: model.upstreamModelId,
5784
+ max_tokens: 1,
5785
+ messages: [{ role: "user", content: "hi" }]
5786
+ }),
5787
+ signal: controller.signal
5788
+ });
5789
+ const payload = await response.json().catch(() => null);
5790
+ return classifyProbeResponse(response.status, payload);
5791
+ } catch {
5792
+ return "unknown";
5793
+ } finally {
5794
+ clearTimeout(timer);
5795
+ }
5796
+ }
5797
+ async function filterModelsByPlan(models, baseUrl, apiKey) {
5798
+ if (!apiKey.trim()) return models;
5799
+ const keep = [];
5800
+ let next = 0;
5801
+ await Promise.all(
5802
+ Array.from({ length: Math.min(PROBE_CONCURRENCY, models.length) }, async () => {
5803
+ for (let i = next++; i < models.length; i = next++) {
5804
+ const model = models[i];
5805
+ if (await probeModel(model, baseUrl, apiKey) !== "not-in-plan") keep.push(model);
5806
+ }
5807
+ })
5808
+ );
5809
+ const order = new Map(models.map((m, i) => [m.id, i]));
5810
+ return keep.sort((a, b) => (order.get(a.id) ?? 0) - (order.get(b.id) ?? 0));
5811
+ }
5812
+ async function fetchCommandCodeModels(baseUrl = COMMANDCODE_BASE_URL, apiKey) {
5813
+ const normalizedBaseUrl = baseUrl.replace(/\/$/, "");
5814
+ const controller = new AbortController();
5815
+ const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS2);
5816
+ let response;
5817
+ try {
5818
+ response = await fetch(`${normalizedBaseUrl}/models`, {
5819
+ method: "GET",
5820
+ headers: {
5821
+ Accept: "application/json",
5822
+ ...apiKey?.trim() ? { Authorization: `Bearer ${apiKey.trim()}` } : {}
5823
+ },
5824
+ redirect: "manual",
5825
+ signal: controller.signal
5826
+ });
5827
+ } finally {
5828
+ clearTimeout(timer);
5829
+ }
5830
+ if (response.status === 401 || response.status === 403) {
5831
+ throw new Error("Command Code rejected the API key.");
5832
+ }
5833
+ if (!response.ok) {
5834
+ throw new Error(`Command Code model list returned HTTP ${response.status}.`);
5835
+ }
5836
+ const payload = await response.json().catch(() => null);
5837
+ const models = parseCommandCodeModels(payload, normalizedBaseUrl);
5838
+ if (models.length === 0) throw new Error("Command Code returned no usable models.");
5839
+ if (!apiKey?.trim()) return models;
5840
+ const available = await filterModelsByPlan(models, normalizedBaseUrl, apiKey);
5841
+ return available.length > 0 ? available : models;
5842
+ }
5843
+
5717
5844
  // src/registry/model-source.ts
5718
5845
  init_provider_templates();
5719
5846
 
@@ -5874,6 +6001,7 @@ function modelInfoToCached(m, npm, apiUrl) {
5874
6001
  }
5875
6002
  async function refreshZenGoProvider(provider) {
5876
6003
  const backendId = provider.id === "go" || provider.templateId === "go" ? "go" : "zen";
6004
+ await fetchModelsDevCache();
5877
6005
  const result = await getModels(BACKENDS[backendId]);
5878
6006
  return result.models.filter((m) => m.modelFormat !== "unsupported").map((m) => {
5879
6007
  const isAnthropic = m.modelFormat === "anthropic";
@@ -6225,6 +6353,20 @@ async function refreshProviderModels(providerId, apiKey, registry = loadRegistry
6225
6353
  let oauthFallbackReason;
6226
6354
  if (source === "zen-go-api") {
6227
6355
  models = await refreshZenGoProvider(provider);
6356
+ } else if (source === "commandcode") {
6357
+ const ccBaseUrl = (provider.api.url ?? COMMANDCODE_BASE_URL).replace(/\/$/, "");
6358
+ try {
6359
+ models = await fetchCommandCodeModels(ccBaseUrl, apiKey ?? void 0);
6360
+ baseUrl = ccBaseUrl;
6361
+ } catch (err) {
6362
+ if (cachedModelCount(provider) > 0) {
6363
+ return skipWithCachedModels(
6364
+ provider,
6365
+ `Command Code catalog refresh failed: ${err instanceof Error ? err.message : String(err)} Kept the existing cached model list; try again later.`
6366
+ );
6367
+ }
6368
+ throw err;
6369
+ }
6228
6370
  } else if (source === "cline-recommended") {
6229
6371
  try {
6230
6372
  models = await fetchClinePassModels();
@@ -8253,6 +8395,17 @@ async function addProviderFromTemplate(template, apiKey, opts) {
8253
8395
  hint: template.signupUrl ? `Verify your key at ${template.signupUrl}` : void 0
8254
8396
  };
8255
8397
  }
8398
+ } else if (template.modelSource === "commandcode") {
8399
+ try {
8400
+ const baseUrl = (opts?.baseUrl?.trim() || template.defaultBaseUrl || COMMANDCODE_BASE_URL).replace(/\/$/, "");
8401
+ fetched = { models: await fetchCommandCodeModels(baseUrl, trimmedKey), baseUrl };
8402
+ } catch (err) {
8403
+ return {
8404
+ added: false,
8405
+ error: err instanceof Error ? err.message : String(err),
8406
+ hint: template.signupUrl ? `Verify your key at ${template.signupUrl}` : void 0
8407
+ };
8408
+ }
8256
8409
  } else {
8257
8410
  fetched = await fetchTemplateModels(template, trimmedKey, opts?.baseUrl);
8258
8411
  }
@@ -9508,4 +9661,4 @@ export {
9508
9661
  supportsClaudeTransparentMode,
9509
9662
  buildHttpProxyRoutes
9510
9663
  };
9511
- //# sourceMappingURL=chunk-YD6A3ZB3.js.map
9664
+ //# sourceMappingURL=chunk-OYE6YZYP.js.map