@jacobbd/relay-ai 0.2.3 → 0.2.5
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/AGENTS.md +2 -2
- package/CHANGELOG.md +8 -83
- package/README.md +3 -3
- package/dist/cli.js +987 -384
- package/package.json +1 -1
- /package/{vertex-models.example.json → assets/vertex-models.example.json} +0 -0
package/dist/cli.js
CHANGED
|
@@ -76,7 +76,7 @@ function classifyModelFormat(modelId, providerNpm) {
|
|
|
76
76
|
if (lower.startsWith("gemini-")) return "unsupported";
|
|
77
77
|
return "openai";
|
|
78
78
|
}
|
|
79
|
-
var VERSION = "0.2.
|
|
79
|
+
var VERSION = "0.2.5";
|
|
80
80
|
|
|
81
81
|
// src/provider-factory.ts
|
|
82
82
|
var RESPONSES_ONLY_PREFIXES = [
|
|
@@ -196,11 +196,15 @@ var OPENAI_EFFORT_LEVELS = ["low", "medium", "high", "xhigh"];
|
|
|
196
196
|
var GEMINI_EFFORT_LEVELS = ["low", "medium", "high"];
|
|
197
197
|
var MISTRAL_EFFORT_LEVELS = ["high", "off"];
|
|
198
198
|
var XAI_EFFORT_LEVELS = ["none", "low", "medium", "high"];
|
|
199
|
+
var OPENROUTER_EFFORT_LEVELS = ["none", "minimal", "low", "medium", "high", "xhigh"];
|
|
199
200
|
var DEEPSEEK_EFFORT_LEVELS = ["high", "max", "off"];
|
|
200
201
|
var EMPTY_REASONING = {
|
|
201
202
|
levels: [],
|
|
202
203
|
defaultLevel: "",
|
|
203
|
-
supportsSummaries: false
|
|
204
|
+
supportsSummaries: false,
|
|
205
|
+
mode: "none",
|
|
206
|
+
source: "none",
|
|
207
|
+
confidence: "inferred"
|
|
204
208
|
};
|
|
205
209
|
var EFFORT_DESCRIPTIONS = {
|
|
206
210
|
off: "Turn off extended reasoning",
|
|
@@ -257,6 +261,41 @@ function isDeepSeekReasoningModel(modelId) {
|
|
|
257
261
|
const lower = modelId.toLowerCase();
|
|
258
262
|
return lower === "deepseek-v4-flash" || lower === "deepseek-v4-pro" || lower.startsWith("deepseek-v4-flash-") || lower.startsWith("deepseek-v4-pro-") || lower === "deepseek-reasoner" || lower === "deepseek-chat";
|
|
259
263
|
}
|
|
264
|
+
function hasSupportedParameter(metadata, param) {
|
|
265
|
+
return (metadata?.supportedParameters ?? []).some((p19) => p19 === param);
|
|
266
|
+
}
|
|
267
|
+
function isOpenRouterRoute(npm, metadata) {
|
|
268
|
+
return npm === "@openrouter/ai-sdk-provider" || metadata?.providerId === "openrouter" || metadata?.apiBaseUrl?.includes("openrouter.ai") === true;
|
|
269
|
+
}
|
|
270
|
+
function openRouterReasoningCapabilities(metadata) {
|
|
271
|
+
if (metadata?.supportedParameters && !hasSupportedParameter(metadata, "reasoning")) {
|
|
272
|
+
return {
|
|
273
|
+
...EMPTY_REASONING,
|
|
274
|
+
source: "provider-metadata",
|
|
275
|
+
confidence: "documented"
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
if (hasSupportedParameter(metadata, "reasoning")) {
|
|
279
|
+
return {
|
|
280
|
+
levels: [...OPENROUTER_EFFORT_LEVELS],
|
|
281
|
+
defaultLevel: "medium",
|
|
282
|
+
supportsSummaries: false,
|
|
283
|
+
mode: "controllable",
|
|
284
|
+
source: "provider-metadata",
|
|
285
|
+
confidence: "documented",
|
|
286
|
+
wireFormat: { kind: "openrouter-reasoning" }
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
if (metadata?.reasoning) {
|
|
290
|
+
return {
|
|
291
|
+
...EMPTY_REASONING,
|
|
292
|
+
mode: "internal-only",
|
|
293
|
+
source: "model-metadata",
|
|
294
|
+
confidence: "inferred"
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
return EMPTY_REASONING;
|
|
298
|
+
}
|
|
260
299
|
function mapCodexEffortToDeepSeek(effort) {
|
|
261
300
|
switch (effort) {
|
|
262
301
|
case "off":
|
|
@@ -356,40 +395,81 @@ function mapCodexEffortToGeminiBudget(effort) {
|
|
|
356
395
|
if (!level) return void 0;
|
|
357
396
|
return GEMINI_25_BUDGETS[level];
|
|
358
397
|
}
|
|
359
|
-
function getReasoningCapabilities(npm, modelId) {
|
|
398
|
+
function getReasoningCapabilities(npm, modelId, metadata) {
|
|
360
399
|
const id = modelId.toLowerCase();
|
|
400
|
+
if (isOpenRouterRoute(npm, metadata)) {
|
|
401
|
+
return openRouterReasoningCapabilities(metadata);
|
|
402
|
+
}
|
|
361
403
|
if (npm === "@ai-sdk/anthropic" || id.startsWith("claude-")) {
|
|
362
|
-
|
|
363
|
-
|
|
404
|
+
const isClaude = isClaudeReasoningModel(modelId);
|
|
405
|
+
if (isClaude || metadata?.reasoning) {
|
|
406
|
+
return {
|
|
407
|
+
levels: [...ANTHROPIC_EFFORT_LEVELS],
|
|
408
|
+
defaultLevel: "high",
|
|
409
|
+
supportsSummaries: true,
|
|
410
|
+
mode: "controllable",
|
|
411
|
+
source: isClaude ? "provider-rule" : "model-metadata",
|
|
412
|
+
confidence: isClaude ? "documented" : "inferred",
|
|
413
|
+
wireFormat: { kind: "anthropic-thinking" }
|
|
414
|
+
};
|
|
364
415
|
}
|
|
365
416
|
return EMPTY_REASONING;
|
|
366
417
|
}
|
|
367
418
|
if (npm === "@ai-sdk/openai" || npm === "@ai-sdk/azure") {
|
|
368
|
-
|
|
419
|
+
const prefersResponses = modelPrefersResponsesApi(modelId);
|
|
420
|
+
if (prefersResponses || metadata?.reasoning) {
|
|
369
421
|
return {
|
|
370
422
|
levels: [...OPENAI_EFFORT_LEVELS],
|
|
371
423
|
defaultLevel: "medium",
|
|
372
|
-
supportsSummaries: true
|
|
424
|
+
supportsSummaries: true,
|
|
425
|
+
mode: "controllable",
|
|
426
|
+
source: prefersResponses ? "provider-rule" : "model-metadata",
|
|
427
|
+
confidence: prefersResponses ? "documented" : "inferred",
|
|
428
|
+
wireFormat: { kind: "openai-reasoning-effort" }
|
|
373
429
|
};
|
|
374
430
|
}
|
|
375
431
|
return EMPTY_REASONING;
|
|
376
432
|
}
|
|
377
433
|
if (npm === "@ai-sdk/google" || id.startsWith("gemini-")) {
|
|
378
434
|
if (isGeminiReasoningModel(modelId)) {
|
|
379
|
-
return {
|
|
435
|
+
return {
|
|
436
|
+
levels: [...GEMINI_EFFORT_LEVELS],
|
|
437
|
+
defaultLevel: "medium",
|
|
438
|
+
supportsSummaries: true,
|
|
439
|
+
mode: "controllable",
|
|
440
|
+
source: "provider-rule",
|
|
441
|
+
confidence: "documented",
|
|
442
|
+
wireFormat: { kind: "google-thinking-config" }
|
|
443
|
+
};
|
|
380
444
|
}
|
|
381
445
|
return EMPTY_REASONING;
|
|
382
446
|
}
|
|
383
447
|
if (npm === "@ai-sdk/mistral") {
|
|
384
448
|
if (isMistralReasoningModel(modelId)) {
|
|
385
|
-
return {
|
|
449
|
+
return {
|
|
450
|
+
levels: [...MISTRAL_EFFORT_LEVELS],
|
|
451
|
+
defaultLevel: "high",
|
|
452
|
+
supportsSummaries: false,
|
|
453
|
+
mode: "controllable",
|
|
454
|
+
source: "provider-rule",
|
|
455
|
+
confidence: "documented",
|
|
456
|
+
wireFormat: { kind: "mistral-reasoning-effort" }
|
|
457
|
+
};
|
|
386
458
|
}
|
|
387
459
|
return EMPTY_REASONING;
|
|
388
460
|
}
|
|
389
461
|
if (npm === "@ai-sdk/xai") {
|
|
390
462
|
if (isXaiReasoningEffortModel(modelId)) {
|
|
391
463
|
const levels = modelPrefersResponsesApi(modelId) ? ["low", "medium", "high", "xhigh"] : [...XAI_EFFORT_LEVELS];
|
|
392
|
-
return {
|
|
464
|
+
return {
|
|
465
|
+
levels,
|
|
466
|
+
defaultLevel: "low",
|
|
467
|
+
supportsSummaries: true,
|
|
468
|
+
mode: "controllable",
|
|
469
|
+
source: "provider-rule",
|
|
470
|
+
confidence: "documented",
|
|
471
|
+
wireFormat: { kind: "openai-reasoning-effort" }
|
|
472
|
+
};
|
|
393
473
|
}
|
|
394
474
|
return EMPTY_REASONING;
|
|
395
475
|
}
|
|
@@ -397,7 +477,44 @@ function getReasoningCapabilities(npm, modelId) {
|
|
|
397
477
|
return {
|
|
398
478
|
levels: [...DEEPSEEK_EFFORT_LEVELS],
|
|
399
479
|
defaultLevel: "high",
|
|
400
|
-
supportsSummaries: true
|
|
480
|
+
supportsSummaries: true,
|
|
481
|
+
mode: "controllable",
|
|
482
|
+
source: "provider-rule",
|
|
483
|
+
confidence: "documented",
|
|
484
|
+
wireFormat: { kind: "deepseek-thinking" }
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
if (hasSupportedParameter(metadata, "reasoning_effort")) {
|
|
488
|
+
return {
|
|
489
|
+
levels: ["low", "medium", "high", "xhigh"],
|
|
490
|
+
defaultLevel: "medium",
|
|
491
|
+
supportsSummaries: false,
|
|
492
|
+
mode: "controllable",
|
|
493
|
+
source: "provider-metadata",
|
|
494
|
+
confidence: "documented",
|
|
495
|
+
wireFormat: { kind: "openai-reasoning-effort" }
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
if (hasSupportedParameter(metadata, "reasoning")) {
|
|
499
|
+
return {
|
|
500
|
+
levels: [...OPENROUTER_EFFORT_LEVELS],
|
|
501
|
+
defaultLevel: "medium",
|
|
502
|
+
supportsSummaries: false,
|
|
503
|
+
mode: "controllable",
|
|
504
|
+
source: "provider-metadata",
|
|
505
|
+
confidence: "documented",
|
|
506
|
+
wireFormat: { kind: "openrouter-reasoning" }
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
if (metadata?.reasoning) {
|
|
510
|
+
return {
|
|
511
|
+
levels: ["low", "medium", "high"],
|
|
512
|
+
defaultLevel: "medium",
|
|
513
|
+
supportsSummaries: false,
|
|
514
|
+
mode: "controllable",
|
|
515
|
+
source: "model-metadata",
|
|
516
|
+
confidence: "inferred",
|
|
517
|
+
wireFormat: { kind: "openai-reasoning-effort" }
|
|
401
518
|
};
|
|
402
519
|
}
|
|
403
520
|
return EMPTY_REASONING;
|
|
@@ -408,8 +525,15 @@ function buildCodexReasoningLevels(capabilities) {
|
|
|
408
525
|
description: EFFORT_DESCRIPTIONS[effort] ?? effort
|
|
409
526
|
}));
|
|
410
527
|
}
|
|
411
|
-
function effortProviderOptions(npm, effort, modelId) {
|
|
528
|
+
function effortProviderOptions(npm, effort, modelId, metadata) {
|
|
412
529
|
if (!effort) return void 0;
|
|
530
|
+
if (isOpenRouterRoute(npm, metadata)) {
|
|
531
|
+
const caps = openRouterReasoningCapabilities(metadata);
|
|
532
|
+
if (caps.mode !== "controllable") return void 0;
|
|
533
|
+
const allowed = new Set(OPENROUTER_EFFORT_LEVELS);
|
|
534
|
+
const mapped = allowed.has(effort) ? effort : effort === "max" ? "xhigh" : void 0;
|
|
535
|
+
return mapped ? { openrouter: { reasoning: { effort: mapped, exclude: false } } } : void 0;
|
|
536
|
+
}
|
|
413
537
|
if (npm === "@ai-sdk/openai" || npm === "@ai-sdk/azure") {
|
|
414
538
|
if (!modelId || !modelPrefersResponsesApi(modelId)) return void 0;
|
|
415
539
|
const reasoningEffort = mapCodexEffortToOpenAI(effort);
|
|
@@ -420,13 +544,14 @@ function effortProviderOptions(npm, effort, modelId) {
|
|
|
420
544
|
const reasoningEffort = mapCodexEffortToXai(effort);
|
|
421
545
|
return reasoningEffort ? { xai: { reasoningEffort } } : void 0;
|
|
422
546
|
}
|
|
423
|
-
if (npm === "@ai-sdk/anthropic") {
|
|
547
|
+
if (npm === "@ai-sdk/anthropic" || npm === VERTEX_ANTHROPIC_NPM) {
|
|
548
|
+
if (!modelId || !isClaudeReasoningModel(modelId)) return void 0;
|
|
424
549
|
const mapped = mapCodexEffortToAnthropic(effort);
|
|
425
550
|
return mapped ? { anthropic: { thinking: { type: "adaptive", effort: mapped } } } : void 0;
|
|
426
551
|
}
|
|
427
552
|
if (npm === "@ai-sdk/google") {
|
|
428
|
-
const
|
|
429
|
-
if (isGemini3Model(
|
|
553
|
+
const id = modelId ?? "";
|
|
554
|
+
if (isGemini3Model(id)) {
|
|
430
555
|
const thinkingLevel = mapCodexEffortToGeminiLevel(effort);
|
|
431
556
|
return thinkingLevel ? { google: { thinkingConfig: { thinkingLevel, includeThoughts: true } } } : void 0;
|
|
432
557
|
}
|
|
@@ -438,9 +563,21 @@ function effortProviderOptions(npm, effort, modelId) {
|
|
|
438
563
|
const reasoningEffort = effort === "off" || effort === "none" ? "none" : "high";
|
|
439
564
|
return { mistral: { reasoningEffort } };
|
|
440
565
|
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
566
|
+
if (npm === "@ai-sdk/openai-compatible" || npm === "@ai-sdk/openai") {
|
|
567
|
+
if (!modelId) return void 0;
|
|
568
|
+
if (isDeepSeekReasoningModel(modelId)) {
|
|
569
|
+
return deepSeekEffortProviderOptions(effort);
|
|
570
|
+
}
|
|
571
|
+
if (hasSupportedParameter(metadata, "reasoning_effort")) {
|
|
572
|
+
const reasoningEffort = mapCodexEffortToOpenAI(effort);
|
|
573
|
+
return reasoningEffort ? { openai: { reasoningEffort }, openaiCompatible: { reasoningEffort }, "openai-compatible": { reasoningEffort } } : void 0;
|
|
574
|
+
}
|
|
575
|
+
if (hasSupportedParameter(metadata, "reasoning")) {
|
|
576
|
+
const allowed = new Set(OPENROUTER_EFFORT_LEVELS);
|
|
577
|
+
const mapped = allowed.has(effort) ? effort : effort === "max" ? "xhigh" : void 0;
|
|
578
|
+
return mapped ? { openrouter: { reasoning: { effort: mapped, exclude: false } } } : void 0;
|
|
579
|
+
}
|
|
580
|
+
return void 0;
|
|
444
581
|
}
|
|
445
582
|
return void 0;
|
|
446
583
|
}
|
|
@@ -1576,14 +1713,19 @@ function resolveBaseURL(model, provider) {
|
|
|
1576
1713
|
}
|
|
1577
1714
|
function resolveCodexRoute(provider, model, apiKey) {
|
|
1578
1715
|
const upstreamModelId2 = model.upstreamModelId || model.id;
|
|
1716
|
+
const inferredNpm = model.modelFormat === "anthropic" ? "@ai-sdk/anthropic" : "@ai-sdk/openai-compatible";
|
|
1717
|
+
const isZenGo = provider.id === "zen" || provider.id === "go";
|
|
1579
1718
|
const base = {
|
|
1580
|
-
npm: model.npm ??
|
|
1719
|
+
npm: isZenGo ? inferredNpm : model.npm ?? inferredNpm,
|
|
1581
1720
|
baseURL: resolveBaseURL(model, provider),
|
|
1582
1721
|
upstreamModelId: upstreamModelId2,
|
|
1583
1722
|
apiKey,
|
|
1584
1723
|
contextWindow: model.contextWindow,
|
|
1585
1724
|
modelId: model.id,
|
|
1586
|
-
providerId: provider.id
|
|
1725
|
+
providerId: provider.id,
|
|
1726
|
+
supportedParameters: model.supportedParameters,
|
|
1727
|
+
reasoning: model.reasoning,
|
|
1728
|
+
interleavedReasoningField: model.interleavedReasoningField
|
|
1587
1729
|
};
|
|
1588
1730
|
if (provider.id === "openai" && model.modelFormat === "openai") {
|
|
1589
1731
|
return { tier: "direct", ...base };
|
|
@@ -1607,7 +1749,10 @@ function buildCodexProxyRoutesForProvider(provider, apiKey, selectedModelId, age
|
|
|
1607
1749
|
apiKey: route.apiKey,
|
|
1608
1750
|
baseURL: route.baseURL,
|
|
1609
1751
|
upstreamModelId: route.upstreamModelId,
|
|
1610
|
-
providerId: route.providerId
|
|
1752
|
+
providerId: route.providerId,
|
|
1753
|
+
supportedParameters: route.supportedParameters,
|
|
1754
|
+
reasoning: route.reasoning,
|
|
1755
|
+
interleavedReasoningField: route.interleavedReasoningField
|
|
1611
1756
|
};
|
|
1612
1757
|
});
|
|
1613
1758
|
}
|
|
@@ -1665,8 +1810,8 @@ function buildCodexAppRootConfig(spec) {
|
|
|
1665
1810
|
// src/codex/catalog.ts
|
|
1666
1811
|
var DEFAULT_CONTEXT = 128e3;
|
|
1667
1812
|
var CODEX_NO_REASONING_EFFORT = "none";
|
|
1668
|
-
function codexCatalogReasoningFields(npm, wireId) {
|
|
1669
|
-
const reasoning = getReasoningCapabilities(npm, wireId);
|
|
1813
|
+
function codexCatalogReasoningFields(npm, wireId, metadata) {
|
|
1814
|
+
const reasoning = getReasoningCapabilities(npm, wireId, metadata);
|
|
1670
1815
|
if (reasoning.levels.length > 0) {
|
|
1671
1816
|
return {
|
|
1672
1817
|
supported_reasoning_levels: buildCodexReasoningLevels(reasoning),
|
|
@@ -1677,9 +1822,7 @@ function codexCatalogReasoningFields(npm, wireId) {
|
|
|
1677
1822
|
}
|
|
1678
1823
|
return {
|
|
1679
1824
|
supported_reasoning_levels: buildCodexReasoningLevels({
|
|
1680
|
-
levels: [CODEX_NO_REASONING_EFFORT]
|
|
1681
|
-
defaultLevel: CODEX_NO_REASONING_EFFORT,
|
|
1682
|
-
supportsSummaries: false
|
|
1825
|
+
levels: [CODEX_NO_REASONING_EFFORT]
|
|
1683
1826
|
}),
|
|
1684
1827
|
default_reasoning_level: CODEX_NO_REASONING_EFFORT,
|
|
1685
1828
|
supports_reasoning_summaries: false,
|
|
@@ -1709,7 +1852,12 @@ function catalogEntryFromModel(model, providerName, priority, appCatalog = false
|
|
|
1709
1852
|
const context = model.contextWindow ?? DEFAULT_CONTEXT;
|
|
1710
1853
|
const label = formatCodexModelLabel(model);
|
|
1711
1854
|
const wireId = model.upstreamModelId ?? model.id;
|
|
1712
|
-
const reasoningFields = codexCatalogReasoningFields(model.npm ?? "", wireId
|
|
1855
|
+
const reasoningFields = codexCatalogReasoningFields(model.npm ?? "", wireId, {
|
|
1856
|
+
apiBaseUrl: model.apiBaseUrl,
|
|
1857
|
+
supportedParameters: model.supportedParameters,
|
|
1858
|
+
reasoning: model.reasoning,
|
|
1859
|
+
interleavedReasoningField: model.interleavedReasoningField
|
|
1860
|
+
});
|
|
1713
1861
|
return {
|
|
1714
1862
|
slug,
|
|
1715
1863
|
display_name: label,
|
|
@@ -2984,7 +3132,10 @@ function normalizeProviders(raw, opts) {
|
|
|
2984
3132
|
npm: model.api?.npm,
|
|
2985
3133
|
apiBaseUrl: model.api?.url,
|
|
2986
3134
|
cost: model.cost,
|
|
2987
|
-
contextWindow: resolveContextWindow(model.id, model.limit?.context)
|
|
3135
|
+
contextWindow: resolveContextWindow(model.id, model.limit?.context),
|
|
3136
|
+
supportedParameters: model.supportedParameters ?? model.supported_parameters,
|
|
3137
|
+
reasoning: model.reasoning,
|
|
3138
|
+
interleavedReasoningField: model.interleaved?.field
|
|
2988
3139
|
});
|
|
2989
3140
|
}
|
|
2990
3141
|
if (models.length === 0) continue;
|
|
@@ -3123,7 +3274,10 @@ function modelToCached(model) {
|
|
|
3123
3274
|
cost: model.cost,
|
|
3124
3275
|
modelFormat: model.modelFormat,
|
|
3125
3276
|
npm: model.npm,
|
|
3126
|
-
apiUrl: model.apiBaseUrl
|
|
3277
|
+
apiUrl: model.apiBaseUrl,
|
|
3278
|
+
supportedParameters: model.supportedParameters,
|
|
3279
|
+
reasoning: model.reasoning,
|
|
3280
|
+
interleavedReasoningField: model.interleavedReasoningField
|
|
3127
3281
|
};
|
|
3128
3282
|
}
|
|
3129
3283
|
function localProviderToRegistry(provider, opts) {
|
|
@@ -3293,7 +3447,8 @@ function parseModelList(body, npm) {
|
|
|
3293
3447
|
brand: deriveBrand(family),
|
|
3294
3448
|
contextWindow: resolveContextWindow(id),
|
|
3295
3449
|
modelFormat: format,
|
|
3296
|
-
npm
|
|
3450
|
+
npm,
|
|
3451
|
+
supportedParameters: Array.isArray(row.supported_parameters) ? row.supported_parameters : void 0
|
|
3297
3452
|
});
|
|
3298
3453
|
}
|
|
3299
3454
|
return models;
|
|
@@ -3818,6 +3973,24 @@ var PROVIDER_TEMPLATES = [
|
|
|
3818
3973
|
supported: false,
|
|
3819
3974
|
unsupportedReason: "Uses gcloud Application Default Credentials \u2014 not supported via API key import."
|
|
3820
3975
|
},
|
|
3976
|
+
{
|
|
3977
|
+
id: "zen",
|
|
3978
|
+
name: "OpenCode Zen",
|
|
3979
|
+
authType: "api",
|
|
3980
|
+
npm: "@ai-sdk/openai-compatible",
|
|
3981
|
+
signupUrl: "https://opencode.ai/auth",
|
|
3982
|
+
modelSource: "zen-go-api",
|
|
3983
|
+
supported: true
|
|
3984
|
+
},
|
|
3985
|
+
{
|
|
3986
|
+
id: "go",
|
|
3987
|
+
name: "OpenCode Go",
|
|
3988
|
+
authType: "api",
|
|
3989
|
+
npm: "@ai-sdk/openai-compatible",
|
|
3990
|
+
signupUrl: "https://opencode.ai/auth",
|
|
3991
|
+
modelSource: "zen-go-api",
|
|
3992
|
+
supported: true
|
|
3993
|
+
},
|
|
3821
3994
|
// OAuth-gated subscription providers — use relay-ai providers auth <id> to sign in
|
|
3822
3995
|
{
|
|
3823
3996
|
id: "github-copilot",
|
|
@@ -3847,14 +4020,6 @@ function filterTemplates(templates, query) {
|
|
|
3847
4020
|
(t) => t.id.toLowerCase().includes(q) || t.name.toLowerCase().includes(q) || t.npm.toLowerCase().includes(q)
|
|
3848
4021
|
);
|
|
3849
4022
|
}
|
|
3850
|
-
function matchesOpenCodeCloudSearch(query) {
|
|
3851
|
-
const q = query.trim().toLowerCase();
|
|
3852
|
-
if (!q) return false;
|
|
3853
|
-
if (q.includes("opencode") || q.includes("open code")) return true;
|
|
3854
|
-
if (q === "zen" || q === "go") return true;
|
|
3855
|
-
if (q.includes("zen ") || q.startsWith("zen/") || q.endsWith(" zen")) return true;
|
|
3856
|
-
return false;
|
|
3857
|
-
}
|
|
3858
4023
|
|
|
3859
4024
|
// src/registry/resolve-template.ts
|
|
3860
4025
|
var TEMPLATE_ID_ALIASES = {
|
|
@@ -3955,8 +4120,20 @@ async function validateImportKey(lp, entry) {
|
|
|
3955
4120
|
}
|
|
3956
4121
|
return reject("invalid-key", "No API base URL \u2014 cannot verify key.");
|
|
3957
4122
|
}
|
|
4123
|
+
let safeBaseUrl = baseUrl;
|
|
4124
|
+
const configuredUrl = entry.api.url?.trim();
|
|
4125
|
+
const templateDefault = catalogTemplate?.defaultBaseUrl?.trim();
|
|
4126
|
+
if (configuredUrl && configuredUrl !== templateDefault) {
|
|
4127
|
+
const urlCheck = await validateCustomEndpointUrl(baseUrl, {
|
|
4128
|
+
allowInsecureLocal: catalogTemplate?.apiKeyOptional === true
|
|
4129
|
+
});
|
|
4130
|
+
if (!urlCheck.ok || !urlCheck.normalizedUrl) {
|
|
4131
|
+
return reject("invalid-key", `${urlCheck.error ?? "Invalid API base URL."} ${urlCheck.hint ?? ""}`.trim());
|
|
4132
|
+
}
|
|
4133
|
+
safeBaseUrl = urlCheck.normalizedUrl;
|
|
4134
|
+
}
|
|
3958
4135
|
if (npm === "@ai-sdk/anthropic") {
|
|
3959
|
-
const result2 = await fetchAnthropicModels(
|
|
4136
|
+
const result2 = await fetchAnthropicModels(safeBaseUrl, key);
|
|
3960
4137
|
if (result2.error) {
|
|
3961
4138
|
return reject(
|
|
3962
4139
|
placeholder ? "placeholder-key" : "invalid-key",
|
|
@@ -3965,8 +4142,8 @@ async function validateImportKey(lp, entry) {
|
|
|
3965
4142
|
}
|
|
3966
4143
|
return { canImport: true };
|
|
3967
4144
|
}
|
|
3968
|
-
const template = catalogTemplate ?? syntheticTemplate(entry,
|
|
3969
|
-
const result = await fetchTemplateModels(template, key,
|
|
4145
|
+
const template = catalogTemplate ?? syntheticTemplate(entry, safeBaseUrl);
|
|
4146
|
+
const result = await fetchTemplateModels(template, key, safeBaseUrl);
|
|
3970
4147
|
if (result.error) {
|
|
3971
4148
|
return reject(
|
|
3972
4149
|
placeholder ? "placeholder-key" : "invalid-key",
|
|
@@ -4076,6 +4253,11 @@ async function importFromOpencode(options = {}) {
|
|
|
4076
4253
|
continue;
|
|
4077
4254
|
}
|
|
4078
4255
|
}
|
|
4256
|
+
const saved = isOAuth ? await saveOAuthKey(lp.id, oauth) : await saveProviderKey(lp);
|
|
4257
|
+
if (!saved) {
|
|
4258
|
+
skipped.push({ id: lp.id, name: lp.name, reason: "credential-save-failed" });
|
|
4259
|
+
continue;
|
|
4260
|
+
}
|
|
4079
4261
|
if (existingIdx >= 0) {
|
|
4080
4262
|
registry.providers[existingIdx] = { ...entry, addedAt: registry.providers[existingIdx].addedAt };
|
|
4081
4263
|
} else {
|
|
@@ -4083,11 +4265,8 @@ async function importFromOpencode(options = {}) {
|
|
|
4083
4265
|
}
|
|
4084
4266
|
imported.push(entry);
|
|
4085
4267
|
importedIds.add(lp.id);
|
|
4086
|
-
|
|
4087
|
-
if (
|
|
4088
|
-
keysSaved += 1;
|
|
4089
|
-
if (isOAuth) oauthImported += 1;
|
|
4090
|
-
}
|
|
4268
|
+
keysSaved += 1;
|
|
4269
|
+
if (isOAuth) oauthImported += 1;
|
|
4091
4270
|
}
|
|
4092
4271
|
const alreadyReportedIds = new Set(skipped.map((s) => s.id));
|
|
4093
4272
|
const registryProviderIds = new Set(registry.providers.map((p19) => p19.id));
|
|
@@ -4588,10 +4767,13 @@ function upstreamModelId(model) {
|
|
|
4588
4767
|
const id = model.upstreamModelId ?? model.id;
|
|
4589
4768
|
return id.replace(/\[1m\]$/i, "");
|
|
4590
4769
|
}
|
|
4770
|
+
function isOpenAIChatCompletionsModel(model) {
|
|
4771
|
+
return model.modelFormat === "openai" && (!!model.completionsUrl || model.sourceBackend === "zen" || model.sourceBackend === "go");
|
|
4772
|
+
}
|
|
4591
4773
|
function formatOpenAIModels(models) {
|
|
4592
4774
|
return {
|
|
4593
4775
|
object: "list",
|
|
4594
|
-
data: models.map((model) => ({
|
|
4776
|
+
data: models.filter(isOpenAIChatCompletionsModel).map((model) => ({
|
|
4595
4777
|
id: model.id,
|
|
4596
4778
|
object: "model",
|
|
4597
4779
|
created: CREATED_AT_UNIX,
|
|
@@ -4623,20 +4805,24 @@ function extractBearerToken(value) {
|
|
|
4623
4805
|
}
|
|
4624
4806
|
|
|
4625
4807
|
// src/upstream-forward.ts
|
|
4626
|
-
function anthropicUpstreamHeaders(apiKey, stream = false) {
|
|
4808
|
+
function anthropicUpstreamHeaders(apiKey, stream = false, inboundBeta) {
|
|
4627
4809
|
const key = sanitizeCredential(apiKey) ?? apiKey.trim();
|
|
4628
|
-
|
|
4810
|
+
const headers = {
|
|
4629
4811
|
"Content-Type": "application/json",
|
|
4630
4812
|
"anthropic-version": "2023-06-01",
|
|
4631
4813
|
Authorization: `Bearer ${key}`,
|
|
4632
4814
|
"x-api-key": key,
|
|
4633
4815
|
...stream ? { Accept: "text/event-stream" } : {}
|
|
4634
4816
|
};
|
|
4817
|
+
if (inboundBeta) {
|
|
4818
|
+
headers["anthropic-beta"] = inboundBeta;
|
|
4819
|
+
}
|
|
4820
|
+
return headers;
|
|
4635
4821
|
}
|
|
4636
|
-
async function postJsonUpstream(url, body, apiKey) {
|
|
4822
|
+
async function postJsonUpstream(url, body, apiKey, inboundBeta) {
|
|
4637
4823
|
const response = await fetch(url, {
|
|
4638
4824
|
method: "POST",
|
|
4639
|
-
headers: anthropicUpstreamHeaders(apiKey, false),
|
|
4825
|
+
headers: anthropicUpstreamHeaders(apiKey, false, inboundBeta),
|
|
4640
4826
|
body: JSON.stringify(body)
|
|
4641
4827
|
});
|
|
4642
4828
|
const text5 = await response.text();
|
|
@@ -4656,12 +4842,12 @@ var UpstreamUnreachableError = class extends Error {
|
|
|
4656
4842
|
this.name = "UpstreamUnreachableError";
|
|
4657
4843
|
}
|
|
4658
4844
|
};
|
|
4659
|
-
async function relayAnthropicMessages(res, messagesUrl, body, apiKey, clientWantsStream) {
|
|
4845
|
+
async function relayAnthropicMessages(res, messagesUrl, body, apiKey, clientWantsStream, inboundBeta) {
|
|
4660
4846
|
let upstreamRes;
|
|
4661
4847
|
try {
|
|
4662
4848
|
upstreamRes = await fetch(messagesUrl, {
|
|
4663
4849
|
method: "POST",
|
|
4664
|
-
headers: anthropicUpstreamHeaders(apiKey, clientWantsStream),
|
|
4850
|
+
headers: anthropicUpstreamHeaders(apiKey, clientWantsStream, inboundBeta),
|
|
4665
4851
|
body: JSON.stringify(body)
|
|
4666
4852
|
});
|
|
4667
4853
|
} catch (err) {
|
|
@@ -4687,20 +4873,19 @@ async function relayAnthropicMessages(res, messagesUrl, body, apiKey, clientWant
|
|
|
4687
4873
|
res.end(JSON.stringify({ type: "error", error: { type: "api_error", message: "Upstream returned empty response body" } }));
|
|
4688
4874
|
return;
|
|
4689
4875
|
}
|
|
4690
|
-
|
|
4876
|
+
const text5 = await upstreamRes.text();
|
|
4691
4877
|
try {
|
|
4692
|
-
|
|
4878
|
+
JSON.parse(text5);
|
|
4693
4879
|
} catch {
|
|
4694
4880
|
res.writeHead(502, { "Content-Type": "application/json" });
|
|
4695
4881
|
res.end(JSON.stringify({ type: "error", error: { type: "api_error", message: "Upstream response was not valid JSON" } }));
|
|
4696
4882
|
return;
|
|
4697
4883
|
}
|
|
4698
|
-
const payload = JSON.stringify(json);
|
|
4699
4884
|
res.writeHead(200, {
|
|
4700
4885
|
"Content-Type": "application/json",
|
|
4701
|
-
"Content-Length": Buffer.byteLength(
|
|
4886
|
+
"Content-Length": Buffer.byteLength(text5).toString()
|
|
4702
4887
|
});
|
|
4703
|
-
res.end(
|
|
4888
|
+
res.end(text5);
|
|
4704
4889
|
}
|
|
4705
4890
|
|
|
4706
4891
|
// src/sdk-adapter.ts
|
|
@@ -4717,7 +4902,7 @@ function silenceSdkWarnings() {
|
|
|
4717
4902
|
sdkWarningsSilenced = true;
|
|
4718
4903
|
globalThis.AI_SDK_LOG_WARNINGS = false;
|
|
4719
4904
|
}
|
|
4720
|
-
var TOOL_USE_SIG_SEP = "
|
|
4905
|
+
var TOOL_USE_SIG_SEP = "__ts__";
|
|
4721
4906
|
function parseToolArguments(value) {
|
|
4722
4907
|
if (value === null || value === void 0) return {};
|
|
4723
4908
|
if (typeof value === "object" && !Array.isArray(value)) return value;
|
|
@@ -4740,15 +4925,26 @@ data: ${JSON.stringify(data)}
|
|
|
4740
4925
|
`;
|
|
4741
4926
|
}
|
|
4742
4927
|
function splitToolUseId(id) {
|
|
4743
|
-
|
|
4744
|
-
if (sep
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4928
|
+
let sep = id.lastIndexOf(TOOL_USE_SIG_SEP);
|
|
4929
|
+
if (sep !== -1) {
|
|
4930
|
+
return {
|
|
4931
|
+
rawId: id.slice(0, sep),
|
|
4932
|
+
thoughtSignature: Buffer.from(id.slice(sep + TOOL_USE_SIG_SEP.length), "base64url").toString("utf8")
|
|
4933
|
+
};
|
|
4934
|
+
}
|
|
4935
|
+
sep = id.lastIndexOf("::ts::");
|
|
4936
|
+
if (sep !== -1) {
|
|
4937
|
+
return {
|
|
4938
|
+
rawId: id.slice(0, sep),
|
|
4939
|
+
thoughtSignature: id.slice(sep + 6)
|
|
4940
|
+
};
|
|
4941
|
+
}
|
|
4942
|
+
return { rawId: id };
|
|
4749
4943
|
}
|
|
4750
4944
|
function encodeToolUseId(rawId, thoughtSignature) {
|
|
4751
|
-
|
|
4945
|
+
if (!thoughtSignature) return rawId;
|
|
4946
|
+
const encoded = Buffer.from(thoughtSignature, "utf8").toString("base64url");
|
|
4947
|
+
return `${rawId}${TOOL_USE_SIG_SEP}${encoded}`;
|
|
4752
4948
|
}
|
|
4753
4949
|
function serializeToolResultContent(content) {
|
|
4754
4950
|
return typeof content === "string" ? content : JSON.stringify(content);
|
|
@@ -4950,7 +5146,7 @@ function translateRequest(body, npm, options) {
|
|
|
4950
5146
|
const effort = anthropicEffortFromRequest(body) ?? options?.defaultEffort;
|
|
4951
5147
|
const providerOptions = deepMergeProviderOptions(
|
|
4952
5148
|
thinkingProviderOptions(npm),
|
|
4953
|
-
effortProviderOptions(npm, effort, body.model)
|
|
5149
|
+
effortProviderOptions(npm, effort, body.model, options?.reasoningMetadata)
|
|
4954
5150
|
);
|
|
4955
5151
|
return {
|
|
4956
5152
|
system,
|
|
@@ -5133,7 +5329,7 @@ async function generateAnthropicResponse(model, params, modelId) {
|
|
|
5133
5329
|
...r.text ? [{ type: "text", text: r.text }] : [],
|
|
5134
5330
|
...r.toolCalls.map((tc) => ({
|
|
5135
5331
|
type: "tool_use",
|
|
5136
|
-
id: tc.toolCallId,
|
|
5332
|
+
id: encodeToolUseId(tc.toolCallId, grabRoundTripSignature(tc)),
|
|
5137
5333
|
name: tc.toolName,
|
|
5138
5334
|
input: tc.input
|
|
5139
5335
|
}))
|
|
@@ -5259,11 +5455,13 @@ function startProxyCatalog(routes, defaultAliasId, debug = false) {
|
|
|
5259
5455
|
return;
|
|
5260
5456
|
}
|
|
5261
5457
|
if (route.modelFormat === "anthropic") {
|
|
5458
|
+
const betaHeaderRaw = req.headers["anthropic-beta"];
|
|
5459
|
+
const inboundBeta = Array.isArray(betaHeaderRaw) ? betaHeaderRaw.join(",") : betaHeaderRaw;
|
|
5262
5460
|
const forwardBody = { ...anthropicBody, model: route.realModelId };
|
|
5263
5461
|
const targetUrl = `${upstreamUrl}/v1/messages`;
|
|
5264
5462
|
plog(() => `anthropic-passthrough: model=${route.realModelId}, stream=${clientWantsStream}`);
|
|
5265
5463
|
try {
|
|
5266
|
-
await relayAnthropicMessages(res, targetUrl, forwardBody, apiKey, clientWantsStream);
|
|
5464
|
+
await relayAnthropicMessages(res, targetUrl, forwardBody, apiKey, clientWantsStream, inboundBeta);
|
|
5267
5465
|
} catch (err) {
|
|
5268
5466
|
const message = err instanceof UpstreamUnreachableError ? err.message : String(err);
|
|
5269
5467
|
plog(() => `anthropic-passthrough error: ${message}`);
|
|
@@ -5272,7 +5470,15 @@ function startProxyCatalog(routes, defaultAliasId, debug = false) {
|
|
|
5272
5470
|
return;
|
|
5273
5471
|
}
|
|
5274
5472
|
if (isSdkMigratedNpm(route.npm)) {
|
|
5275
|
-
const params = translateRequest(anthropicBody, route.npm
|
|
5473
|
+
const params = translateRequest(anthropicBody, route.npm, {
|
|
5474
|
+
reasoningMetadata: {
|
|
5475
|
+
providerId: route.providerId,
|
|
5476
|
+
apiBaseUrl: route.baseURL,
|
|
5477
|
+
supportedParameters: route.supportedParameters,
|
|
5478
|
+
reasoning: route.reasoning,
|
|
5479
|
+
interleavedReasoningField: route.interleavedReasoningField
|
|
5480
|
+
}
|
|
5481
|
+
});
|
|
5276
5482
|
plog(
|
|
5277
5483
|
() => `sdk: npm=${route.npm} model=${route.realModelId}, stream=${clientWantsStream}, tools=${anthropicBody.tools?.length ?? 0}, msgs=${params.messages.length}`
|
|
5278
5484
|
);
|
|
@@ -5349,7 +5555,11 @@ function startProxy(completionsUrl, modelId, debug = false, contextWindow, sdk)
|
|
|
5349
5555
|
modelFormat: "openai",
|
|
5350
5556
|
contextWindow,
|
|
5351
5557
|
npm: sdk?.npm,
|
|
5352
|
-
baseURL: sdk?.baseURL
|
|
5558
|
+
baseURL: sdk?.baseURL,
|
|
5559
|
+
providerId: sdk?.providerId,
|
|
5560
|
+
supportedParameters: sdk?.supportedParameters,
|
|
5561
|
+
reasoning: sdk?.reasoning,
|
|
5562
|
+
interleavedReasoningField: sdk?.interleavedReasoningField
|
|
5353
5563
|
}], clientModelId, debug);
|
|
5354
5564
|
}
|
|
5355
5565
|
|
|
@@ -5366,7 +5576,11 @@ function localModelToRoute(lp, model) {
|
|
|
5366
5576
|
modelFormat: model.modelFormat,
|
|
5367
5577
|
contextWindow: model.contextWindow,
|
|
5368
5578
|
npm: model.npm,
|
|
5369
|
-
baseURL: model.apiBaseUrl
|
|
5579
|
+
baseURL: model.apiBaseUrl,
|
|
5580
|
+
providerId: lp.id,
|
|
5581
|
+
supportedParameters: model.supportedParameters,
|
|
5582
|
+
reasoning: model.reasoning,
|
|
5583
|
+
interleavedReasoningField: model.interleavedReasoningField
|
|
5370
5584
|
};
|
|
5371
5585
|
}
|
|
5372
5586
|
function zenGoModelToRoute(model, apiKey) {
|
|
@@ -5384,7 +5598,8 @@ function zenGoModelToRoute(model, apiKey) {
|
|
|
5384
5598
|
// openai-format Zen/Go models route through the SDK (openai-compatible);
|
|
5385
5599
|
// anthropic models stay direct passthrough (no npm).
|
|
5386
5600
|
npm: isAnthropic ? void 0 : "@ai-sdk/openai-compatible",
|
|
5387
|
-
baseURL: isAnthropic ? void 0 : `${backend.baseUrl}/v1
|
|
5601
|
+
baseURL: isAnthropic ? void 0 : `${backend.baseUrl}/v1`,
|
|
5602
|
+
providerId: model.sourceBackend
|
|
5388
5603
|
};
|
|
5389
5604
|
}
|
|
5390
5605
|
function makeRouteResolver(localProviders, zenModels, goModels, zenGoApiKey) {
|
|
@@ -5554,6 +5769,7 @@ function cachedModelToLocal(cached, provider) {
|
|
|
5554
5769
|
const apiUrl = cached.apiUrl ?? provider.api.url ?? "";
|
|
5555
5770
|
const endpoint = resolveEndpoint(npm, apiUrl);
|
|
5556
5771
|
if (endpoint === null) return null;
|
|
5772
|
+
const modelsDev = findModelsDevModel(provider.id, cached.id);
|
|
5557
5773
|
const { id, upstreamModelId: upstreamModelId2 } = normalizeGoogleModelId(cached.id, npm);
|
|
5558
5774
|
const normalizedUpstream = normalizeGoogleModelId(cached.upstreamModelId ?? cached.id, npm).upstreamModelId;
|
|
5559
5775
|
const family = npm === "@ai-sdk/google" ? id.split(/[-/:]/)[0] ?? id : cached.family ?? "";
|
|
@@ -5569,7 +5785,10 @@ function cachedModelToLocal(cached, provider) {
|
|
|
5569
5785
|
npm: npm || void 0,
|
|
5570
5786
|
apiBaseUrl: apiUrl || void 0,
|
|
5571
5787
|
cost: cached.cost,
|
|
5572
|
-
contextWindow: cached.contextWindow ?? resolveContextWindow(id)
|
|
5788
|
+
contextWindow: cached.contextWindow ?? resolveContextWindow(id),
|
|
5789
|
+
supportedParameters: cached.supportedParameters,
|
|
5790
|
+
reasoning: cached.reasoning ?? modelsDev?.reasoning,
|
|
5791
|
+
interleavedReasoningField: cached.interleavedReasoningField ?? modelsDev?.interleaved?.field
|
|
5573
5792
|
};
|
|
5574
5793
|
}
|
|
5575
5794
|
function materializeOne(provider, resolveCredential, agent) {
|
|
@@ -5770,7 +5989,10 @@ function localProvidersToServerModels(localProviders) {
|
|
|
5770
5989
|
npm: model.modelFormat === "openai" ? model.npm || "@ai-sdk/openai-compatible" : model.npm,
|
|
5771
5990
|
apiBaseUrl: model.apiBaseUrl,
|
|
5772
5991
|
apiKey: provider.apiKey,
|
|
5773
|
-
contextWindow: model.contextWindow
|
|
5992
|
+
contextWindow: model.contextWindow,
|
|
5993
|
+
supportedParameters: model.supportedParameters,
|
|
5994
|
+
reasoning: model.reasoning,
|
|
5995
|
+
interleavedReasoningField: model.interleavedReasoningField
|
|
5774
5996
|
}))
|
|
5775
5997
|
);
|
|
5776
5998
|
}
|
|
@@ -5893,11 +6115,18 @@ async function askSaveServerPassword() {
|
|
|
5893
6115
|
|
|
5894
6116
|
// src/server/router.ts
|
|
5895
6117
|
import { createServer as createServer2 } from "http";
|
|
6118
|
+
function makeServerLog(debugLogPath) {
|
|
6119
|
+
if (!debugLogPath) return () => {
|
|
6120
|
+
};
|
|
6121
|
+
resetTraceLog(debugLogPath);
|
|
6122
|
+
return (msg) => writeSecureLogLine(debugLogPath, typeof msg === "function" ? msg() : msg);
|
|
6123
|
+
}
|
|
5896
6124
|
async function startServer(options) {
|
|
5897
6125
|
silenceSdkWarnings();
|
|
5898
6126
|
const languageModelCache = /* @__PURE__ */ new Map();
|
|
6127
|
+
const plog = makeServerLog(options.debugLogPath);
|
|
5899
6128
|
const server = createServer2((req, res) => {
|
|
5900
|
-
void routeRequest(req, res, options, languageModelCache);
|
|
6129
|
+
void routeRequest(req, res, options, languageModelCache, plog);
|
|
5901
6130
|
});
|
|
5902
6131
|
await new Promise((resolve, reject2) => {
|
|
5903
6132
|
server.once("error", reject2);
|
|
@@ -5920,9 +6149,10 @@ async function startServer(options) {
|
|
|
5920
6149
|
})
|
|
5921
6150
|
};
|
|
5922
6151
|
}
|
|
5923
|
-
async function routeRequest(req, res, options, modelCache) {
|
|
6152
|
+
async function routeRequest(req, res, options, modelCache, plog) {
|
|
5924
6153
|
try {
|
|
5925
6154
|
const pathname = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`).pathname;
|
|
6155
|
+
plog(`${req.method} ${pathname}`);
|
|
5926
6156
|
if (req.method === "GET" && pathname === "/health") {
|
|
5927
6157
|
sendJson(res, 200, { ok: true });
|
|
5928
6158
|
return;
|
|
@@ -5944,7 +6174,7 @@ async function routeRequest(req, res, options, modelCache) {
|
|
|
5944
6174
|
return;
|
|
5945
6175
|
}
|
|
5946
6176
|
if (req.method === "POST" && pathname === "/anthropic/v1/messages") {
|
|
5947
|
-
await handleAnthropicMessages(req, res, options, modelCache);
|
|
6177
|
+
await handleAnthropicMessages(req, res, options, modelCache, plog);
|
|
5948
6178
|
return;
|
|
5949
6179
|
}
|
|
5950
6180
|
if (req.method === "POST" && pathname === "/openai/v1/chat/completions") {
|
|
@@ -5956,14 +6186,18 @@ async function routeRequest(req, res, options, modelCache) {
|
|
|
5956
6186
|
sendJson(res, 500, { error: { message: err instanceof Error ? err.message : String(err) } });
|
|
5957
6187
|
}
|
|
5958
6188
|
}
|
|
5959
|
-
async function handleAnthropicMessages(req, res, options, modelCache) {
|
|
6189
|
+
async function handleAnthropicMessages(req, res, options, modelCache, plog) {
|
|
5960
6190
|
const body = await readJson(req);
|
|
5961
6191
|
if (!body) {
|
|
5962
6192
|
sendJson(res, 400, { error: { message: "Invalid JSON body" } });
|
|
5963
6193
|
return;
|
|
5964
6194
|
}
|
|
5965
6195
|
const model = lookupModel(res, options.catalog, body.model);
|
|
5966
|
-
if (!model)
|
|
6196
|
+
if (!model) {
|
|
6197
|
+
plog(`model not found: ${body.model}`);
|
|
6198
|
+
return;
|
|
6199
|
+
}
|
|
6200
|
+
plog(() => `anthropic-messages model=${body.model} format=${model.modelFormat} npm=${model.npm ?? "none"} stream=${body.stream}`);
|
|
5967
6201
|
if (model.modelFormat === "anthropic") {
|
|
5968
6202
|
if (model.baseUrl && !/^https?:\/\//i.test(model.baseUrl)) {
|
|
5969
6203
|
sendJson(res, 400, { error: { message: `Invalid provider baseUrl: must be http:// or https://` } });
|
|
@@ -5971,7 +6205,10 @@ async function handleAnthropicMessages(req, res, options, modelCache) {
|
|
|
5971
6205
|
}
|
|
5972
6206
|
const messagesUrl = model.baseUrl ? `${model.baseUrl}/v1/messages` : `${backendFor(options, model).baseUrl}/v1/messages`;
|
|
5973
6207
|
const apiKey = model.apiKey ?? options.apiKey;
|
|
5974
|
-
|
|
6208
|
+
const betaHeaderRaw = req.headers["anthropic-beta"];
|
|
6209
|
+
const inboundBeta = Array.isArray(betaHeaderRaw) ? betaHeaderRaw.join(",") : betaHeaderRaw;
|
|
6210
|
+
plog(() => `anthropic-passthrough \u2192 ${messagesUrl}`);
|
|
6211
|
+
await forwardJson(res, messagesUrl, { ...body, model: upstreamModelId(model) }, apiKey, inboundBeta);
|
|
5975
6212
|
return;
|
|
5976
6213
|
}
|
|
5977
6214
|
if (model.modelFormat === "openai") {
|
|
@@ -5980,22 +6217,32 @@ async function handleAnthropicMessages(req, res, options, modelCache) {
|
|
|
5980
6217
|
return;
|
|
5981
6218
|
}
|
|
5982
6219
|
const apiKey = model.apiKey ?? options.apiKey;
|
|
5983
|
-
|
|
6220
|
+
const cacheKey = sdkModelCacheKey(model);
|
|
6221
|
+
let languageModel = modelCache.get(cacheKey);
|
|
5984
6222
|
if (!languageModel) {
|
|
5985
6223
|
languageModel = await createLanguageModel({
|
|
5986
6224
|
npm: model.npm,
|
|
5987
6225
|
modelId: upstreamModelId(model),
|
|
5988
6226
|
apiKey,
|
|
5989
6227
|
baseURL: model.apiBaseUrl,
|
|
5990
|
-
providerId: model.sourceBackend,
|
|
6228
|
+
providerId: model.providerId ?? model.sourceBackend,
|
|
5991
6229
|
vertex: options.vertex
|
|
5992
6230
|
});
|
|
5993
|
-
modelCache.set(
|
|
6231
|
+
modelCache.set(cacheKey, languageModel);
|
|
5994
6232
|
}
|
|
5995
6233
|
const params = translateRequest(body, model.npm, {
|
|
5996
|
-
defaultEffort: anthropicEffortFromRequest(body) ? void 0 : model.defaultEffort
|
|
6234
|
+
defaultEffort: anthropicEffortFromRequest(body) ? void 0 : model.defaultEffort,
|
|
6235
|
+
reasoningMetadata: {
|
|
6236
|
+
providerId: model.providerId,
|
|
6237
|
+
apiBaseUrl: model.apiBaseUrl,
|
|
6238
|
+
supportedParameters: model.supportedParameters,
|
|
6239
|
+
reasoning: model.reasoning,
|
|
6240
|
+
interleavedReasoningField: model.interleavedReasoningField
|
|
6241
|
+
}
|
|
5997
6242
|
});
|
|
5998
6243
|
const clientWantsStream = Boolean(body.stream);
|
|
6244
|
+
const responseModelId = options.gateway?.maskGatewayIds ? gatewayDisplayName(model, options.gateway) : typeof body.model === "string" ? body.model : model.id;
|
|
6245
|
+
plog(() => `sdk npm=${model.npm} upstream=${upstreamModelId(model)} responseModel=${responseModelId} stream=${clientWantsStream}`);
|
|
5999
6246
|
try {
|
|
6000
6247
|
if (clientWantsStream) {
|
|
6001
6248
|
res.writeHead(200, {
|
|
@@ -6003,12 +6250,10 @@ async function handleAnthropicMessages(req, res, options, modelCache) {
|
|
|
6003
6250
|
"Cache-Control": "no-cache",
|
|
6004
6251
|
"Connection": "keep-alive"
|
|
6005
6252
|
});
|
|
6006
|
-
|
|
6007
|
-
await streamAnthropicResponse(languageModel, params, clientModel, (chunk) => res.write(chunk));
|
|
6253
|
+
await streamAnthropicResponse(languageModel, params, responseModelId, (chunk) => res.write(chunk));
|
|
6008
6254
|
res.end();
|
|
6009
6255
|
} else {
|
|
6010
|
-
const
|
|
6011
|
-
const anthropicResponse = await generateAnthropicResponse(languageModel, params, clientModel);
|
|
6256
|
+
const anthropicResponse = await generateAnthropicResponse(languageModel, params, responseModelId);
|
|
6012
6257
|
sendJson(res, 200, anthropicResponse);
|
|
6013
6258
|
}
|
|
6014
6259
|
} catch (err) {
|
|
@@ -6029,6 +6274,14 @@ async function handleOpenAIChatCompletions(req, res, options) {
|
|
|
6029
6274
|
const model = lookupModel(res, options.catalog, body.model);
|
|
6030
6275
|
if (!model) return;
|
|
6031
6276
|
if (model.modelFormat === "openai") {
|
|
6277
|
+
if (!isOpenAIChatCompletionsModel(model)) {
|
|
6278
|
+
sendJson(res, 400, {
|
|
6279
|
+
error: {
|
|
6280
|
+
message: `OpenAI chat completions are not available for model: ${model.id}. Use /anthropic/v1/messages.`
|
|
6281
|
+
}
|
|
6282
|
+
});
|
|
6283
|
+
return;
|
|
6284
|
+
}
|
|
6032
6285
|
if (model.completionsUrl && !/^https?:\/\//i.test(model.completionsUrl)) {
|
|
6033
6286
|
sendJson(res, 400, { error: { message: `Invalid provider completionsUrl: must be http:// or https://` } });
|
|
6034
6287
|
return;
|
|
@@ -6064,15 +6317,22 @@ function backendFor(options, model) {
|
|
|
6064
6317
|
if (model.sourceBackend === "go") return options.backends.go;
|
|
6065
6318
|
throw new Error(`Provider ${model.sourceBackend} is not a cloud backend \u2014 model must set baseUrl/completionsUrl`);
|
|
6066
6319
|
}
|
|
6067
|
-
|
|
6068
|
-
|
|
6320
|
+
function sdkModelCacheKey(model) {
|
|
6321
|
+
return [
|
|
6322
|
+
model.providerId ?? model.sourceBackend,
|
|
6323
|
+
model.id,
|
|
6324
|
+
upstreamModelId(model),
|
|
6325
|
+
model.npm ?? "",
|
|
6326
|
+
model.apiBaseUrl ?? ""
|
|
6327
|
+
].join("");
|
|
6328
|
+
}
|
|
6329
|
+
async function forwardJson(res, url, body, apiKey, inboundBeta) {
|
|
6330
|
+
const upstream = await postJsonUpstream(url, body, apiKey, inboundBeta);
|
|
6069
6331
|
sendJson(res, upstream.status, upstream.body);
|
|
6070
6332
|
}
|
|
6071
6333
|
async function readJson(req) {
|
|
6072
6334
|
try {
|
|
6073
|
-
const
|
|
6074
|
-
for await (const chunk of req) chunks.push(Buffer.from(chunk));
|
|
6075
|
-
const raw = Buffer.concat(chunks).toString();
|
|
6335
|
+
const raw = await readBody(req);
|
|
6076
6336
|
return raw ? JSON.parse(raw) : {};
|
|
6077
6337
|
} catch {
|
|
6078
6338
|
return null;
|
|
@@ -6228,7 +6488,9 @@ function resolveVertexLocation(env = process.env) {
|
|
|
6228
6488
|
function defaultAdcCredentialsPath(home = homedir7()) {
|
|
6229
6489
|
return join10(home, ".config", "gcloud", "application_default_credentials.json");
|
|
6230
6490
|
}
|
|
6231
|
-
function hasApplicationDefaultCredentials(home = homedir7(), adcPath = defaultAdcCredentialsPath(home)) {
|
|
6491
|
+
function hasApplicationDefaultCredentials(home = homedir7(), adcPath = defaultAdcCredentialsPath(home), env = process.env) {
|
|
6492
|
+
const explicitPath = env["GOOGLE_APPLICATION_CREDENTIALS"]?.trim();
|
|
6493
|
+
if (explicitPath && existsSync10(explicitPath)) return true;
|
|
6232
6494
|
return existsSync10(adcPath);
|
|
6233
6495
|
}
|
|
6234
6496
|
function loadVertexModelEntries(env = process.env) {
|
|
@@ -6259,19 +6521,23 @@ function buildVertexRuntimeConfig(env = process.env) {
|
|
|
6259
6521
|
};
|
|
6260
6522
|
}
|
|
6261
6523
|
function vertexModelsToServerModels(config) {
|
|
6262
|
-
return config.models.map((model) =>
|
|
6263
|
-
|
|
6264
|
-
|
|
6265
|
-
|
|
6266
|
-
|
|
6267
|
-
|
|
6268
|
-
|
|
6269
|
-
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
|
|
6274
|
-
|
|
6524
|
+
return config.models.map((model) => {
|
|
6525
|
+
const caps = getReasoningCapabilities(VERTEX_ANTHROPIC_NPM, model.upstream_id ?? model.id);
|
|
6526
|
+
return {
|
|
6527
|
+
id: model.id,
|
|
6528
|
+
name: model.display_name,
|
|
6529
|
+
isFree: false,
|
|
6530
|
+
brand: "Anthropic",
|
|
6531
|
+
sourceBackend: "vertex",
|
|
6532
|
+
modelFormat: "openai",
|
|
6533
|
+
upstreamModelId: model.upstream_id ?? model.id,
|
|
6534
|
+
npm: VERTEX_ANTHROPIC_NPM,
|
|
6535
|
+
providerLabel: "Vertex AI",
|
|
6536
|
+
providerId: "vertex",
|
|
6537
|
+
contextWindow: resolveContextWindow(model.id),
|
|
6538
|
+
...caps.defaultLevel ? { defaultEffort: caps.defaultLevel } : {}
|
|
6539
|
+
};
|
|
6540
|
+
});
|
|
6275
6541
|
}
|
|
6276
6542
|
function vertexClientModelLookupCandidates(modelId) {
|
|
6277
6543
|
const candidates = [modelId];
|
|
@@ -6388,7 +6654,13 @@ async function loadServerModels() {
|
|
|
6388
6654
|
}
|
|
6389
6655
|
function enrichServerModelReasoning(model) {
|
|
6390
6656
|
if (!model.npm || model.modelFormat !== "openai") return model;
|
|
6391
|
-
const caps = getReasoningCapabilities(model.npm, upstreamModelId(model)
|
|
6657
|
+
const caps = getReasoningCapabilities(model.npm, upstreamModelId(model), {
|
|
6658
|
+
providerId: model.providerId,
|
|
6659
|
+
apiBaseUrl: model.apiBaseUrl,
|
|
6660
|
+
supportedParameters: model.supportedParameters,
|
|
6661
|
+
reasoning: model.reasoning,
|
|
6662
|
+
interleavedReasoningField: model.interleavedReasoningField
|
|
6663
|
+
});
|
|
6392
6664
|
if (!caps.defaultLevel) return model;
|
|
6393
6665
|
return { ...model, defaultEffort: caps.defaultLevel };
|
|
6394
6666
|
}
|
|
@@ -6974,6 +7246,71 @@ async function pickGlobalFavoriteModel(providers, favorites) {
|
|
|
6974
7246
|
}
|
|
6975
7247
|
}
|
|
6976
7248
|
|
|
7249
|
+
// src/favorites-resolver.ts
|
|
7250
|
+
var ZEN_GO_PROVIDER_NAME = {
|
|
7251
|
+
zen: "OpenCode Zen",
|
|
7252
|
+
go: "OpenCode Go"
|
|
7253
|
+
};
|
|
7254
|
+
function resolveFavorite(fav, ctx) {
|
|
7255
|
+
if (fav.providerId === "zen" || fav.providerId === "go") {
|
|
7256
|
+
if (!ctx.zenGoApiKey) return void 0;
|
|
7257
|
+
const models = fav.providerId === "zen" ? ctx.zenModels : ctx.goModels;
|
|
7258
|
+
const model = models?.find((m) => m.id === fav.modelId);
|
|
7259
|
+
if (!model) return void 0;
|
|
7260
|
+
return {
|
|
7261
|
+
providerId: fav.providerId,
|
|
7262
|
+
providerName: ZEN_GO_PROVIDER_NAME[fav.providerId],
|
|
7263
|
+
model,
|
|
7264
|
+
apiKey: ctx.zenGoApiKey,
|
|
7265
|
+
sourceBackend: fav.providerId
|
|
7266
|
+
};
|
|
7267
|
+
}
|
|
7268
|
+
if (ctx.findLocalModel) {
|
|
7269
|
+
const found = ctx.findLocalModel(fav.providerId, fav.modelId);
|
|
7270
|
+
if (!found) return void 0;
|
|
7271
|
+
if (ctx.agent && shouldHideModel({ providerId: fav.providerId, modelId: fav.modelId, agent: ctx.agent })) {
|
|
7272
|
+
return void 0;
|
|
7273
|
+
}
|
|
7274
|
+
return {
|
|
7275
|
+
providerId: fav.providerId,
|
|
7276
|
+
providerName: found.provider.name,
|
|
7277
|
+
model: found.model,
|
|
7278
|
+
apiKey: found.provider.apiKey
|
|
7279
|
+
};
|
|
7280
|
+
}
|
|
7281
|
+
return void 0;
|
|
7282
|
+
}
|
|
7283
|
+
function buildFavoritesList(starting, favorites, ctx, max = 20) {
|
|
7284
|
+
const droppedFavorites = [];
|
|
7285
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7286
|
+
const out = [];
|
|
7287
|
+
if (starting) {
|
|
7288
|
+
seen.add(`${starting.providerId}::${starting.model.id}`);
|
|
7289
|
+
out.push(starting);
|
|
7290
|
+
}
|
|
7291
|
+
for (const fav of favorites) {
|
|
7292
|
+
if (out.length >= max) break;
|
|
7293
|
+
const key = `${fav.providerId}::${fav.modelId}`;
|
|
7294
|
+
if (seen.has(key)) continue;
|
|
7295
|
+
const resolved = resolveFavorite(fav, ctx);
|
|
7296
|
+
if (!resolved) {
|
|
7297
|
+
droppedFavorites.push(fav);
|
|
7298
|
+
continue;
|
|
7299
|
+
}
|
|
7300
|
+
seen.add(key);
|
|
7301
|
+
out.push(resolved);
|
|
7302
|
+
}
|
|
7303
|
+
return { resolved: out, droppedFavorites };
|
|
7304
|
+
}
|
|
7305
|
+
function resolveFirstAvailableFavorite(favorites, providers) {
|
|
7306
|
+
for (const fav of favorites) {
|
|
7307
|
+
const provider = providers.find((lp) => lp.id === fav.providerId);
|
|
7308
|
+
const model = provider?.models.find((m) => m.id === fav.modelId);
|
|
7309
|
+
if (provider && model) return { provider, model };
|
|
7310
|
+
}
|
|
7311
|
+
return void 0;
|
|
7312
|
+
}
|
|
7313
|
+
|
|
6977
7314
|
// src/providers-command.ts
|
|
6978
7315
|
import pc10 from "picocolors";
|
|
6979
7316
|
import * as p10 from "@clack/prompts";
|
|
@@ -7136,18 +7473,35 @@ function modelInfoToCached(m, npm, apiUrl) {
|
|
|
7136
7473
|
async function refreshZenGoProvider(provider) {
|
|
7137
7474
|
const backendId = provider.id === "go" || provider.templateId === "go" ? "go" : "zen";
|
|
7138
7475
|
const result = await getModels(BACKENDS[backendId]);
|
|
7139
|
-
return result.models.filter((m) => m.modelFormat !== "unsupported").map((m) =>
|
|
7476
|
+
return result.models.filter((m) => m.modelFormat !== "unsupported").map((m) => {
|
|
7477
|
+
const isAnthropic = m.modelFormat === "anthropic";
|
|
7478
|
+
const npm = isAnthropic ? "@ai-sdk/anthropic" : "@ai-sdk/openai-compatible";
|
|
7479
|
+
const apiUrl = isAnthropic ? BACKENDS[backendId].baseUrl : `${BACKENDS[backendId].baseUrl}/v1`;
|
|
7480
|
+
return modelInfoToCached(m, npm, apiUrl);
|
|
7481
|
+
});
|
|
7140
7482
|
}
|
|
7141
7483
|
async function refreshApiListProvider(provider, apiKey) {
|
|
7142
7484
|
const npm = provider.api.npm ?? "@ai-sdk/openai-compatible";
|
|
7143
7485
|
const catalogTemplate = resolveProviderTemplate(provider);
|
|
7144
7486
|
const baseUrl = effectiveProviderBaseUrl(provider, catalogTemplate);
|
|
7145
|
-
const template = catalogTemplate ?? syntheticTemplate(provider, baseUrl);
|
|
7146
7487
|
if (!baseUrl) {
|
|
7147
7488
|
return { models: [], error: "Provider has no API base URL configured." };
|
|
7148
7489
|
}
|
|
7490
|
+
let safeBaseUrl = baseUrl;
|
|
7491
|
+
const configuredUrl = provider.api.url?.trim();
|
|
7492
|
+
const templateDefault = catalogTemplate?.defaultBaseUrl?.trim();
|
|
7493
|
+
if (configuredUrl && configuredUrl !== templateDefault) {
|
|
7494
|
+
const urlCheck = await validateCustomEndpointUrl(baseUrl, {
|
|
7495
|
+
allowInsecureLocal: catalogTemplate?.apiKeyOptional === true
|
|
7496
|
+
});
|
|
7497
|
+
if (!urlCheck.ok || !urlCheck.normalizedUrl) {
|
|
7498
|
+
return { models: [], error: `${urlCheck.error ?? "Invalid API base URL."} ${urlCheck.hint ?? ""}`.trim() };
|
|
7499
|
+
}
|
|
7500
|
+
safeBaseUrl = urlCheck.normalizedUrl;
|
|
7501
|
+
}
|
|
7502
|
+
const template = catalogTemplate ?? syntheticTemplate(provider, safeBaseUrl);
|
|
7149
7503
|
if (npm === "@ai-sdk/anthropic") {
|
|
7150
|
-
const fetched2 = await fetchAnthropicModels(
|
|
7504
|
+
const fetched2 = await fetchAnthropicModels(safeBaseUrl, apiKey);
|
|
7151
7505
|
if (fetched2.error || fetched2.models.length === 0) {
|
|
7152
7506
|
return { models: [], error: fetched2.error ?? "No models returned.", baseUrl: fetched2.baseUrl };
|
|
7153
7507
|
}
|
|
@@ -7156,7 +7510,7 @@ async function refreshApiListProvider(provider, apiKey) {
|
|
|
7156
7510
|
baseUrl: fetched2.baseUrl
|
|
7157
7511
|
};
|
|
7158
7512
|
}
|
|
7159
|
-
const fetched = await fetchTemplateModels(template, apiKey,
|
|
7513
|
+
const fetched = await fetchTemplateModels(template, apiKey, safeBaseUrl);
|
|
7160
7514
|
if (fetched.error || fetched.models.length === 0) {
|
|
7161
7515
|
return { models: [], error: fetched.error ?? "No models returned." };
|
|
7162
7516
|
}
|
|
@@ -7206,7 +7560,10 @@ async function refreshProviderModels(providerId, apiKey, registry = loadRegistry
|
|
|
7206
7560
|
if (source === "zen-go-api") {
|
|
7207
7561
|
models = await refreshZenGoProvider(provider);
|
|
7208
7562
|
} else {
|
|
7209
|
-
|
|
7563
|
+
const template = resolveProviderTemplate(provider);
|
|
7564
|
+
const keyOptional = template?.apiKeyOptional === true;
|
|
7565
|
+
const effectiveKey = keyOptional && isLikelyPlaceholderKey(apiKey) ? "" : apiKey;
|
|
7566
|
+
if (!keyOptional && isLikelyPlaceholderKey(effectiveKey)) {
|
|
7210
7567
|
if (cachedModelCount(provider) > 0) {
|
|
7211
7568
|
return skipWithCachedModels(
|
|
7212
7569
|
provider,
|
|
@@ -7220,7 +7577,7 @@ async function refreshProviderModels(providerId, apiKey, registry = loadRegistry
|
|
|
7220
7577
|
reason: "No usable API key \u2014 add the provider via relay-ai providers add with a real key."
|
|
7221
7578
|
};
|
|
7222
7579
|
}
|
|
7223
|
-
if (!
|
|
7580
|
+
if (!keyOptional && !effectiveKey) {
|
|
7224
7581
|
return {
|
|
7225
7582
|
id: provider.id,
|
|
7226
7583
|
name: provider.name,
|
|
@@ -7228,7 +7585,7 @@ async function refreshProviderModels(providerId, apiKey, registry = loadRegistry
|
|
|
7228
7585
|
reason: "API key not available \u2014 cannot refresh models."
|
|
7229
7586
|
};
|
|
7230
7587
|
}
|
|
7231
|
-
const fetched = await refreshApiListProvider(provider,
|
|
7588
|
+
const fetched = await refreshApiListProvider(provider, effectiveKey ?? "");
|
|
7232
7589
|
if (fetched.error) {
|
|
7233
7590
|
if ((fetched.error.includes("rejected") || fetched.error.includes("401") || fetched.error.includes("403")) && cachedModelCount(provider) > 0) {
|
|
7234
7591
|
return skipWithCachedModels(
|
|
@@ -7425,7 +7782,10 @@ async function authenticateProvider(providerId, options = {}) {
|
|
|
7425
7782
|
if (!supportsNativeOAuth(providerId)) {
|
|
7426
7783
|
if (findOpencodeBinary()) {
|
|
7427
7784
|
const cred2 = await runOpencodeAuthBroker(providerId, { method: options.brokerMethod });
|
|
7428
|
-
await saveProviderCredential(oauthAuthRef(providerId), oauthCredentialToKeychainJson(cred2));
|
|
7785
|
+
const saved2 = await saveProviderCredential(oauthAuthRef(providerId), oauthCredentialToKeychainJson(cred2));
|
|
7786
|
+
if (!saved2) {
|
|
7787
|
+
p9.log.warn("Could not save OAuth tokens to Keychain \u2014 session may not persist.");
|
|
7788
|
+
}
|
|
7429
7789
|
const registryProvider2 = await upsertOAuthProvider(providerId, cred2);
|
|
7430
7790
|
return { providerId, credential: cred2, registryProvider: registryProvider2 };
|
|
7431
7791
|
}
|
|
@@ -7547,7 +7907,7 @@ ${pc10.bold("Usage:")}
|
|
|
7547
7907
|
${pc10.bold("Subcommands:")}
|
|
7548
7908
|
(none) Provider hub wizard ${pc10.dim("[Phase 1.1]")}
|
|
7549
7909
|
add Add a provider (Groq, Mistral, Together AI, \u2026) ${pc10.dim("[Phase 1.1]")}
|
|
7550
|
-
import
|
|
7910
|
+
import import providers from 'open code CLI' (one-time) ${pc10.dim("[Phase 1.0]")}
|
|
7551
7911
|
auth Sign in with OAuth (xAI, OpenAI ChatGPT) ${pc10.dim("[Phase 2]")}
|
|
7552
7912
|
list Show configured providers ${pc10.dim("[Phase 1.0]")}
|
|
7553
7913
|
remove Remove a provider by id ${pc10.dim("[Phase 1.1]")}
|
|
@@ -7596,7 +7956,7 @@ async function runProvidersImport() {
|
|
|
7596
7956
|
);
|
|
7597
7957
|
if (result.skipped.length > 0) {
|
|
7598
7958
|
for (const s of result.skipped) {
|
|
7599
|
-
const reason = s.reason === "user-skipped" ? "skipped by you" : s.reason === "conflict-kept" ? "kept your existing config" : s.reason === "oauth-no-token" ? "OAuth provider in OpenCode but not signed in \u2014 run relay-ai providers auth" : s.reason === "no-api-key" ? "no API key in OpenCode \u2014 add key there or use relay-ai providers add" : s.reason === "manual-only" ? "uses gcloud/AWS credentials \u2014 not importable via API key" : s.reason === "placeholder-key" ? "placeholder API key \u2014 provider not imported" : s.reason === "invalid-key" ? "API key failed verification \u2014 provider not imported" : s.reason;
|
|
7959
|
+
const reason = s.reason === "user-skipped" ? "skipped by you" : s.reason === "conflict-kept" ? "kept your existing config" : s.reason === "oauth-no-token" ? "OAuth provider in OpenCode but not signed in \u2014 run relay-ai providers auth" : s.reason === "no-api-key" ? "no API key in OpenCode \u2014 add key there or use relay-ai providers add" : s.reason === "manual-only" ? "uses gcloud/AWS credentials \u2014 not importable via API key" : s.reason === "placeholder-key" ? "placeholder API key \u2014 provider not imported" : s.reason === "invalid-key" ? "API key failed verification \u2014 provider not imported" : s.reason === "credential-save-failed" ? "could not save credential \u2014 provider not imported" : s.reason;
|
|
7600
7960
|
p10.log.warn(`Skipped ${s.name} (${s.id}): ${reason}`);
|
|
7601
7961
|
}
|
|
7602
7962
|
}
|
|
@@ -7607,6 +7967,18 @@ async function runProvidersImport() {
|
|
|
7607
7967
|
}
|
|
7608
7968
|
}
|
|
7609
7969
|
}
|
|
7970
|
+
if (result.imported.length > 0) {
|
|
7971
|
+
const refreshSpinner = p10.spinner();
|
|
7972
|
+
refreshSpinner.start("Fetching model capabilities from providers...");
|
|
7973
|
+
await Promise.all(result.imported.map(async (provider) => {
|
|
7974
|
+
const key = await resolveRefreshCredential(
|
|
7975
|
+
provider,
|
|
7976
|
+
async (pr) => resolveProviderCredential(pr.id, pr.authRef)
|
|
7977
|
+
);
|
|
7978
|
+
await refreshProviderModels(provider.id, key);
|
|
7979
|
+
}));
|
|
7980
|
+
refreshSpinner.stop("Model capabilities refreshed.");
|
|
7981
|
+
}
|
|
7610
7982
|
return 0;
|
|
7611
7983
|
}
|
|
7612
7984
|
async function runProvidersAuth(providerId, method) {
|
|
@@ -7696,7 +8068,9 @@ async function runProvidersList() {
|
|
|
7696
8068
|
}
|
|
7697
8069
|
async function pickTemplateFromCatalog() {
|
|
7698
8070
|
while (true) {
|
|
7699
|
-
const
|
|
8071
|
+
const registry = loadRegistry();
|
|
8072
|
+
const configuredIds = new Set(registry.providers.map((p19) => p19.id));
|
|
8073
|
+
const templates = listAddableTemplates(configuredIds);
|
|
7700
8074
|
if (templates.length === 0) return null;
|
|
7701
8075
|
const method = await p10.select({
|
|
7702
8076
|
message: `Choose a provider (${templates.length} available)`,
|
|
@@ -7727,22 +8101,12 @@ async function pickTemplateFromCatalog() {
|
|
|
7727
8101
|
const query = String(searchInput);
|
|
7728
8102
|
const matched = filterTemplates(templates, query);
|
|
7729
8103
|
if (matched.length === 0) {
|
|
7730
|
-
|
|
7731
|
-
|
|
7732
|
-
|
|
7733
|
-
|
|
7734
|
-
|
|
7735
|
-
message: "Add OpenCode Zen / Go with your OpenCode API key?",
|
|
7736
|
-
initialValue: true
|
|
7737
|
-
});
|
|
7738
|
-
if (p10.isCancel(addCloud)) continue;
|
|
7739
|
-
if (addCloud) {
|
|
7740
|
-
await runOpenCodeCloudAddFlow();
|
|
7741
|
-
return null;
|
|
7742
|
-
}
|
|
7743
|
-
continue;
|
|
8104
|
+
const alreadyAdded = filterTemplates(listSupportedTemplates(), query).filter((t) => configuredIds.has(t.id));
|
|
8105
|
+
if (alreadyAdded.length > 0) {
|
|
8106
|
+
p10.log.info(`Already configured: ${alreadyAdded.map((t) => t.name).join(", ")}`);
|
|
8107
|
+
} else {
|
|
8108
|
+
p10.log.warn("No providers match \u2014 try a different search");
|
|
7744
8109
|
}
|
|
7745
|
-
p10.log.warn("No providers match \u2014 try a different search");
|
|
7746
8110
|
continue;
|
|
7747
8111
|
}
|
|
7748
8112
|
const options = matched.map((t) => ({
|
|
@@ -7766,6 +8130,40 @@ async function runTemplateAddFlow() {
|
|
|
7766
8130
|
}
|
|
7767
8131
|
const template = await pickTemplateFromCatalog();
|
|
7768
8132
|
if (!template) return 0;
|
|
8133
|
+
if (template.modelSource === "zen-go-api") {
|
|
8134
|
+
const existingKey = await readGlobalOpencodeCredential();
|
|
8135
|
+
let apiKey2 = existingKey;
|
|
8136
|
+
if (!apiKey2) {
|
|
8137
|
+
printPanel(pc10.cyan("OpenCode cloud"), [
|
|
8138
|
+
`${pc10.white("Get an API key at:")} ${fmtUrl("https://opencode.ai/auth")}`,
|
|
8139
|
+
`${pc10.dim("Uses OpenCode Zen / Go cloud models \u2014 not the same as importing from the OpenCode CLI.")}`
|
|
8140
|
+
]);
|
|
8141
|
+
const collected = await resolveOrCollectApiKey(false, false);
|
|
8142
|
+
if (!collected) {
|
|
8143
|
+
p10.cancel("Cancelled.");
|
|
8144
|
+
return 0;
|
|
8145
|
+
}
|
|
8146
|
+
apiKey2 = collected;
|
|
8147
|
+
}
|
|
8148
|
+
await migrateGlobalOpencodeCredential();
|
|
8149
|
+
const spinner10 = p10.spinner();
|
|
8150
|
+
spinner10.start(`Adding ${template.name}...`);
|
|
8151
|
+
const stub = template.id === "zen" ? addZenRegistryStub() : addGoRegistryStub();
|
|
8152
|
+
if (!stub.added && stub.reason) {
|
|
8153
|
+
spinner10.stop("");
|
|
8154
|
+
p10.log.warn(stub.reason);
|
|
8155
|
+
return 0;
|
|
8156
|
+
}
|
|
8157
|
+
const registry = loadRegistry();
|
|
8158
|
+
const refreshResult = await refreshProviderModels(template.id, apiKey2, registry);
|
|
8159
|
+
spinner10.stop("");
|
|
8160
|
+
if (refreshResult.ok) {
|
|
8161
|
+
p10.log.success(`Added ${template.name} \u2014 ${fmtCount(refreshResult.modelCount ?? 0, "model")} updated.`);
|
|
8162
|
+
} else {
|
|
8163
|
+
p10.log.warn(`Added ${template.name}, but model refresh failed: ${refreshResult.reason ?? "Unknown error"}`);
|
|
8164
|
+
}
|
|
8165
|
+
return 0;
|
|
8166
|
+
}
|
|
7769
8167
|
if (template.signupUrl) {
|
|
7770
8168
|
printPanel(fmtProvider(template.name), [
|
|
7771
8169
|
`${pc10.white("Get an API key at:")} ${fmtUrl(template.signupUrl)}`
|
|
@@ -7811,66 +8209,6 @@ async function runTemplateAddFlow() {
|
|
|
7811
8209
|
logConnected(template.name, result.modelCount ?? 0);
|
|
7812
8210
|
return 0;
|
|
7813
8211
|
}
|
|
7814
|
-
async function runOpenCodeCloudAddFlow() {
|
|
7815
|
-
const registry = loadRegistry();
|
|
7816
|
-
const hasZen = registry.providers.some((pr) => pr.id === "zen");
|
|
7817
|
-
const hasGo = registry.providers.some((pr) => pr.id === "go");
|
|
7818
|
-
if (hasZen && hasGo) {
|
|
7819
|
-
p10.log.info("OpenCode Zen and Go are already configured.");
|
|
7820
|
-
return 0;
|
|
7821
|
-
}
|
|
7822
|
-
printPanel(pc10.cyan("OpenCode cloud"), [
|
|
7823
|
-
`${pc10.white("Get an API key at:")} ${fmtUrl("https://opencode.ai/auth")}`,
|
|
7824
|
-
`${pc10.dim("Uses OpenCode Zen / Go cloud models \u2014 not the same as importing from the OpenCode CLI.")}`
|
|
7825
|
-
]);
|
|
7826
|
-
const existingKey = await readGlobalOpencodeCredential();
|
|
7827
|
-
if (!existingKey) {
|
|
7828
|
-
const apiKey = await resolveOrCollectApiKey(false, false);
|
|
7829
|
-
if (!apiKey) {
|
|
7830
|
-
p10.cancel("Cancelled.");
|
|
7831
|
-
return 0;
|
|
7832
|
-
}
|
|
7833
|
-
}
|
|
7834
|
-
await migrateGlobalOpencodeCredential();
|
|
7835
|
-
let pick;
|
|
7836
|
-
if (!hasZen && !hasGo) {
|
|
7837
|
-
const choice = await p10.select({
|
|
7838
|
-
message: "Which OpenCode cloud backend?",
|
|
7839
|
-
options: [
|
|
7840
|
-
{ value: "zen", label: "OpenCode Zen", hint: "Free + paid models" },
|
|
7841
|
-
{ value: "go", label: "OpenCode Go", hint: "Paid models" },
|
|
7842
|
-
{ value: "both", label: "Both Zen and Go", hint: "Same API key" }
|
|
7843
|
-
]
|
|
7844
|
-
});
|
|
7845
|
-
if (p10.isCancel(choice)) {
|
|
7846
|
-
p10.cancel("Cancelled.");
|
|
7847
|
-
return 0;
|
|
7848
|
-
}
|
|
7849
|
-
pick = choice;
|
|
7850
|
-
} else if (!hasZen) {
|
|
7851
|
-
pick = "zen";
|
|
7852
|
-
} else {
|
|
7853
|
-
pick = "go";
|
|
7854
|
-
}
|
|
7855
|
-
const added = [];
|
|
7856
|
-
if ((pick === "zen" || pick === "both") && !hasZen) {
|
|
7857
|
-
const zen = addZenRegistryStub();
|
|
7858
|
-
if (zen.added) added.push("OpenCode Zen");
|
|
7859
|
-
else if (zen.reason) p10.log.warn(zen.reason);
|
|
7860
|
-
}
|
|
7861
|
-
if ((pick === "go" || pick === "both") && !hasGo) {
|
|
7862
|
-
const go = addGoRegistryStub();
|
|
7863
|
-
if (go.added) added.push("OpenCode Go");
|
|
7864
|
-
else if (go.reason) p10.log.warn(go.reason);
|
|
7865
|
-
}
|
|
7866
|
-
if (added.length === 0) {
|
|
7867
|
-
p10.log.info("Nothing new to add.");
|
|
7868
|
-
return 0;
|
|
7869
|
-
}
|
|
7870
|
-
p10.log.success(`Added ${added.join(" and ")}.`);
|
|
7871
|
-
p10.log.info("Run relay-ai providers refresh-models to cache model lists.");
|
|
7872
|
-
return 0;
|
|
7873
|
-
}
|
|
7874
8212
|
async function runCustomEndpointAddFlow() {
|
|
7875
8213
|
const kindChoice = await p10.select({
|
|
7876
8214
|
message: "Custom server type",
|
|
@@ -7932,14 +8270,9 @@ async function runProvidersAdd() {
|
|
|
7932
8270
|
const registry = loadRegistry();
|
|
7933
8271
|
const hasOpencode = findOpencodeBinary() !== null;
|
|
7934
8272
|
const options = [
|
|
7935
|
-
{
|
|
7936
|
-
value: "opencode-cloud",
|
|
7937
|
-
label: "OpenCode Zen / Go (cloud API key)",
|
|
7938
|
-
hint: "Key from opencode.ai/auth \u2014 not in the Groq/Mistral list"
|
|
7939
|
-
},
|
|
7940
8273
|
{
|
|
7941
8274
|
value: "import",
|
|
7942
|
-
label: "
|
|
8275
|
+
label: "import providers from 'open code CLI'",
|
|
7943
8276
|
hint: hasOpencode ? "Import Groq, OpenAI, etc. from your OpenCode config" : "Requires OpenCode CLI"
|
|
7944
8277
|
}
|
|
7945
8278
|
];
|
|
@@ -7961,9 +8294,6 @@ async function runProvidersAdd() {
|
|
|
7961
8294
|
p10.cancel("Cancelled.");
|
|
7962
8295
|
return 0;
|
|
7963
8296
|
}
|
|
7964
|
-
if (choice === "opencode-cloud") {
|
|
7965
|
-
return runOpenCodeCloudAddFlow();
|
|
7966
|
-
}
|
|
7967
8297
|
if (choice === "import") {
|
|
7968
8298
|
if (!hasOpencode) {
|
|
7969
8299
|
p10.log.error("OpenCode CLI not found. Install from https://opencode.ai");
|
|
@@ -8008,6 +8338,9 @@ async function runCloudBuiltinDetail(id) {
|
|
|
8008
8338
|
printCloudProviderPanel(name);
|
|
8009
8339
|
return "back";
|
|
8010
8340
|
}
|
|
8341
|
+
function providerHubChoiceValue(entry) {
|
|
8342
|
+
return entry.cloudBuiltin ? `cloud:${entry.cloudBuiltin}` : `provider:${entry.id}`;
|
|
8343
|
+
}
|
|
8011
8344
|
async function runProviderDetail(id) {
|
|
8012
8345
|
const registry = loadRegistry();
|
|
8013
8346
|
const provider = registry.providers.find((pr) => pr.id === id);
|
|
@@ -8065,23 +8398,24 @@ async function runProvidersHub() {
|
|
|
8065
8398
|
const hasOpencode = findOpencodeBinary() !== null;
|
|
8066
8399
|
while (true) {
|
|
8067
8400
|
const entries = await resolveProvidersForDisplay();
|
|
8068
|
-
const options = [
|
|
8401
|
+
const options = [
|
|
8402
|
+
{ value: "add", label: pc10.bold("+ Add a provider"), hint: "" }
|
|
8403
|
+
];
|
|
8069
8404
|
for (const entry of entries) {
|
|
8070
8405
|
const hint = entry.id;
|
|
8071
|
-
const value =
|
|
8406
|
+
const value = providerHubChoiceValue(entry);
|
|
8072
8407
|
options.push({
|
|
8073
8408
|
value,
|
|
8074
8409
|
label: providerLabel(entry.name, entry.modelCount, entry.enabled),
|
|
8075
8410
|
hint
|
|
8076
8411
|
});
|
|
8077
8412
|
}
|
|
8078
|
-
options.push({ value: "add", label: "+ Add a provider", hint: "" });
|
|
8079
8413
|
options.push({ value: "auth-menu", label: "\u2192 Sign in with OAuth (xAI / OpenAI)", hint: "Device code or OpenCode broker" });
|
|
8080
8414
|
if (entries.length > 0) {
|
|
8081
8415
|
options.push({ value: "refresh-all", label: "\u21BA Refresh all models", hint: "Update model lists for all providers" });
|
|
8082
8416
|
}
|
|
8083
8417
|
if (hasOpencode) {
|
|
8084
|
-
options.push({ value: "import", label: "\u2192
|
|
8418
|
+
options.push({ value: "import", label: "\u2192 import providers from 'open code CLI'", hint: "One-time import" });
|
|
8085
8419
|
}
|
|
8086
8420
|
options.push({ value: "done", label: "Done", hint: "" });
|
|
8087
8421
|
const choice = await p10.select({
|
|
@@ -8291,12 +8625,12 @@ function translateResponsesTools(tools) {
|
|
|
8291
8625
|
}
|
|
8292
8626
|
return Object.keys(out).length ? out : void 0;
|
|
8293
8627
|
}
|
|
8294
|
-
function translateResponsesRequest(body, npm) {
|
|
8628
|
+
function translateResponsesRequest(body, npm, metadata) {
|
|
8295
8629
|
const { system, messages } = translateResponsesInput(body.input, body.instructions, npm);
|
|
8296
8630
|
const effort = body.reasoning?.effort;
|
|
8297
8631
|
const providerOptions = deepMergeProviderOptions(
|
|
8298
8632
|
thinkingProviderOptions(npm),
|
|
8299
|
-
effortProviderOptions(npm, effort, body.model)
|
|
8633
|
+
effortProviderOptions(npm, effort, body.model, metadata)
|
|
8300
8634
|
);
|
|
8301
8635
|
return {
|
|
8302
8636
|
system,
|
|
@@ -8338,11 +8672,9 @@ async function writeResponsesStream(fullStream, modelId, write) {
|
|
|
8338
8672
|
let textItemId = null;
|
|
8339
8673
|
let textOutputIndex = 0;
|
|
8340
8674
|
let textFull = "";
|
|
8341
|
-
|
|
8342
|
-
|
|
8343
|
-
let
|
|
8344
|
-
let toolOutputIndex = 0;
|
|
8345
|
-
let toolArgsFull = "";
|
|
8675
|
+
const toolStates = [];
|
|
8676
|
+
const toolStatesById = /* @__PURE__ */ new Map();
|
|
8677
|
+
let currentToolState = null;
|
|
8346
8678
|
let reasoningItemId = null;
|
|
8347
8679
|
let reasoningText = "";
|
|
8348
8680
|
let reasoningOutputIndex = 0;
|
|
@@ -8367,6 +8699,51 @@ async function writeResponsesStream(fullStream, modelId, write) {
|
|
|
8367
8699
|
}
|
|
8368
8700
|
return textItemId;
|
|
8369
8701
|
};
|
|
8702
|
+
const rememberToolState = (state) => {
|
|
8703
|
+
toolStates.push(state);
|
|
8704
|
+
toolStatesById.set(state.itemId, state);
|
|
8705
|
+
toolStatesById.set(state.callId, state);
|
|
8706
|
+
currentToolState = state;
|
|
8707
|
+
return state;
|
|
8708
|
+
};
|
|
8709
|
+
const createToolState = (rawId, name, signature) => {
|
|
8710
|
+
const itemId = rawId ?? newItemId("fc");
|
|
8711
|
+
const state = rememberToolState({
|
|
8712
|
+
itemId,
|
|
8713
|
+
callId: encodeToolUseId(itemId, signature),
|
|
8714
|
+
name: name ?? "unknown",
|
|
8715
|
+
outputIndex: outputIndex++,
|
|
8716
|
+
args: ""
|
|
8717
|
+
});
|
|
8718
|
+
emit("response.output_item.added", {
|
|
8719
|
+
type: "response.output_item.added",
|
|
8720
|
+
output_index: state.outputIndex,
|
|
8721
|
+
item: {
|
|
8722
|
+
type: "function_call",
|
|
8723
|
+
id: state.itemId,
|
|
8724
|
+
call_id: state.callId,
|
|
8725
|
+
name: state.name,
|
|
8726
|
+
arguments: "",
|
|
8727
|
+
status: "in_progress"
|
|
8728
|
+
}
|
|
8729
|
+
});
|
|
8730
|
+
return state;
|
|
8731
|
+
};
|
|
8732
|
+
const findToolState = (part) => {
|
|
8733
|
+
const key = part.id ?? part.toolCallId;
|
|
8734
|
+
if (key) return toolStatesById.get(key) ?? currentToolState;
|
|
8735
|
+
return currentToolState;
|
|
8736
|
+
};
|
|
8737
|
+
const appendToolArgs = (state, delta) => {
|
|
8738
|
+
if (!delta) return;
|
|
8739
|
+
state.args += delta;
|
|
8740
|
+
emit("response.function_call_arguments.delta", {
|
|
8741
|
+
type: "response.function_call_arguments.delta",
|
|
8742
|
+
item_id: state.itemId,
|
|
8743
|
+
output_index: state.outputIndex,
|
|
8744
|
+
delta
|
|
8745
|
+
});
|
|
8746
|
+
};
|
|
8370
8747
|
for await (const part of fullStream) {
|
|
8371
8748
|
switch (part.type) {
|
|
8372
8749
|
case "reasoning-start":
|
|
@@ -8410,64 +8787,20 @@ async function writeResponsesStream(fullStream, modelId, write) {
|
|
|
8410
8787
|
break;
|
|
8411
8788
|
case "tool-input-start": {
|
|
8412
8789
|
const sig = grabRoundTripSignature(part);
|
|
8413
|
-
|
|
8414
|
-
toolCallId = encodeToolUseId(toolItemId, sig);
|
|
8415
|
-
toolName = part.toolName ?? "unknown";
|
|
8416
|
-
toolArgsFull = "";
|
|
8417
|
-
toolOutputIndex = outputIndex;
|
|
8418
|
-
outputIndex++;
|
|
8419
|
-
emit("response.output_item.added", {
|
|
8420
|
-
type: "response.output_item.added",
|
|
8421
|
-
output_index: toolOutputIndex,
|
|
8422
|
-
item: {
|
|
8423
|
-
type: "function_call",
|
|
8424
|
-
id: toolItemId,
|
|
8425
|
-
call_id: toolCallId,
|
|
8426
|
-
name: toolName,
|
|
8427
|
-
arguments: "",
|
|
8428
|
-
status: "in_progress"
|
|
8429
|
-
}
|
|
8430
|
-
});
|
|
8790
|
+
createToolState(part.id ?? part.toolCallId, part.toolName, sig);
|
|
8431
8791
|
break;
|
|
8432
8792
|
}
|
|
8433
|
-
case "tool-input-delta":
|
|
8434
|
-
|
|
8435
|
-
if (
|
|
8436
|
-
emit("response.function_call_arguments.delta", {
|
|
8437
|
-
type: "response.function_call_arguments.delta",
|
|
8438
|
-
item_id: toolItemId,
|
|
8439
|
-
output_index: toolOutputIndex,
|
|
8440
|
-
delta: part.delta ?? part.text ?? ""
|
|
8441
|
-
});
|
|
8442
|
-
}
|
|
8793
|
+
case "tool-input-delta": {
|
|
8794
|
+
const state = findToolState(part);
|
|
8795
|
+
if (state) appendToolArgs(state, part.delta ?? part.text ?? "");
|
|
8443
8796
|
break;
|
|
8797
|
+
}
|
|
8444
8798
|
case "tool-call": {
|
|
8445
|
-
|
|
8446
|
-
|
|
8447
|
-
|
|
8448
|
-
|
|
8449
|
-
|
|
8450
|
-
toolArgsFull = JSON.stringify(part.input ?? {});
|
|
8451
|
-
toolOutputIndex = outputIndex;
|
|
8452
|
-
outputIndex++;
|
|
8453
|
-
emit("response.output_item.added", {
|
|
8454
|
-
type: "response.output_item.added",
|
|
8455
|
-
output_index: toolOutputIndex,
|
|
8456
|
-
item: {
|
|
8457
|
-
type: "function_call",
|
|
8458
|
-
id: toolItemId,
|
|
8459
|
-
call_id: toolCallId,
|
|
8460
|
-
name: toolName,
|
|
8461
|
-
arguments: "",
|
|
8462
|
-
status: "in_progress"
|
|
8463
|
-
}
|
|
8464
|
-
});
|
|
8465
|
-
emit("response.function_call_arguments.delta", {
|
|
8466
|
-
type: "response.function_call_arguments.delta",
|
|
8467
|
-
item_id: toolItemId,
|
|
8468
|
-
output_index: toolOutputIndex,
|
|
8469
|
-
delta: toolArgsFull
|
|
8470
|
-
});
|
|
8799
|
+
const sig = grabRoundTripSignature(part);
|
|
8800
|
+
const key = part.toolCallId ?? part.id;
|
|
8801
|
+
const state = (key ? toolStatesById.get(key) : void 0) ?? createToolState(key, part.toolName, sig);
|
|
8802
|
+
if (!state.args) {
|
|
8803
|
+
appendToolArgs(state, JSON.stringify(part.input ?? {}));
|
|
8471
8804
|
}
|
|
8472
8805
|
break;
|
|
8473
8806
|
}
|
|
@@ -8530,24 +8863,24 @@ async function writeResponsesStream(fullStream, modelId, write) {
|
|
|
8530
8863
|
});
|
|
8531
8864
|
outputItems.unshift(reasoningItem);
|
|
8532
8865
|
}
|
|
8533
|
-
|
|
8866
|
+
for (const tool3 of toolStates) {
|
|
8534
8867
|
emit("response.function_call_arguments.done", {
|
|
8535
8868
|
type: "response.function_call_arguments.done",
|
|
8536
|
-
item_id:
|
|
8537
|
-
output_index:
|
|
8538
|
-
arguments:
|
|
8869
|
+
item_id: tool3.itemId,
|
|
8870
|
+
output_index: tool3.outputIndex,
|
|
8871
|
+
arguments: tool3.args
|
|
8539
8872
|
});
|
|
8540
8873
|
const fcItem = {
|
|
8541
8874
|
type: "function_call",
|
|
8542
|
-
id:
|
|
8543
|
-
call_id:
|
|
8544
|
-
name:
|
|
8545
|
-
arguments:
|
|
8875
|
+
id: tool3.itemId,
|
|
8876
|
+
call_id: tool3.callId,
|
|
8877
|
+
name: tool3.name,
|
|
8878
|
+
arguments: tool3.args,
|
|
8546
8879
|
status: "completed"
|
|
8547
8880
|
};
|
|
8548
8881
|
emit("response.output_item.done", {
|
|
8549
8882
|
type: "response.output_item.done",
|
|
8550
|
-
output_index:
|
|
8883
|
+
output_index: tool3.outputIndex,
|
|
8551
8884
|
item: fcItem
|
|
8552
8885
|
});
|
|
8553
8886
|
outputItems.push(fcItem);
|
|
@@ -8597,10 +8930,11 @@ async function generateResponsesResponse(model, params, modelId) {
|
|
|
8597
8930
|
});
|
|
8598
8931
|
}
|
|
8599
8932
|
for (const tc of r.toolCalls) {
|
|
8933
|
+
const encodedId = encodeToolUseId(tc.toolCallId, grabRoundTripSignature(tc));
|
|
8600
8934
|
output.push({
|
|
8601
8935
|
type: "function_call",
|
|
8602
8936
|
id: tc.toolCallId,
|
|
8603
|
-
call_id:
|
|
8937
|
+
call_id: encodedId,
|
|
8604
8938
|
name: tc.toolName,
|
|
8605
8939
|
arguments: JSON.stringify(tc.input ?? {}),
|
|
8606
8940
|
status: "completed"
|
|
@@ -8748,7 +9082,8 @@ async function startCodexProxy(routes, options = {}) {
|
|
|
8748
9082
|
modelId: route.upstreamModelId,
|
|
8749
9083
|
apiKey: route.apiKey,
|
|
8750
9084
|
baseURL: route.baseURL,
|
|
8751
|
-
providerId: route.modelId
|
|
9085
|
+
providerId: route.modelId,
|
|
9086
|
+
vertex: route.vertex
|
|
8752
9087
|
}));
|
|
8753
9088
|
}
|
|
8754
9089
|
return new Promise((resolve, reject2) => {
|
|
@@ -8831,7 +9166,14 @@ async function startCodexProxy(routes, options = {}) {
|
|
|
8831
9166
|
try {
|
|
8832
9167
|
const params = translateResponsesRequest(
|
|
8833
9168
|
body,
|
|
8834
|
-
route.npm
|
|
9169
|
+
route.npm,
|
|
9170
|
+
{
|
|
9171
|
+
providerId: route.providerId,
|
|
9172
|
+
apiBaseUrl: route.baseURL,
|
|
9173
|
+
supportedParameters: route.supportedParameters,
|
|
9174
|
+
reasoning: route.reasoning,
|
|
9175
|
+
interleavedReasoningField: route.interleavedReasoningField
|
|
9176
|
+
}
|
|
8835
9177
|
);
|
|
8836
9178
|
if (debug) {
|
|
8837
9179
|
const effort = body.reasoning?.effort;
|
|
@@ -8992,13 +9334,8 @@ function isProcessAlive(pid) {
|
|
|
8992
9334
|
return false;
|
|
8993
9335
|
}
|
|
8994
9336
|
}
|
|
8995
|
-
function sessionAgeMs(lock) {
|
|
8996
|
-
const started = Date.parse(lock.startedAt);
|
|
8997
|
-
if (Number.isNaN(started)) return Infinity;
|
|
8998
|
-
return Date.now() - started;
|
|
8999
|
-
}
|
|
9000
9337
|
function isConcurrentSession(lock) {
|
|
9001
|
-
return isProcessAlive(lock.pid)
|
|
9338
|
+
return isProcessAlive(lock.pid);
|
|
9002
9339
|
}
|
|
9003
9340
|
function restoreCodexOverlay(env = process.env) {
|
|
9004
9341
|
const removed = [];
|
|
@@ -9041,13 +9378,16 @@ function checkSessionLock(isTty, env = process.env) {
|
|
|
9041
9378
|
// src/codex/profile.ts
|
|
9042
9379
|
var CODEX_LAUNCH_SANDBOX = "danger-full-access";
|
|
9043
9380
|
function profileReasoningLine(effort) {
|
|
9044
|
-
return effort ? `model_reasoning_effort =
|
|
9381
|
+
return effort ? `model_reasoning_effort = ${tomlString(effort)}
|
|
9045
9382
|
` : "";
|
|
9046
9383
|
}
|
|
9047
9384
|
function profileSandboxLine() {
|
|
9048
|
-
return `sandbox =
|
|
9385
|
+
return `sandbox = ${tomlString(CODEX_LAUNCH_SANDBOX)}
|
|
9049
9386
|
`;
|
|
9050
9387
|
}
|
|
9388
|
+
function tomlString(value) {
|
|
9389
|
+
return JSON.stringify(value);
|
|
9390
|
+
}
|
|
9051
9391
|
function buildCodexProfileToml(spec) {
|
|
9052
9392
|
const { route, proxyPort, catalogPath, modelReasoningEffort } = spec;
|
|
9053
9393
|
const model = route.modelId;
|
|
@@ -9056,26 +9396,26 @@ function buildCodexProfileToml(spec) {
|
|
|
9056
9396
|
const envKey = codexProviderEnvKey(route.providerId);
|
|
9057
9397
|
const baseUrl = route.baseURL ?? "https://api.openai.com/v1";
|
|
9058
9398
|
return `# Generated by relay-ai \u2014 do not edit
|
|
9059
|
-
${profileSandboxLine()}model =
|
|
9060
|
-
model_provider =
|
|
9061
|
-
model_catalog_json =
|
|
9399
|
+
${profileSandboxLine()}model = ${tomlString(model)}
|
|
9400
|
+
model_provider = ${tomlString(route.providerId)}
|
|
9401
|
+
model_catalog_json = ${tomlString(catalogPath)}
|
|
9062
9402
|
${reasoning}
|
|
9063
9403
|
[model_providers.${route.providerId}]
|
|
9064
|
-
name =
|
|
9065
|
-
base_url =
|
|
9066
|
-
env_key =
|
|
9404
|
+
name = ${tomlString(route.providerId)}
|
|
9405
|
+
base_url = ${tomlString(baseUrl)}
|
|
9406
|
+
env_key = ${tomlString(envKey)}
|
|
9067
9407
|
wire_api = "responses"
|
|
9068
9408
|
`;
|
|
9069
9409
|
}
|
|
9070
9410
|
const proxyBase = `http://127.0.0.1:${proxyPort}/v1`;
|
|
9071
9411
|
return `# Generated by relay-ai \u2014 do not edit
|
|
9072
|
-
${profileSandboxLine()}model =
|
|
9412
|
+
${profileSandboxLine()}model = ${tomlString(model)}
|
|
9073
9413
|
model_provider = "relay-ai-proxy"
|
|
9074
|
-
model_catalog_json =
|
|
9414
|
+
model_catalog_json = ${tomlString(catalogPath)}
|
|
9075
9415
|
${reasoning}
|
|
9076
9416
|
[model_providers.relay-ai-proxy]
|
|
9077
9417
|
name = "relay-ai"
|
|
9078
|
-
base_url =
|
|
9418
|
+
base_url = ${tomlString(proxyBase)}
|
|
9079
9419
|
env_key = "RELAY_AI_CODEX_KEY"
|
|
9080
9420
|
wire_api = "responses"
|
|
9081
9421
|
`;
|
|
@@ -9340,7 +9680,13 @@ function buildEntry(r, priority) {
|
|
|
9340
9680
|
}
|
|
9341
9681
|
function defaultReasoningEffortForFavorite(r) {
|
|
9342
9682
|
const model = enrichFavoriteModel(r);
|
|
9343
|
-
const caps = getReasoningCapabilities(model.npm ?? "", model.upstreamModelId ?? model.id
|
|
9683
|
+
const caps = getReasoningCapabilities(model.npm ?? "", model.upstreamModelId ?? model.id, {
|
|
9684
|
+
providerId: r.providerId,
|
|
9685
|
+
apiBaseUrl: model.apiBaseUrl,
|
|
9686
|
+
supportedParameters: model.supportedParameters,
|
|
9687
|
+
reasoning: model.reasoning,
|
|
9688
|
+
interleavedReasoningField: model.interleavedReasoningField
|
|
9689
|
+
});
|
|
9344
9690
|
return caps.levels.length > 0 ? caps.defaultLevel : "none";
|
|
9345
9691
|
}
|
|
9346
9692
|
function buildFavoritesAppCatalog(resolved) {
|
|
@@ -9356,65 +9702,6 @@ function buildFavoritesAppCatalog(resolved) {
|
|
|
9356
9702
|
|
|
9357
9703
|
// src/codex/favorites-launch.ts
|
|
9358
9704
|
import * as p12 from "@clack/prompts";
|
|
9359
|
-
|
|
9360
|
-
// src/favorites-resolver.ts
|
|
9361
|
-
var ZEN_GO_PROVIDER_NAME = {
|
|
9362
|
-
zen: "OpenCode Zen",
|
|
9363
|
-
go: "OpenCode Go"
|
|
9364
|
-
};
|
|
9365
|
-
function resolveFavorite(fav, ctx) {
|
|
9366
|
-
if (fav.providerId === "zen" || fav.providerId === "go") {
|
|
9367
|
-
if (!ctx.zenGoApiKey) return void 0;
|
|
9368
|
-
const models = fav.providerId === "zen" ? ctx.zenModels : ctx.goModels;
|
|
9369
|
-
const model = models?.find((m) => m.id === fav.modelId);
|
|
9370
|
-
if (!model) return void 0;
|
|
9371
|
-
return {
|
|
9372
|
-
providerId: fav.providerId,
|
|
9373
|
-
providerName: ZEN_GO_PROVIDER_NAME[fav.providerId],
|
|
9374
|
-
model,
|
|
9375
|
-
apiKey: ctx.zenGoApiKey,
|
|
9376
|
-
sourceBackend: fav.providerId
|
|
9377
|
-
};
|
|
9378
|
-
}
|
|
9379
|
-
if (ctx.findLocalModel) {
|
|
9380
|
-
const found = ctx.findLocalModel(fav.providerId, fav.modelId);
|
|
9381
|
-
if (!found) return void 0;
|
|
9382
|
-
if (ctx.agent && shouldHideModel({ providerId: fav.providerId, modelId: fav.modelId, agent: ctx.agent })) {
|
|
9383
|
-
return void 0;
|
|
9384
|
-
}
|
|
9385
|
-
return {
|
|
9386
|
-
providerId: fav.providerId,
|
|
9387
|
-
providerName: found.provider.name,
|
|
9388
|
-
model: found.model,
|
|
9389
|
-
apiKey: found.provider.apiKey
|
|
9390
|
-
};
|
|
9391
|
-
}
|
|
9392
|
-
return void 0;
|
|
9393
|
-
}
|
|
9394
|
-
function buildFavoritesList(starting, favorites, ctx, max = 20) {
|
|
9395
|
-
const droppedFavorites = [];
|
|
9396
|
-
const seen = /* @__PURE__ */ new Set();
|
|
9397
|
-
const out = [];
|
|
9398
|
-
if (starting) {
|
|
9399
|
-
seen.add(`${starting.providerId}::${starting.model.id}`);
|
|
9400
|
-
out.push(starting);
|
|
9401
|
-
}
|
|
9402
|
-
for (const fav of favorites) {
|
|
9403
|
-
if (out.length >= max) break;
|
|
9404
|
-
const key = `${fav.providerId}::${fav.modelId}`;
|
|
9405
|
-
if (seen.has(key)) continue;
|
|
9406
|
-
const resolved = resolveFavorite(fav, ctx);
|
|
9407
|
-
if (!resolved) {
|
|
9408
|
-
droppedFavorites.push(fav);
|
|
9409
|
-
continue;
|
|
9410
|
-
}
|
|
9411
|
-
seen.add(key);
|
|
9412
|
-
out.push(resolved);
|
|
9413
|
-
}
|
|
9414
|
-
return { resolved: out, droppedFavorites };
|
|
9415
|
-
}
|
|
9416
|
-
|
|
9417
|
-
// src/codex/favorites-launch.ts
|
|
9418
9705
|
function buildCodexProxyRoutesFromResolved(resolved, providersById) {
|
|
9419
9706
|
return resolved.map((r) => {
|
|
9420
9707
|
const provider = providersById.get(r.providerId);
|
|
@@ -9650,15 +9937,26 @@ ${pc13.bold("Examples:")}
|
|
|
9650
9937
|
${pc13.bold("Favorites:")}
|
|
9651
9938
|
When you have saved favorites via ${pc13.cyan("relay-ai models")}, the Codex
|
|
9652
9939
|
picker will show your starting model + favorites for mid-session switching.
|
|
9653
|
-
Zen/Go favorites are
|
|
9654
|
-
${pc13.cyan("relay-ai server")} for those.`;
|
|
9940
|
+
Zen/Go favorites are included when an OpenCode API key is available.`;
|
|
9655
9941
|
}
|
|
9656
9942
|
async function writeLaunchArtifacts(route, selectedModel, providerName, proxyPort) {
|
|
9657
9943
|
const catalogPath = getCatalogOutputPath(route.providerId);
|
|
9658
9944
|
const catalog = buildCatalogFile([selectedModel], providerName);
|
|
9659
9945
|
writeOverlayFile(catalogPath, serializeCatalog(catalog));
|
|
9660
9946
|
const profilePath = getProfileOutputPath();
|
|
9661
|
-
|
|
9947
|
+
const caps = getReasoningCapabilities(route.npm, route.upstreamModelId, {
|
|
9948
|
+
providerId: route.providerId,
|
|
9949
|
+
apiBaseUrl: route.baseURL,
|
|
9950
|
+
supportedParameters: route.supportedParameters,
|
|
9951
|
+
reasoning: route.reasoning,
|
|
9952
|
+
interleavedReasoningField: route.interleavedReasoningField
|
|
9953
|
+
});
|
|
9954
|
+
writeOverlayFile(profilePath, buildCodexProfileToml({
|
|
9955
|
+
route,
|
|
9956
|
+
proxyPort,
|
|
9957
|
+
catalogPath,
|
|
9958
|
+
modelReasoningEffort: caps.defaultLevel || void 0
|
|
9959
|
+
}));
|
|
9662
9960
|
return { profilePath, catalogPath };
|
|
9663
9961
|
}
|
|
9664
9962
|
async function writeFavoritesLaunchArtifacts(resolved, starting, proxyPort) {
|
|
@@ -9696,6 +9994,103 @@ function printCodexCleanupReminder(hadProxy) {
|
|
|
9696
9994
|
parts.push("If a future session acts stuck: relay-ai codex --restore");
|
|
9697
9995
|
p13.log.info(parts.join(" "));
|
|
9698
9996
|
}
|
|
9997
|
+
function vertexEntryToLocalModel(entry) {
|
|
9998
|
+
return {
|
|
9999
|
+
id: entry.id,
|
|
10000
|
+
name: entry.display_name,
|
|
10001
|
+
family: "claude",
|
|
10002
|
+
brand: "Anthropic",
|
|
10003
|
+
modelFormat: "openai",
|
|
10004
|
+
upstreamModelId: entry.upstream_id ?? entry.id,
|
|
10005
|
+
baseUrl: "",
|
|
10006
|
+
npm: VERTEX_ANTHROPIC_NPM,
|
|
10007
|
+
contextWindow: resolveContextWindow(entry.id)
|
|
10008
|
+
};
|
|
10009
|
+
}
|
|
10010
|
+
async function runCodexVertexLaunch(passthroughArgs, trace) {
|
|
10011
|
+
if (!hasApplicationDefaultCredentials()) {
|
|
10012
|
+
p13.log.error("Google Application Default Credentials not found.");
|
|
10013
|
+
p13.log.info("Run: gcloud auth application-default login");
|
|
10014
|
+
return 1;
|
|
10015
|
+
}
|
|
10016
|
+
const config = buildVertexRuntimeConfig();
|
|
10017
|
+
if (!config) {
|
|
10018
|
+
p13.log.error("ANTHROPIC_VERTEX_PROJECT_ID (or GOOGLE_CLOUD_PROJECT) is not set.");
|
|
10019
|
+
p13.log.info("Set your project: export ANTHROPIC_VERTEX_PROJECT_ID=your-project-id");
|
|
10020
|
+
return 1;
|
|
10021
|
+
}
|
|
10022
|
+
let selectedEntry;
|
|
10023
|
+
if (config.models.length === 1) {
|
|
10024
|
+
selectedEntry = config.models[0];
|
|
10025
|
+
} else {
|
|
10026
|
+
const choice = await p13.select({
|
|
10027
|
+
message: "Select a Vertex AI model:",
|
|
10028
|
+
options: config.models.map((m) => ({ value: m, label: m.display_name, hint: m.id }))
|
|
10029
|
+
});
|
|
10030
|
+
if (p13.isCancel(choice)) {
|
|
10031
|
+
p13.cancel("Cancelled.");
|
|
10032
|
+
return 0;
|
|
10033
|
+
}
|
|
10034
|
+
selectedEntry = choice;
|
|
10035
|
+
}
|
|
10036
|
+
process.env["ANTHROPIC_VERTEX_PROJECT_ID"] = config.project;
|
|
10037
|
+
process.env["GOOGLE_CLOUD_LOCATION"] = config.location;
|
|
10038
|
+
const vertexConfig = { project: config.project, location: config.location };
|
|
10039
|
+
const allModels = config.models.map(vertexEntryToLocalModel);
|
|
10040
|
+
const allRoutes = allModels.map((m) => ({
|
|
10041
|
+
modelId: m.id,
|
|
10042
|
+
upstreamModelId: m.upstreamModelId,
|
|
10043
|
+
npm: VERTEX_ANTHROPIC_NPM,
|
|
10044
|
+
apiKey: "",
|
|
10045
|
+
providerId: "vertex",
|
|
10046
|
+
vertex: vertexConfig
|
|
10047
|
+
}));
|
|
10048
|
+
const startingRoute = {
|
|
10049
|
+
tier: "proxy",
|
|
10050
|
+
modelId: selectedEntry.id,
|
|
10051
|
+
upstreamModelId: selectedEntry.upstream_id ?? selectedEntry.id,
|
|
10052
|
+
npm: VERTEX_ANTHROPIC_NPM,
|
|
10053
|
+
apiKey: "",
|
|
10054
|
+
providerId: "vertex"
|
|
10055
|
+
};
|
|
10056
|
+
const debugLogPath = getCodexProxyDebugLogPath();
|
|
10057
|
+
let proxyHandle = null;
|
|
10058
|
+
try {
|
|
10059
|
+
p13.log.info(`Vertex AI \xB7 ${selectedEntry.display_name} \u2014 project: ${config.project} / location: ${config.location}`);
|
|
10060
|
+
proxyHandle = await startCodexProxy(allRoutes, { debug: trace });
|
|
10061
|
+
const proxyPort = proxyHandle.port;
|
|
10062
|
+
const catalogPath = getCatalogOutputPath("vertex");
|
|
10063
|
+
writeOverlayFile(catalogPath, serializeCatalog(buildCatalogFile(allModels, "Vertex AI")));
|
|
10064
|
+
const profilePath = getProfileOutputPath();
|
|
10065
|
+
const caps = getReasoningCapabilities(VERTEX_ANTHROPIC_NPM, selectedEntry.id);
|
|
10066
|
+
writeOverlayFile(profilePath, buildCodexProfileToml({
|
|
10067
|
+
route: startingRoute,
|
|
10068
|
+
proxyPort,
|
|
10069
|
+
catalogPath,
|
|
10070
|
+
modelReasoningEffort: caps.defaultLevel || void 0
|
|
10071
|
+
}));
|
|
10072
|
+
writeSessionLock({
|
|
10073
|
+
pid: process.pid,
|
|
10074
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10075
|
+
profilePath,
|
|
10076
|
+
catalogPaths: [catalogPath],
|
|
10077
|
+
proxyPort
|
|
10078
|
+
});
|
|
10079
|
+
if (!isAgentStdoutMode()) {
|
|
10080
|
+
logProxy(proxyPort);
|
|
10081
|
+
logActiveModel(selectedEntry.display_name, selectedEntry.id);
|
|
10082
|
+
printCodexCliCleanupPanel("relay-ai codex --restore");
|
|
10083
|
+
}
|
|
10084
|
+
const childEnv = buildCodexChildEnv(startingRoute, proxyPort);
|
|
10085
|
+
const exitCode = await launchCodex(selectedEntry.id, childEnv, passthroughArgs);
|
|
10086
|
+
if (trace) printTraceLog(debugLogPath);
|
|
10087
|
+
printCodexCleanupReminder(true);
|
|
10088
|
+
return exitCode;
|
|
10089
|
+
} finally {
|
|
10090
|
+
proxyHandle?.close();
|
|
10091
|
+
restoreCodexOverlay();
|
|
10092
|
+
}
|
|
10093
|
+
}
|
|
9699
10094
|
async function runCodexCommand(codexArgs, trace = false, launch = {}) {
|
|
9700
10095
|
if (codexArgs.includes("--help") || codexArgs.includes("-h")) {
|
|
9701
10096
|
console.log(codexHelpText());
|
|
@@ -9727,6 +10122,21 @@ async function runCodexCommand(codexArgs, trace = false, launch = {}) {
|
|
|
9727
10122
|
p13.log.info(`Debug log: ${debugLogPath}`);
|
|
9728
10123
|
}
|
|
9729
10124
|
const isTty = Boolean(process.stdin.isTTY);
|
|
10125
|
+
if (launch.vertex) {
|
|
10126
|
+
if (!configOnly) {
|
|
10127
|
+
const sessionCheck = checkSessionLock(isTty);
|
|
10128
|
+
if (!sessionCheck.ok) {
|
|
10129
|
+
if (sessionCheck.reason === "non_tty") {
|
|
10130
|
+
console.error(pc13.red("relay-ai codex --vertex requires an interactive terminal."));
|
|
10131
|
+
return 1;
|
|
10132
|
+
}
|
|
10133
|
+
console.error(pc13.yellow(`Another relay-ai codex session may be running (pid ${sessionCheck.lock.pid}).`));
|
|
10134
|
+
console.error("Run relay-ai codex --restore to clean up, or wait for it to finish.");
|
|
10135
|
+
return 1;
|
|
10136
|
+
}
|
|
10137
|
+
}
|
|
10138
|
+
return runCodexVertexLaunch(passthroughArgs, trace);
|
|
10139
|
+
}
|
|
9730
10140
|
const prefs = loadPreferences();
|
|
9731
10141
|
const launchPlan = planLaunchWizard({
|
|
9732
10142
|
explicit: { providerId: launch.launchProvider, modelId: launch.launchModel },
|
|
@@ -9821,9 +10231,17 @@ Error: ${launchPlan.error}
|
|
|
9821
10231
|
const pickedProvider = await pickCodexProvider(compatible, prefs, favoritesActive);
|
|
9822
10232
|
if (!pickedProvider) return 0;
|
|
9823
10233
|
if (pickedProvider === "__favorites__") {
|
|
9824
|
-
const
|
|
9825
|
-
|
|
9826
|
-
|
|
10234
|
+
const favoriteProviders = compatible.map((provider) => ({
|
|
10235
|
+
...provider,
|
|
10236
|
+
models: routableModelsForProvider(provider, "codex")
|
|
10237
|
+
}));
|
|
10238
|
+
const favoriteStart = resolveFirstAvailableFavorite(favorites, favoriteProviders);
|
|
10239
|
+
if (!favoriteStart) {
|
|
10240
|
+
p13.log.warn("No saved Codex favorites are currently available.");
|
|
10241
|
+
return 0;
|
|
10242
|
+
}
|
|
10243
|
+
activeProvider = favoriteStart.provider;
|
|
10244
|
+
selectedModel = favoriteStart.model;
|
|
9827
10245
|
p13.log.step(`Loaded Favorites Catalog. Starting model: ${selectedModel.name || selectedModel.id} (${activeProvider.name})`);
|
|
9828
10246
|
} else {
|
|
9829
10247
|
activeProvider = pickedProvider;
|
|
@@ -9895,7 +10313,11 @@ Error: ${launchPlan.error}
|
|
|
9895
10313
|
npm: route.npm,
|
|
9896
10314
|
apiKey: route.apiKey,
|
|
9897
10315
|
baseURL: route.baseURL,
|
|
9898
|
-
upstreamModelId: route.upstreamModelId
|
|
10316
|
+
upstreamModelId: route.upstreamModelId,
|
|
10317
|
+
providerId: route.providerId,
|
|
10318
|
+
supportedParameters: route.supportedParameters,
|
|
10319
|
+
reasoning: route.reasoning,
|
|
10320
|
+
interleavedReasoningField: route.interleavedReasoningField
|
|
9899
10321
|
}], { debug: trace });
|
|
9900
10322
|
proxyPort = proxyHandle.port;
|
|
9901
10323
|
}
|
|
@@ -10053,7 +10475,13 @@ function mergeAppConfig(existing, spec) {
|
|
|
10053
10475
|
};
|
|
10054
10476
|
const existingEffort = typeof out.model_reasoning_effort === "string" ? out.model_reasoning_effort : void 0;
|
|
10055
10477
|
if (existingEffort !== void 0) {
|
|
10056
|
-
const caps = getReasoningCapabilities(spec.route.npm, spec.route.modelId
|
|
10478
|
+
const caps = getReasoningCapabilities(spec.route.npm, spec.route.modelId, {
|
|
10479
|
+
providerId: spec.route.providerId,
|
|
10480
|
+
apiBaseUrl: spec.route.baseURL,
|
|
10481
|
+
supportedParameters: spec.route.supportedParameters,
|
|
10482
|
+
reasoning: spec.route.reasoning,
|
|
10483
|
+
interleavedReasoningField: spec.route.interleavedReasoningField
|
|
10484
|
+
});
|
|
10057
10485
|
if (caps.levels.length === 0 || !caps.levels.includes(existingEffort)) {
|
|
10058
10486
|
if (caps.levels.length > 0 && caps.defaultLevel) {
|
|
10059
10487
|
out.model_reasoning_effort = caps.defaultLevel;
|
|
@@ -10243,7 +10671,7 @@ function removeAppCatalogs(env = process.env) {
|
|
|
10243
10671
|
}
|
|
10244
10672
|
function restoreCodexAppOverlay(env = process.env) {
|
|
10245
10673
|
const lock = readAppSessionLock(env);
|
|
10246
|
-
if (lock && isConcurrentSession(lock)) {
|
|
10674
|
+
if (lock && lock.pid !== process.pid && isConcurrentSession(lock)) {
|
|
10247
10675
|
return {
|
|
10248
10676
|
restored: false,
|
|
10249
10677
|
liveSession: true,
|
|
@@ -10272,7 +10700,7 @@ function recoverInterruptedCodexAppSession(env = process.env) {
|
|
|
10272
10700
|
const lock = readAppSessionLock(env);
|
|
10273
10701
|
const managed = isAppManagedConfig(readCodexConfigText());
|
|
10274
10702
|
if (!lock && !managed) return { recovered: false };
|
|
10275
|
-
if (lock && isConcurrentSession(lock)) {
|
|
10703
|
+
if (lock && lock.pid !== process.pid && isConcurrentSession(lock)) {
|
|
10276
10704
|
return { recovered: false };
|
|
10277
10705
|
}
|
|
10278
10706
|
restoreCodexAppOverlay(env);
|
|
@@ -10281,7 +10709,7 @@ function recoverInterruptedCodexAppSession(env = process.env) {
|
|
|
10281
10709
|
function checkAppSessionLock(isTty, env = process.env) {
|
|
10282
10710
|
if (!isTty) return { ok: false, reason: "non_tty" };
|
|
10283
10711
|
const lock = readAppSessionLock(env);
|
|
10284
|
-
if (lock && isConcurrentSession(lock)) {
|
|
10712
|
+
if (lock && lock.pid !== process.pid && isConcurrentSession(lock)) {
|
|
10285
10713
|
return { ok: false, reason: "concurrent", lock };
|
|
10286
10714
|
}
|
|
10287
10715
|
return { ok: true };
|
|
@@ -10547,13 +10975,154 @@ ${pc14.bold("Examples:")}
|
|
|
10547
10975
|
${pc14.bold("Favorites:")}
|
|
10548
10976
|
When you have saved favorites via ${pc14.cyan("relay-ai models")}, the Codex App
|
|
10549
10977
|
picker will show your starting model + favorites for mid-session switching.
|
|
10550
|
-
Zen/Go favorites are
|
|
10551
|
-
${pc14.cyan("relay-ai server")} for those.`;
|
|
10978
|
+
Zen/Go favorites are included when an OpenCode API key is available.`;
|
|
10552
10979
|
}
|
|
10553
10980
|
function providerForCodexPicker(provider) {
|
|
10554
10981
|
return { ...provider, models: routableModelsForProvider(provider, "codex-app") };
|
|
10555
10982
|
}
|
|
10556
|
-
|
|
10983
|
+
function vertexEntryToLocalModel2(entry) {
|
|
10984
|
+
return {
|
|
10985
|
+
id: entry.id,
|
|
10986
|
+
name: entry.display_name,
|
|
10987
|
+
family: "claude",
|
|
10988
|
+
brand: "Anthropic",
|
|
10989
|
+
modelFormat: "openai",
|
|
10990
|
+
upstreamModelId: entry.upstream_id ?? entry.id,
|
|
10991
|
+
baseUrl: "",
|
|
10992
|
+
npm: VERTEX_ANTHROPIC_NPM,
|
|
10993
|
+
contextWindow: resolveContextWindow(entry.id)
|
|
10994
|
+
};
|
|
10995
|
+
}
|
|
10996
|
+
async function runCodexAppVertexLaunch(configOnly) {
|
|
10997
|
+
if (!hasApplicationDefaultCredentials()) {
|
|
10998
|
+
p15.log.error("Google Application Default Credentials not found.");
|
|
10999
|
+
p15.log.info("Run: gcloud auth application-default login");
|
|
11000
|
+
return 1;
|
|
11001
|
+
}
|
|
11002
|
+
const config = buildVertexRuntimeConfig();
|
|
11003
|
+
if (!config) {
|
|
11004
|
+
p15.log.error("ANTHROPIC_VERTEX_PROJECT_ID (or GOOGLE_CLOUD_PROJECT) is not set.");
|
|
11005
|
+
p15.log.info("Set your project: export ANTHROPIC_VERTEX_PROJECT_ID=your-project-id");
|
|
11006
|
+
return 1;
|
|
11007
|
+
}
|
|
11008
|
+
let selectedEntry;
|
|
11009
|
+
if (config.models.length === 1) {
|
|
11010
|
+
selectedEntry = config.models[0];
|
|
11011
|
+
} else {
|
|
11012
|
+
const choice = await p15.select({
|
|
11013
|
+
message: "Select a starting Vertex AI model:",
|
|
11014
|
+
options: config.models.map((m) => ({ value: m, label: m.display_name, hint: m.id }))
|
|
11015
|
+
});
|
|
11016
|
+
if (p15.isCancel(choice)) {
|
|
11017
|
+
p15.cancel("Cancelled.");
|
|
11018
|
+
return 0;
|
|
11019
|
+
}
|
|
11020
|
+
selectedEntry = choice;
|
|
11021
|
+
}
|
|
11022
|
+
process.env["ANTHROPIC_VERTEX_PROJECT_ID"] = config.project;
|
|
11023
|
+
process.env["GOOGLE_CLOUD_LOCATION"] = config.location;
|
|
11024
|
+
const vertexConfig = { project: config.project, location: config.location };
|
|
11025
|
+
const vertexModels = config.models.map(vertexEntryToLocalModel2);
|
|
11026
|
+
const catalogPath = getAppCatalogPath("vertex");
|
|
11027
|
+
const route = {
|
|
11028
|
+
tier: "proxy",
|
|
11029
|
+
modelId: selectedEntry.id,
|
|
11030
|
+
upstreamModelId: selectedEntry.upstream_id ?? selectedEntry.id,
|
|
11031
|
+
npm: VERTEX_ANTHROPIC_NPM,
|
|
11032
|
+
apiKey: "",
|
|
11033
|
+
providerId: "vertex"
|
|
11034
|
+
};
|
|
11035
|
+
if (configOnly) {
|
|
11036
|
+
const home = process.env["HOME"] ?? "";
|
|
11037
|
+
const shortenPath = (fp) => home ? fp.replace(home, "~") : fp;
|
|
11038
|
+
console.log("");
|
|
11039
|
+
console.log(pc14.bold(pc14.cyan(" CONFIG PREVIEW \u2014 relay-ai codex-app --vertex")));
|
|
11040
|
+
console.log("");
|
|
11041
|
+
console.log(` ${pc14.bold("Mode:")} Vertex AI`);
|
|
11042
|
+
console.log(` ${pc14.bold("Project:")} ${config.project}`);
|
|
11043
|
+
console.log(` ${pc14.bold("Location:")} ${config.location}`);
|
|
11044
|
+
console.log(` ${pc14.bold("Model:")} ${selectedEntry.display_name}`);
|
|
11045
|
+
console.log(` ${pc14.bold("Catalog:")} ${vertexModels.length} model${vertexModels.length !== 1 ? "s" : ""} available`);
|
|
11046
|
+
console.log("");
|
|
11047
|
+
console.log(` ${pc14.bold("Catalog file:")}`);
|
|
11048
|
+
console.log(` ${pc14.dim(shortenPath(catalogPath))}`);
|
|
11049
|
+
console.log("");
|
|
11050
|
+
console.log(pc14.dim(" No app was launched."));
|
|
11051
|
+
console.log(pc14.dim(" Run ") + pc14.cyan("relay-ai codex-app --vertex") + pc14.dim(" to launch."));
|
|
11052
|
+
console.log("");
|
|
11053
|
+
return 0;
|
|
11054
|
+
}
|
|
11055
|
+
let proxyHandle = null;
|
|
11056
|
+
let sessionActive = false;
|
|
11057
|
+
try {
|
|
11058
|
+
proxyHandle = await startCodexProxy(
|
|
11059
|
+
vertexModels.map((m) => ({
|
|
11060
|
+
modelId: m.id,
|
|
11061
|
+
upstreamModelId: m.upstreamModelId,
|
|
11062
|
+
npm: VERTEX_ANTHROPIC_NPM,
|
|
11063
|
+
apiKey: "",
|
|
11064
|
+
providerId: "vertex",
|
|
11065
|
+
vertex: vertexConfig
|
|
11066
|
+
})),
|
|
11067
|
+
{ requireAuth: false }
|
|
11068
|
+
);
|
|
11069
|
+
const proxyPort = proxyHandle.port;
|
|
11070
|
+
const catalogFile = buildAppCatalogFile(vertexModels, "Vertex AI", selectedEntry.id);
|
|
11071
|
+
writeOverlayFile(catalogPath, serializeCatalog(catalogFile));
|
|
11072
|
+
const spec = {
|
|
11073
|
+
route,
|
|
11074
|
+
proxyPort,
|
|
11075
|
+
catalogPath,
|
|
11076
|
+
providerDisplayName: `${selectedEntry.display_name} \xB7 Vertex AI`
|
|
11077
|
+
};
|
|
11078
|
+
saveAppRestoreStateBeforePatch();
|
|
11079
|
+
const backupPath = backupConfigToml();
|
|
11080
|
+
applyAppConfigPatch(spec);
|
|
11081
|
+
writeAppSessionLock({
|
|
11082
|
+
pid: process.pid,
|
|
11083
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11084
|
+
configPath: getCodexConfigPath(),
|
|
11085
|
+
catalogPaths: [catalogPath],
|
|
11086
|
+
restoreStatePath: getAppRestoreStatePath(),
|
|
11087
|
+
backupPath,
|
|
11088
|
+
proxyPort
|
|
11089
|
+
});
|
|
11090
|
+
sessionActive = true;
|
|
11091
|
+
p15.log.info(`Vertex AI \xB7 ${selectedEntry.display_name} \u2014 project: ${config.project} / location: ${config.location}`);
|
|
11092
|
+
logProxy(proxyPort);
|
|
11093
|
+
logActiveModel(selectedEntry.display_name, selectedEntry.id);
|
|
11094
|
+
try {
|
|
11095
|
+
await launchOrRestartCodexApp();
|
|
11096
|
+
} catch (err) {
|
|
11097
|
+
p15.log.warn(String(err instanceof Error ? err.message : err));
|
|
11098
|
+
p15.log.info(codexAppInstallHint());
|
|
11099
|
+
}
|
|
11100
|
+
printCodexAppSessionPanel({
|
|
11101
|
+
modelLabel: selectedEntry.display_name,
|
|
11102
|
+
modelId: selectedEntry.id,
|
|
11103
|
+
providerName: "Vertex AI",
|
|
11104
|
+
restoreCommand: "relay-ai codex-app --restore"
|
|
11105
|
+
});
|
|
11106
|
+
codexAppOutro(selectedEntry.display_name);
|
|
11107
|
+
await waitForShutdown2();
|
|
11108
|
+
console.log("");
|
|
11109
|
+
if (sessionActive) {
|
|
11110
|
+
restoreCodexAppOverlay();
|
|
11111
|
+
sessionActive = false;
|
|
11112
|
+
}
|
|
11113
|
+
if (isCodexAppRunning()) {
|
|
11114
|
+
const shouldClose = await p15.confirm({ message: "Codex Desktop is still running. Close it?" });
|
|
11115
|
+
if (shouldClose && !p15.isCancel(shouldClose)) {
|
|
11116
|
+
quitCodexAppGracefully();
|
|
11117
|
+
}
|
|
11118
|
+
}
|
|
11119
|
+
return 0;
|
|
11120
|
+
} finally {
|
|
11121
|
+
proxyHandle?.close();
|
|
11122
|
+
if (sessionActive) restoreCodexAppOverlay();
|
|
11123
|
+
}
|
|
11124
|
+
}
|
|
11125
|
+
async function runCodexAppCommand(args, opts = {}) {
|
|
10557
11126
|
if (args.includes("--help") || args.includes("-h")) {
|
|
10558
11127
|
console.log(codexAppHelpText());
|
|
10559
11128
|
return 0;
|
|
@@ -10590,6 +11159,9 @@ async function runCodexAppCommand(args) {
|
|
|
10590
11159
|
p15.log.warn("Recovered from an interrupted codex-app session (restored Codex config).");
|
|
10591
11160
|
}
|
|
10592
11161
|
}
|
|
11162
|
+
if (opts.vertex) {
|
|
11163
|
+
return runCodexAppVertexLaunch(configOnly);
|
|
11164
|
+
}
|
|
10593
11165
|
const catalogSpinner = p15.spinner();
|
|
10594
11166
|
catalogSpinner.start("Loading your providers...");
|
|
10595
11167
|
let catalog;
|
|
@@ -10626,9 +11198,14 @@ async function runCodexAppCommand(args) {
|
|
|
10626
11198
|
const pickedProvider = await pickCodexProvider(compatible, prefs, favoritesActive);
|
|
10627
11199
|
if (!pickedProvider) return 0;
|
|
10628
11200
|
if (pickedProvider === "__favorites__") {
|
|
10629
|
-
const
|
|
10630
|
-
|
|
10631
|
-
|
|
11201
|
+
const favoriteProviders = compatible.map(providerForCodexPicker);
|
|
11202
|
+
const favoriteStart = resolveFirstAvailableFavorite(favorites, favoriteProviders);
|
|
11203
|
+
if (!favoriteStart) {
|
|
11204
|
+
p15.log.warn("No saved Codex App favorites are currently available.");
|
|
11205
|
+
return 0;
|
|
11206
|
+
}
|
|
11207
|
+
activeProvider = favoriteStart.provider;
|
|
11208
|
+
selectedModel = favoriteStart.model;
|
|
10632
11209
|
p15.log.step(`Loaded Favorites Catalog. Starting model: ${selectedModel.name || selectedModel.id} (${activeProvider.name})`);
|
|
10633
11210
|
} else {
|
|
10634
11211
|
activeProvider = providerForCodexPicker(pickedProvider);
|
|
@@ -11203,6 +11780,8 @@ function claudeAppHelpText() {
|
|
|
11203
11780
|
|
|
11204
11781
|
${pc15.bold("Usage:")}
|
|
11205
11782
|
relay-ai claude-app
|
|
11783
|
+
relay-ai claude-app --trace
|
|
11784
|
+
relay-ai claude-app --restore
|
|
11206
11785
|
|
|
11207
11786
|
${pc15.bold("Description:")}
|
|
11208
11787
|
Picks a provider and model from ~/.relay-ai/providers.json, patches Claude Desktop config
|
|
@@ -11214,6 +11793,7 @@ ${pc15.bold("Platforms:")}
|
|
|
11214
11793
|
|
|
11215
11794
|
${pc15.bold("Cleanup:")}
|
|
11216
11795
|
Ctrl+C stops the proxy and restores your previous Claude config.
|
|
11796
|
+
After a crash: relay-ai claude-app --restore
|
|
11217
11797
|
`;
|
|
11218
11798
|
}
|
|
11219
11799
|
function providerForClaudePicker(provider) {
|
|
@@ -11224,6 +11804,14 @@ async function runClaudeAppCommand(args) {
|
|
|
11224
11804
|
console.log(claudeAppHelpText());
|
|
11225
11805
|
return 0;
|
|
11226
11806
|
}
|
|
11807
|
+
if (args.includes("--restore")) {
|
|
11808
|
+
recoverSession();
|
|
11809
|
+
console.log("Restored Claude Desktop relay-ai config.");
|
|
11810
|
+
return 0;
|
|
11811
|
+
}
|
|
11812
|
+
const trace = args.includes("--trace");
|
|
11813
|
+
const debugLogPath = trace ? getProxyDebugLogPath() : void 0;
|
|
11814
|
+
if (trace) console.log(`Debug log: ${debugLogPath}`);
|
|
11227
11815
|
try {
|
|
11228
11816
|
claudeAppSupported();
|
|
11229
11817
|
} catch (err) {
|
|
@@ -11321,7 +11909,8 @@ async function runClaudeAppCommand(args) {
|
|
|
11321
11909
|
serverPassword: null,
|
|
11322
11910
|
catalog: createGatewayModelCatalog(serverModels, { maskGatewayIds: true }),
|
|
11323
11911
|
backends: BACKENDS,
|
|
11324
|
-
gateway: { maskGatewayIds: true }
|
|
11912
|
+
gateway: { maskGatewayIds: true },
|
|
11913
|
+
debugLogPath
|
|
11325
11914
|
});
|
|
11326
11915
|
const uuid = writeRelayAiConfig(proxyHandle.port);
|
|
11327
11916
|
writeSessionLock2({
|
|
@@ -11792,7 +12381,7 @@ PROVIDER / MODEL DISCOVERY FOR ALEF CONFIG
|
|
|
11792
12381
|
5. relay-ai --ai (includes live state section at bottom of output)
|
|
11793
12382
|
|
|
11794
12383
|
ALEF CHECKLIST
|
|
11795
|
-
\u25A1 relay-ai on PATH (npm install -g relay-ai; dev: npm link after builds)
|
|
12384
|
+
\u25A1 relay-ai on PATH (npm install -g @jacobbd/relay-ai; dev: npm link after builds)
|
|
11796
12385
|
\u25A1 Always pass --provider + --model (or provider__model slug) \u2014 never rely on wizard
|
|
11797
12386
|
\u25A1 Claude: --output-format stream-json (or json) with -p
|
|
11798
12387
|
\u25A1 Codex: exec --json (not bare codex exec without --json if parsing stdout)
|
|
@@ -11998,6 +12587,7 @@ function parseArgs(args) {
|
|
|
11998
12587
|
for (const arg of rest) {
|
|
11999
12588
|
if (arg === "--help" || arg === "-h") parsed2.showHelp = true;
|
|
12000
12589
|
else if (arg === "--version" || arg === "-v") parsed2.showVersion = true;
|
|
12590
|
+
else if (arg === "--vertex") parsed2.vertex = true;
|
|
12001
12591
|
}
|
|
12002
12592
|
return parsed2;
|
|
12003
12593
|
}
|
|
@@ -12018,6 +12608,10 @@ function parseArgs(args) {
|
|
|
12018
12608
|
parsed2.trace = true;
|
|
12019
12609
|
continue;
|
|
12020
12610
|
}
|
|
12611
|
+
if (arg === "--vertex") {
|
|
12612
|
+
parsed2.vertex = true;
|
|
12613
|
+
continue;
|
|
12614
|
+
}
|
|
12021
12615
|
if (arg === "--help" || arg === "-h") {
|
|
12022
12616
|
parsed2.showHelp = true;
|
|
12023
12617
|
continue;
|
|
@@ -12175,7 +12769,7 @@ ${pc16.bold("Behavior:")}
|
|
|
12175
12769
|
${pc16.bold("Vertex env:")}
|
|
12176
12770
|
ANTHROPIC_VERTEX_PROJECT_ID or GOOGLE_CLOUD_PROJECT \u2014 your GCP project
|
|
12177
12771
|
GOOGLE_CLOUD_LOCATION or CLOUD_ML_REGION \u2014 region (default: global)
|
|
12178
|
-
Optional catalog: ~/.relay-ai/vertex-models.json (see vertex-models.example.json)
|
|
12772
|
+
Optional catalog: ~/.relay-ai/vertex-models.json (see assets/vertex-models.example.json)
|
|
12179
12773
|
|
|
12180
12774
|
${pc16.bold("Endpoints:")}
|
|
12181
12775
|
Anthropic-compatible: ANTHROPIC_BASE_URL=http://127.0.0.1:17645/anthropic
|
|
@@ -12461,9 +13055,13 @@ Error: ${launchPlan.error}
|
|
|
12461
13055
|
}
|
|
12462
13056
|
const providerChoice = chosen;
|
|
12463
13057
|
if (providerChoice === "__favorites__") {
|
|
12464
|
-
const
|
|
12465
|
-
|
|
12466
|
-
|
|
13058
|
+
const favoriteStart = resolveFirstAvailableFavorite(favorites, allProviders);
|
|
13059
|
+
if (!favoriteStart) {
|
|
13060
|
+
p18.log.warn("No saved favorites are currently available.");
|
|
13061
|
+
return 0;
|
|
13062
|
+
}
|
|
13063
|
+
activeProvider = favoriteStart.provider;
|
|
13064
|
+
selectedModel = favoriteStart.model;
|
|
12467
13065
|
p18.log.step(`Loaded Favorites Catalog. Starting model: ${selectedModel.name || selectedModel.id} (${activeProvider.name})`);
|
|
12468
13066
|
} else {
|
|
12469
13067
|
activeProvider = allProviders.find((lp) => lp.id === providerChoice);
|
|
@@ -12559,7 +13157,11 @@ Error: ${launchPlan.error}
|
|
|
12559
13157
|
{
|
|
12560
13158
|
npm: selectedModel.npm,
|
|
12561
13159
|
baseURL: selectedModel.apiBaseUrl,
|
|
12562
|
-
upstreamModelId: selectedModel.upstreamModelId
|
|
13160
|
+
upstreamModelId: selectedModel.upstreamModelId,
|
|
13161
|
+
providerId: activeProvider.id,
|
|
13162
|
+
supportedParameters: selectedModel.supportedParameters,
|
|
13163
|
+
reasoning: selectedModel.reasoning,
|
|
13164
|
+
interleavedReasoningField: selectedModel.interleavedReasoningField
|
|
12563
13165
|
}
|
|
12564
13166
|
);
|
|
12565
13167
|
if (!isAgentStdoutMode()) {
|
|
@@ -12659,7 +13261,7 @@ Error: ${parsed.error}
|
|
|
12659
13261
|
console.log(VERSION);
|
|
12660
13262
|
return 0;
|
|
12661
13263
|
}
|
|
12662
|
-
return runCodexAppCommand(parsed.claudeArgs);
|
|
13264
|
+
return runCodexAppCommand(parsed.claudeArgs, { vertex: parsed.vertex });
|
|
12663
13265
|
}
|
|
12664
13266
|
if (parsed.command === "claude-app") {
|
|
12665
13267
|
if (parsed.showVersion) {
|
|
@@ -12679,7 +13281,8 @@ Error: ${parsed.error}
|
|
|
12679
13281
|
}
|
|
12680
13282
|
return runCodexCommand(parsed.claudeArgs, parsed.trace, {
|
|
12681
13283
|
launchProvider: parsed.launchProvider,
|
|
12682
|
-
launchModel: parsed.launchModel
|
|
13284
|
+
launchModel: parsed.launchModel,
|
|
13285
|
+
vertex: parsed.vertex
|
|
12683
13286
|
});
|
|
12684
13287
|
}
|
|
12685
13288
|
if (parsed.showVersion) {
|