@jacobbd/relay-ai 0.9.2 → 0.9.4
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/dist/{chunk-GQCFLSEM.js → chunk-KDIY732Q.js} +510 -186
- package/dist/chunk-KDIY732Q.js.map +1 -0
- package/dist/{chunk-Q2FTCICO.js → chunk-NYKVDBQC.js} +2 -2
- package/dist/{chunk-Q2FTCICO.js.map → chunk-NYKVDBQC.js.map} +1 -1
- package/dist/cli.js +148 -9
- package/dist/cli.js.map +1 -1
- package/dist/core/index.d.ts +32 -4
- package/dist/core/index.js +617 -109
- package/dist/core/index.js.map +1 -1
- package/dist/{provider-templates-XKNRKAQU.js → provider-templates-CGWE66TD.js} +2 -2
- package/dist/ui/public/app.js +190 -1
- package/dist/{ui-command-SDMYDYT6.js → ui-command-OIY4243G.js} +59 -7
- package/dist/ui-command-OIY4243G.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-GQCFLSEM.js.map +0 -1
- package/dist/ui-command-SDMYDYT6.js.map +0 -1
- /package/dist/{provider-templates-XKNRKAQU.js.map → provider-templates-CGWE66TD.js.map} +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
getTemplateById,
|
|
4
4
|
init_provider_templates
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-NYKVDBQC.js";
|
|
6
6
|
|
|
7
7
|
// src/constants.ts
|
|
8
8
|
import { homedir } from "os";
|
|
@@ -11,7 +11,7 @@ import { join } from "path";
|
|
|
11
11
|
// package.json
|
|
12
12
|
var package_default = {
|
|
13
13
|
name: "@jacobbd/relay-ai",
|
|
14
|
-
version: "0.9.
|
|
14
|
+
version: "0.9.4",
|
|
15
15
|
publishConfig: {
|
|
16
16
|
access: "public"
|
|
17
17
|
},
|
|
@@ -385,9 +385,7 @@ function createResponsesLiteNormalizeState() {
|
|
|
385
385
|
textDeltaForwarded: false,
|
|
386
386
|
messageAddedIds: /* @__PURE__ */ new Set(),
|
|
387
387
|
messageDoneIds: /* @__PURE__ */ new Set(),
|
|
388
|
-
|
|
389
|
-
functionDeltaIndexes: /* @__PURE__ */ new Set(),
|
|
390
|
-
functionDoneCallIds: /* @__PURE__ */ new Set()
|
|
388
|
+
functionCalls: []
|
|
391
389
|
};
|
|
392
390
|
}
|
|
393
391
|
function nextId(state, prefix) {
|
|
@@ -411,20 +409,92 @@ function normalizeErrorEvent(event) {
|
|
|
411
409
|
}
|
|
412
410
|
};
|
|
413
411
|
}
|
|
414
|
-
function
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
412
|
+
function resolveFunctionCall(state, hint) {
|
|
413
|
+
if (hint.callId) {
|
|
414
|
+
const byCallId = state.functionCalls.find((entry2) => entry2.callId === hint.callId);
|
|
415
|
+
if (byCallId) return byCallId;
|
|
416
|
+
}
|
|
417
|
+
if (hint.itemId) {
|
|
418
|
+
const byItemId = state.functionCalls.find((entry2) => entry2.itemId === hint.itemId);
|
|
419
|
+
if (byItemId) return byItemId;
|
|
420
|
+
}
|
|
421
|
+
if (hint.outputIndex !== void 0) {
|
|
422
|
+
const open4 = [...state.functionCalls].reverse().find((entry2) => entry2.outputIndex === hint.outputIndex && !entry2.done && !(hint.callId && entry2.callId !== hint.callId));
|
|
423
|
+
if (open4) return open4;
|
|
424
|
+
}
|
|
425
|
+
if (hint.callId === void 0 && hint.itemId === void 0 && hint.outputIndex === void 0 && state.lastFunctionCall) {
|
|
426
|
+
return state.lastFunctionCall;
|
|
427
|
+
}
|
|
428
|
+
const entry = {
|
|
429
|
+
itemId: hint.itemId ?? nextId(state, "fc"),
|
|
430
|
+
callId: hint.callId ?? hint.itemId ?? nextId(state, "call"),
|
|
431
|
+
name: "",
|
|
432
|
+
args: "",
|
|
433
|
+
upstream: {},
|
|
434
|
+
outputIndex: hint.outputIndex ?? state.lastOutputIndex,
|
|
435
|
+
added: false,
|
|
436
|
+
deltaForwarded: false,
|
|
437
|
+
doneSeen: false,
|
|
438
|
+
done: false
|
|
439
|
+
};
|
|
440
|
+
state.functionCalls.push(entry);
|
|
441
|
+
return entry;
|
|
442
|
+
}
|
|
443
|
+
function absorbFunctionItem(entry, item, authoritative) {
|
|
444
|
+
entry.upstream = { ...entry.upstream, ...item };
|
|
445
|
+
const name = asString(item.name);
|
|
446
|
+
if (name) entry.name = name;
|
|
447
|
+
const callId = asString(item.call_id);
|
|
448
|
+
if (callId) entry.callId = callId;
|
|
449
|
+
if (authoritative && typeof item.arguments === "string") entry.upstreamArgs = item.arguments;
|
|
450
|
+
}
|
|
451
|
+
function resolveFunctionArgs(entry) {
|
|
452
|
+
if (entry.upstreamArgs) return entry.upstreamArgs;
|
|
453
|
+
if (entry.args) return entry.args;
|
|
454
|
+
return entry.upstreamArgs;
|
|
455
|
+
}
|
|
456
|
+
function functionItemPayload(entry, extra) {
|
|
418
457
|
return {
|
|
419
|
-
...
|
|
458
|
+
...entry.upstream,
|
|
420
459
|
type: "function_call",
|
|
421
|
-
id,
|
|
422
|
-
call_id: callId,
|
|
423
|
-
name:
|
|
424
|
-
|
|
425
|
-
|
|
460
|
+
id: entry.itemId,
|
|
461
|
+
call_id: entry.callId,
|
|
462
|
+
name: entry.name,
|
|
463
|
+
...extra
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
function functionAddedEvent(entry) {
|
|
467
|
+
entry.added = true;
|
|
468
|
+
return {
|
|
469
|
+
type: "response.output_item.added",
|
|
470
|
+
output_index: entry.outputIndex,
|
|
471
|
+
item: functionItemPayload(entry, { arguments: "" })
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
function functionDoneEvent(entry, args) {
|
|
475
|
+
entry.done = true;
|
|
476
|
+
return {
|
|
477
|
+
type: "response.output_item.done",
|
|
478
|
+
output_index: entry.outputIndex,
|
|
479
|
+
item: functionItemPayload(entry, { arguments: args, status: "completed" })
|
|
426
480
|
};
|
|
427
481
|
}
|
|
482
|
+
function completeFunctionCall(entry, args) {
|
|
483
|
+
if (entry.done) return [];
|
|
484
|
+
const events = [];
|
|
485
|
+
if (!entry.added) events.push(functionAddedEvent(entry));
|
|
486
|
+
if (!entry.deltaForwarded && args.length > 0) {
|
|
487
|
+
entry.deltaForwarded = true;
|
|
488
|
+
events.push({
|
|
489
|
+
type: "response.function_call_arguments.delta",
|
|
490
|
+
item_id: entry.itemId,
|
|
491
|
+
output_index: entry.outputIndex,
|
|
492
|
+
delta: args
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
events.push(functionDoneEvent(entry, args));
|
|
496
|
+
return events;
|
|
497
|
+
}
|
|
428
498
|
function messageText(item) {
|
|
429
499
|
if (typeof item.text === "string") return item.text;
|
|
430
500
|
if (!Array.isArray(item.content)) return "";
|
|
@@ -451,47 +521,43 @@ function synthesizeMessage(item, outputIndex, state) {
|
|
|
451
521
|
];
|
|
452
522
|
}
|
|
453
523
|
function synthesizeFunctionCall(item, outputIndex, state) {
|
|
454
|
-
const
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
if (!state.functionAddedIndexes.has(outputIndex)) {
|
|
459
|
-
events.push({
|
|
460
|
-
type: "response.output_item.added",
|
|
461
|
-
output_index: outputIndex,
|
|
462
|
-
item: { ...normalized, arguments: "" }
|
|
463
|
-
});
|
|
464
|
-
state.functionAddedIndexes.add(outputIndex);
|
|
465
|
-
}
|
|
466
|
-
if (!state.functionDeltaIndexes.has(outputIndex) && typeof normalized.arguments === "string" && normalized.arguments.length > 0) {
|
|
467
|
-
events.push({
|
|
468
|
-
type: "response.function_call_arguments.delta",
|
|
469
|
-
item_id: normalized.id,
|
|
470
|
-
output_index: outputIndex,
|
|
471
|
-
delta: normalized.arguments
|
|
472
|
-
});
|
|
473
|
-
state.functionDeltaIndexes.add(outputIndex);
|
|
474
|
-
}
|
|
475
|
-
events.push({
|
|
476
|
-
type: "response.output_item.done",
|
|
477
|
-
output_index: outputIndex,
|
|
478
|
-
item: { ...normalized, status: "completed" }
|
|
524
|
+
const entry = resolveFunctionCall(state, {
|
|
525
|
+
itemId: asString(item.id),
|
|
526
|
+
callId: asString(item.call_id),
|
|
527
|
+
outputIndex
|
|
479
528
|
});
|
|
480
|
-
|
|
481
|
-
state.
|
|
482
|
-
|
|
529
|
+
absorbFunctionItem(entry, item, true);
|
|
530
|
+
state.lastFunctionCall = entry;
|
|
531
|
+
state.lastOutputIndex = entry.outputIndex;
|
|
532
|
+
const args = resolveFunctionArgs(entry);
|
|
533
|
+
if (args === void 0) {
|
|
534
|
+
entry.doneSeen = true;
|
|
535
|
+
return [];
|
|
536
|
+
}
|
|
537
|
+
return completeFunctionCall(entry, args);
|
|
483
538
|
}
|
|
484
539
|
function recoverFromCompletedOutput(response, state) {
|
|
485
|
-
if (!Array.isArray(response.output)) return [];
|
|
486
540
|
const recovered = [];
|
|
487
|
-
response.output
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
541
|
+
if (Array.isArray(response.output)) {
|
|
542
|
+
response.output.forEach((item, index) => {
|
|
543
|
+
if (!isRecord(item) || typeof item.type !== "string") return;
|
|
544
|
+
if (item.type === "message" && !state.textDeltaForwarded) {
|
|
545
|
+
recovered.push(...synthesizeMessage(item, index, state));
|
|
546
|
+
} else if (item.type === "function_call") {
|
|
547
|
+
recovered.push(...synthesizeFunctionCall(item, index, state));
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
for (const entry of state.functionCalls) {
|
|
552
|
+
if (entry.done || !entry.doneSeen) continue;
|
|
553
|
+
recovered.push(normalizeErrorEvent({
|
|
554
|
+
error: {
|
|
555
|
+
type: "invalid_response",
|
|
556
|
+
code: "incomplete_function_call",
|
|
557
|
+
message: `Provider ended the response without arguments for function call "${entry.callId}"${entry.name ? ` (${entry.name})` : ""}.`
|
|
558
|
+
}
|
|
559
|
+
}));
|
|
560
|
+
}
|
|
495
561
|
return recovered;
|
|
496
562
|
}
|
|
497
563
|
function normalizeResponsesLiteEvent(event, state) {
|
|
@@ -507,19 +573,40 @@ function normalizeResponsesLiteEvent(event, state) {
|
|
|
507
573
|
return [{ ...event, output_index: outputIndex, item: { ...event.item, id } }];
|
|
508
574
|
}
|
|
509
575
|
if (event.item.type === "function_call") {
|
|
510
|
-
const
|
|
511
|
-
|
|
512
|
-
|
|
576
|
+
const entry = resolveFunctionCall(state, {
|
|
577
|
+
itemId: asString(event.item.id),
|
|
578
|
+
callId: asString(event.item.call_id),
|
|
579
|
+
outputIndex
|
|
580
|
+
});
|
|
581
|
+
absorbFunctionItem(entry, event.item, false);
|
|
582
|
+
entry.outputIndex = outputIndex;
|
|
583
|
+
entry.added = true;
|
|
584
|
+
state.lastFunctionCall = entry;
|
|
585
|
+
return [{ ...event, output_index: outputIndex, item: functionItemPayload(entry, { arguments: "" }) }];
|
|
513
586
|
}
|
|
514
587
|
return [{ ...event, output_index: outputIndex }];
|
|
515
588
|
}
|
|
516
589
|
if (event.type === "response.output_item.done" && isRecord(event.item)) {
|
|
517
590
|
const outputIndex = typeof event.output_index === "number" ? event.output_index : state.lastOutputIndex;
|
|
518
591
|
if (event.item.type === "function_call") {
|
|
519
|
-
const
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
592
|
+
const entry = resolveFunctionCall(state, {
|
|
593
|
+
itemId: asString(event.item.id),
|
|
594
|
+
callId: asString(event.item.call_id),
|
|
595
|
+
outputIndex
|
|
596
|
+
});
|
|
597
|
+
absorbFunctionItem(entry, event.item, true);
|
|
598
|
+
entry.outputIndex = outputIndex;
|
|
599
|
+
entry.doneSeen = true;
|
|
600
|
+
state.lastFunctionCall = entry;
|
|
601
|
+
state.lastOutputIndex = outputIndex;
|
|
602
|
+
const args = resolveFunctionArgs(entry);
|
|
603
|
+
if (args === void 0 || !entry.name) return [];
|
|
604
|
+
if (entry.done) return [];
|
|
605
|
+
const events = [];
|
|
606
|
+
if (!entry.added) events.push(functionAddedEvent(entry));
|
|
607
|
+
events.push({ ...event, output_index: outputIndex, item: functionItemPayload(entry, { arguments: args, status: "completed" }) });
|
|
608
|
+
entry.done = true;
|
|
609
|
+
return events;
|
|
523
610
|
}
|
|
524
611
|
if (event.item.type === "message") {
|
|
525
612
|
const id = asString(event.item.id) ?? state.lastMessageItemId ?? nextId(state, "msg");
|
|
@@ -546,12 +633,16 @@ function normalizeResponsesLiteEvent(event, state) {
|
|
|
546
633
|
return events;
|
|
547
634
|
}
|
|
548
635
|
if (event.type === "response.function_call_arguments.delta") {
|
|
549
|
-
const
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
636
|
+
const entry = resolveFunctionCall(state, {
|
|
637
|
+
itemId: asString(event.item_id),
|
|
638
|
+
outputIndex: typeof event.output_index === "number" ? event.output_index : void 0
|
|
639
|
+
});
|
|
640
|
+
const delta = typeof event.delta === "string" ? event.delta : "";
|
|
641
|
+
entry.args += delta;
|
|
642
|
+
entry.deltaForwarded = true;
|
|
643
|
+
state.lastFunctionCall = entry;
|
|
644
|
+
state.lastOutputIndex = entry.outputIndex;
|
|
645
|
+
return [{ ...event, item_id: entry.itemId, output_index: entry.outputIndex, delta }];
|
|
555
646
|
}
|
|
556
647
|
if (event.type === "response.completed" || event.type === "response.incomplete") {
|
|
557
648
|
const response = isRecord(event.response) ? event.response : {};
|
|
@@ -1051,10 +1142,11 @@ async function createLanguageModel(spec) {
|
|
|
1051
1142
|
return model;
|
|
1052
1143
|
}
|
|
1053
1144
|
var ANTHROPIC_EFFORT_LEVELS = ["low", "medium", "high"];
|
|
1054
|
-
var OPENAI_EFFORT_LEVELS = ["low", "medium", "high"
|
|
1145
|
+
var OPENAI_EFFORT_LEVELS = ["low", "medium", "high"];
|
|
1055
1146
|
var GEMINI_EFFORT_LEVELS = ["low", "medium", "high"];
|
|
1056
1147
|
var MISTRAL_EFFORT_LEVELS = ["high", "off"];
|
|
1057
|
-
var
|
|
1148
|
+
var XAI_CHAT_EFFORT_LEVELS = ["low", "high"];
|
|
1149
|
+
var XAI_RESPONSES_EFFORT_LEVELS = ["low", "medium", "high"];
|
|
1058
1150
|
var OPENROUTER_EFFORT_LEVELS = ["none", "minimal", "low", "medium", "high", "xhigh"];
|
|
1059
1151
|
var DEEPSEEK_EFFORT_LEVELS = ["high", "max", "off"];
|
|
1060
1152
|
var GLM_52_EFFORT_LEVELS = ["high", "xhigh"];
|
|
@@ -1225,7 +1317,50 @@ function mapCodexEffortToAnthropic(effort) {
|
|
|
1225
1317
|
return void 0;
|
|
1226
1318
|
}
|
|
1227
1319
|
}
|
|
1228
|
-
|
|
1320
|
+
var OPENAI_MODEL_REASONING = {
|
|
1321
|
+
"gpt-5-pro": { levels: ["high"], defaultLevel: "high" },
|
|
1322
|
+
"gpt-5.1": { levels: ["none", "low", "medium", "high"], defaultLevel: "none" },
|
|
1323
|
+
"gpt-5.1-codex-max": { levels: ["low", "medium", "high", "xhigh"], defaultLevel: "medium" },
|
|
1324
|
+
"gpt-5.2": { levels: ["none", "low", "medium", "high", "xhigh"], defaultLevel: "none" },
|
|
1325
|
+
"gpt-5.2-codex": { levels: ["low", "medium", "high", "xhigh"], defaultLevel: "medium" },
|
|
1326
|
+
"gpt-5.2-pro": { levels: ["medium", "high", "xhigh"], defaultLevel: "medium" },
|
|
1327
|
+
"gpt-5.3-codex": { levels: ["low", "medium", "high", "xhigh"], defaultLevel: "medium" },
|
|
1328
|
+
"gpt-5.4": { levels: ["none", "low", "medium", "high", "xhigh"], defaultLevel: "none" },
|
|
1329
|
+
"gpt-5.4-mini": { levels: ["none", "low", "medium", "high", "xhigh"], defaultLevel: "none" },
|
|
1330
|
+
"gpt-5.4-nano": { levels: ["none", "low", "medium", "high", "xhigh"], defaultLevel: "none" },
|
|
1331
|
+
"gpt-5.4-pro": { levels: ["medium", "high", "xhigh"], defaultLevel: "medium" },
|
|
1332
|
+
"gpt-5.5": { levels: ["none", "low", "medium", "high", "xhigh"], defaultLevel: "medium" },
|
|
1333
|
+
"gpt-5.5-pro": { levels: ["medium", "high", "xhigh"], defaultLevel: "high" },
|
|
1334
|
+
"gpt-5.6": { levels: ["none", "low", "medium", "high", "xhigh", "max"], defaultLevel: "medium" },
|
|
1335
|
+
"gpt-5.6-luna": { levels: ["none", "low", "medium", "high", "xhigh", "max"], defaultLevel: "medium" },
|
|
1336
|
+
"gpt-5.6-sol": { levels: ["none", "low", "medium", "high", "xhigh", "max"], defaultLevel: "medium" },
|
|
1337
|
+
"gpt-5.6-terra": { levels: ["none", "low", "medium", "high", "xhigh", "max"], defaultLevel: "medium" }
|
|
1338
|
+
};
|
|
1339
|
+
var OPENAI_NON_REASONING_MODELS = /* @__PURE__ */ new Set([
|
|
1340
|
+
"chat-latest",
|
|
1341
|
+
"gpt-5-chat-latest",
|
|
1342
|
+
"gpt-5.1-chat-latest",
|
|
1343
|
+
"gpt-5.2-chat-latest",
|
|
1344
|
+
"gpt-5.3-chat-latest"
|
|
1345
|
+
]);
|
|
1346
|
+
var OPENAI_DATED_SNAPSHOT_SUFFIX = /-\d{4}-\d{2}-\d{2}$/;
|
|
1347
|
+
function canonicalOpenAiModelId(modelId, metadata) {
|
|
1348
|
+
return (metadata?.upstreamModelId ?? modelId ?? "").toLowerCase();
|
|
1349
|
+
}
|
|
1350
|
+
function openAiReasoningProfile(modelId, metadata) {
|
|
1351
|
+
const id = canonicalOpenAiModelId(modelId, metadata);
|
|
1352
|
+
if (!id) return void 0;
|
|
1353
|
+
return OPENAI_MODEL_REASONING[id] ?? OPENAI_MODEL_REASONING[id.replace(OPENAI_DATED_SNAPSHOT_SUFFIX, "")];
|
|
1354
|
+
}
|
|
1355
|
+
function openAiModelReasons(modelId, metadata) {
|
|
1356
|
+
const id = canonicalOpenAiModelId(modelId, metadata);
|
|
1357
|
+
if (OPENAI_NON_REASONING_MODELS.has(id.replace(OPENAI_DATED_SNAPSHOT_SUFFIX, ""))) return false;
|
|
1358
|
+
return !!openAiReasoningProfile(modelId, metadata) || modelPrefersResponsesApi(id) || !!metadata?.reasoning;
|
|
1359
|
+
}
|
|
1360
|
+
function mapCodexEffortToOpenAI(effort, allowed) {
|
|
1361
|
+
return allowed.includes(effort) ? effort : void 0;
|
|
1362
|
+
}
|
|
1363
|
+
function mapCodexEffortToOpenAICompatible(effort) {
|
|
1229
1364
|
if (effort === "xhigh") return "high";
|
|
1230
1365
|
const allowed = ["low", "medium", "high"];
|
|
1231
1366
|
return allowed.includes(effort) ? effort : void 0;
|
|
@@ -1241,16 +1376,12 @@ function mapCodexEffortToGlm52(effort) {
|
|
|
1241
1376
|
return void 0;
|
|
1242
1377
|
}
|
|
1243
1378
|
}
|
|
1244
|
-
function mapCodexEffortToXai(effort) {
|
|
1379
|
+
function mapCodexEffortToXai(effort, supportsMedium) {
|
|
1245
1380
|
switch (effort) {
|
|
1246
|
-
case "none":
|
|
1247
|
-
case "minimal":
|
|
1248
|
-
return void 0;
|
|
1249
|
-
// xAI SDK only accepts 'low'|'high'; omit param for 'none'
|
|
1250
1381
|
case "low":
|
|
1251
|
-
case "medium":
|
|
1252
1382
|
return "low";
|
|
1253
|
-
|
|
1383
|
+
case "medium":
|
|
1384
|
+
return supportsMedium ? "medium" : void 0;
|
|
1254
1385
|
case "high":
|
|
1255
1386
|
case "xhigh":
|
|
1256
1387
|
case "max":
|
|
@@ -1282,7 +1413,31 @@ function mapCodexEffortToGeminiBudget(effort) {
|
|
|
1282
1413
|
if (!level) return void 0;
|
|
1283
1414
|
return GEMINI_25_BUDGETS[level];
|
|
1284
1415
|
}
|
|
1416
|
+
function withMappableLevels(caps, npm, modelId, metadata) {
|
|
1417
|
+
if (caps.mode !== "controllable") return caps;
|
|
1418
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1419
|
+
const levels = caps.levels.filter((level) => {
|
|
1420
|
+
const mapped = effortProviderOptions(npm, level, modelId, metadata);
|
|
1421
|
+
if (mapped === void 0) return false;
|
|
1422
|
+
const wire = JSON.stringify(mapped);
|
|
1423
|
+
if (seen.has(wire)) return false;
|
|
1424
|
+
seen.add(wire);
|
|
1425
|
+
return true;
|
|
1426
|
+
});
|
|
1427
|
+
if (levels.length === caps.levels.length) return caps;
|
|
1428
|
+
if (levels.length === 0) {
|
|
1429
|
+
return { ...caps, levels: [], defaultLevel: "", mode: "internal-only" };
|
|
1430
|
+
}
|
|
1431
|
+
return {
|
|
1432
|
+
...caps,
|
|
1433
|
+
levels,
|
|
1434
|
+
defaultLevel: levels.includes(caps.defaultLevel) ? caps.defaultLevel : levels[levels.length - 1]
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1285
1437
|
function getReasoningCapabilities(npm, modelId, metadata) {
|
|
1438
|
+
return withMappableLevels(resolveRawReasoningCapabilities(npm, modelId, metadata), npm, modelId, metadata);
|
|
1439
|
+
}
|
|
1440
|
+
function resolveRawReasoningCapabilities(npm, modelId, metadata) {
|
|
1286
1441
|
const id = modelId.toLowerCase();
|
|
1287
1442
|
if (isOpenRouterRoute(npm, metadata)) {
|
|
1288
1443
|
return openRouterReasoningCapabilities(metadata);
|
|
@@ -1303,15 +1458,18 @@ function getReasoningCapabilities(npm, modelId, metadata) {
|
|
|
1303
1458
|
return EMPTY_REASONING;
|
|
1304
1459
|
}
|
|
1305
1460
|
if (npm === "@ai-sdk/openai" || npm === "@ai-sdk/azure") {
|
|
1306
|
-
const
|
|
1307
|
-
|
|
1461
|
+
const canonicalId = canonicalOpenAiModelId(modelId, metadata);
|
|
1462
|
+
const profile = openAiReasoningProfile(modelId, metadata);
|
|
1463
|
+
const prefersResponses = modelPrefersResponsesApi(canonicalId);
|
|
1464
|
+
if (openAiModelReasons(modelId, metadata) && shouldUseOpenAiResponsesEndpoint(canonicalId)) {
|
|
1465
|
+
const levels = profile?.levels ?? [...OPENAI_EFFORT_LEVELS];
|
|
1308
1466
|
return {
|
|
1309
|
-
levels: [...
|
|
1310
|
-
defaultLevel: "medium",
|
|
1467
|
+
levels: [...levels],
|
|
1468
|
+
defaultLevel: profile?.defaultLevel ?? (levels.includes("medium") ? "medium" : levels[levels.length - 1]),
|
|
1311
1469
|
supportsSummaries: true,
|
|
1470
|
+
source: profile || prefersResponses ? "provider-rule" : "model-metadata",
|
|
1471
|
+
confidence: profile || prefersResponses ? "documented" : "inferred",
|
|
1312
1472
|
mode: "controllable",
|
|
1313
|
-
source: prefersResponses ? "provider-rule" : "model-metadata",
|
|
1314
|
-
confidence: prefersResponses ? "documented" : "inferred",
|
|
1315
1473
|
wireFormat: { kind: "openai-reasoning-effort" }
|
|
1316
1474
|
};
|
|
1317
1475
|
}
|
|
@@ -1347,7 +1505,7 @@ function getReasoningCapabilities(npm, modelId, metadata) {
|
|
|
1347
1505
|
}
|
|
1348
1506
|
if (npm === "@ai-sdk/xai") {
|
|
1349
1507
|
if (isXaiReasoningEffortModel(modelId)) {
|
|
1350
|
-
const levels = modelPrefersResponsesApi(modelId) ? [
|
|
1508
|
+
const levels = modelPrefersResponsesApi(modelId) ? [...XAI_RESPONSES_EFFORT_LEVELS] : [...XAI_CHAT_EFFORT_LEVELS];
|
|
1351
1509
|
return {
|
|
1352
1510
|
levels,
|
|
1353
1511
|
defaultLevel: xaiDefaultReasoningEffort(modelId),
|
|
@@ -1444,13 +1602,15 @@ function effortProviderOptions(npm, effort, modelId, metadata) {
|
|
|
1444
1602
|
return mapped ? { openrouter: { reasoning: { effort: mapped, exclude: false } } } : void 0;
|
|
1445
1603
|
}
|
|
1446
1604
|
if (npm === "@ai-sdk/openai" || npm === "@ai-sdk/azure") {
|
|
1447
|
-
if (!modelId || !
|
|
1448
|
-
|
|
1605
|
+
if (!modelId || !shouldUseOpenAiResponsesEndpoint(canonicalOpenAiModelId(modelId, metadata))) return void 0;
|
|
1606
|
+
if (!openAiModelReasons(modelId, metadata)) return void 0;
|
|
1607
|
+
const allowed = openAiReasoningProfile(modelId, metadata)?.levels ?? OPENAI_EFFORT_LEVELS;
|
|
1608
|
+
const reasoningEffort = mapCodexEffortToOpenAI(effort, allowed);
|
|
1449
1609
|
return reasoningEffort ? { openai: { reasoningEffort } } : void 0;
|
|
1450
1610
|
}
|
|
1451
1611
|
if (npm === "@ai-sdk/xai") {
|
|
1452
1612
|
if (!modelId || !isXaiReasoningEffortModel(modelId)) return void 0;
|
|
1453
|
-
const reasoningEffort = mapCodexEffortToXai(effort);
|
|
1613
|
+
const reasoningEffort = mapCodexEffortToXai(effort, modelPrefersResponsesApi(modelId));
|
|
1454
1614
|
return reasoningEffort ? { xai: { reasoningEffort } } : void 0;
|
|
1455
1615
|
}
|
|
1456
1616
|
if (npm === "@ai-sdk/anthropic" || npm === VERTEX_ANTHROPIC_NPM) {
|
|
@@ -1478,7 +1638,7 @@ function effortProviderOptions(npm, effort, modelId, metadata) {
|
|
|
1478
1638
|
return deepSeekEffortProviderOptions(effort);
|
|
1479
1639
|
}
|
|
1480
1640
|
if (isKimiReasoningModel(modelId)) {
|
|
1481
|
-
const reasoningEffort =
|
|
1641
|
+
const reasoningEffort = mapCodexEffortToOpenAICompatible(effort);
|
|
1482
1642
|
if (reasoningEffort) {
|
|
1483
1643
|
const key = metadata?.providerId ? toCamelCase(metadata.providerId) : "openaiCompatible";
|
|
1484
1644
|
return { [key]: { reasoningEffort } };
|
|
@@ -1494,7 +1654,7 @@ function effortProviderOptions(npm, effort, modelId, metadata) {
|
|
|
1494
1654
|
return void 0;
|
|
1495
1655
|
}
|
|
1496
1656
|
if (hasSupportedParameter(metadata, "reasoning_effort")) {
|
|
1497
|
-
const reasoningEffort =
|
|
1657
|
+
const reasoningEffort = mapCodexEffortToOpenAICompatible(effort);
|
|
1498
1658
|
return reasoningEffort ? { openai: { reasoningEffort }, openaiCompatible: { reasoningEffort } } : void 0;
|
|
1499
1659
|
}
|
|
1500
1660
|
if (hasSupportedParameter(metadata, "reasoning")) {
|
|
@@ -3586,8 +3746,11 @@ function buildChildEnv(baseUrl, model, apiKey, proxyPort, contextWindow2, enable
|
|
|
3586
3746
|
env["ANTHROPIC_API_KEY"] = apiKey;
|
|
3587
3747
|
const bareModel = stripOneMContextSuffix(model);
|
|
3588
3748
|
env["ANTHROPIC_MODEL"] = claudeCodeClientModelId(model, contextWindow2);
|
|
3589
|
-
|
|
3749
|
+
if (!enableGatewayDiscovery) {
|
|
3750
|
+
env["CLAUDE_CODE_MAX_CONTEXT_TOKENS"] = String(resolveContextWindow(bareModel, contextWindow2));
|
|
3751
|
+
}
|
|
3590
3752
|
if (enableGatewayDiscovery) {
|
|
3753
|
+
delete env["CLAUDE_CODE_MAX_CONTEXT_TOKENS"];
|
|
3591
3754
|
env["CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY"] = "1";
|
|
3592
3755
|
}
|
|
3593
3756
|
applyClaudeCodeThirdPartyCompat(env);
|
|
@@ -6929,8 +7092,8 @@ function anthropicError(res, status, message) {
|
|
|
6929
7092
|
});
|
|
6930
7093
|
}
|
|
6931
7094
|
function aliasModelId(realId, providerId) {
|
|
6932
|
-
if (realId.startsWith("claude-")) return realId;
|
|
6933
7095
|
const sanitized = providerId.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
7096
|
+
if (realId.startsWith("claude-") && !sanitized.startsWith("custom-")) return realId;
|
|
6934
7097
|
return `anthropic-${sanitized}__${realId}`;
|
|
6935
7098
|
}
|
|
6936
7099
|
function buildProxySubagentModelRouting(routes, parentRoute) {
|
|
@@ -9244,6 +9407,80 @@ function goRegistryStub() {
|
|
|
9244
9407
|
};
|
|
9245
9408
|
}
|
|
9246
9409
|
|
|
9410
|
+
// src/registry/fetch-anthropic-models.ts
|
|
9411
|
+
async function fetchAnthropicModels(baseUrl, apiKey, extraHeaders) {
|
|
9412
|
+
const root = baseUrl.replace(/\/v1\/?$/, "").replace(/\/$/, "");
|
|
9413
|
+
const modelsUrl2 = `${root}/v1/models`;
|
|
9414
|
+
const controller = new AbortController();
|
|
9415
|
+
const timer = setTimeout(() => controller.abort(), 1e4);
|
|
9416
|
+
try {
|
|
9417
|
+
const response = await fetch(modelsUrl2, {
|
|
9418
|
+
method: "GET",
|
|
9419
|
+
headers: {
|
|
9420
|
+
"x-api-key": apiKey,
|
|
9421
|
+
"anthropic-version": "2023-06-01",
|
|
9422
|
+
Accept: "application/json",
|
|
9423
|
+
...extraHeaders
|
|
9424
|
+
},
|
|
9425
|
+
redirect: "manual",
|
|
9426
|
+
signal: controller.signal
|
|
9427
|
+
});
|
|
9428
|
+
let logTrace;
|
|
9429
|
+
if (process.env.RELAY_AI_TRACE === "1") {
|
|
9430
|
+
logTrace = makeTraceLogger(getProviderDebugLogPath());
|
|
9431
|
+
}
|
|
9432
|
+
const rawBodyText = await response.text().catch(() => "");
|
|
9433
|
+
if (logTrace) {
|
|
9434
|
+
logTrace(`[fetchAnthropicModels] HTTP ${response.status} from ${modelsUrl2}`);
|
|
9435
|
+
logTrace(`[fetchAnthropicModels] Body: ${rawBodyText}`);
|
|
9436
|
+
}
|
|
9437
|
+
if (response.ok) {
|
|
9438
|
+
let json = {};
|
|
9439
|
+
try {
|
|
9440
|
+
if (rawBodyText.trim()) {
|
|
9441
|
+
json = JSON.parse(rawBodyText);
|
|
9442
|
+
}
|
|
9443
|
+
} catch {
|
|
9444
|
+
}
|
|
9445
|
+
const models = [];
|
|
9446
|
+
for (const row of json.data ?? []) {
|
|
9447
|
+
const id = row.id?.trim();
|
|
9448
|
+
if (!id) continue;
|
|
9449
|
+
models.push({
|
|
9450
|
+
id,
|
|
9451
|
+
name: row.name?.trim() || id,
|
|
9452
|
+
upstreamModelId: id,
|
|
9453
|
+
family: id.split("-")[0] ?? id,
|
|
9454
|
+
brand: deriveBrand(id),
|
|
9455
|
+
contextWindow: resolveContextWindow(id),
|
|
9456
|
+
modelFormat: "anthropic",
|
|
9457
|
+
npm: "@ai-sdk/anthropic",
|
|
9458
|
+
apiUrl: root
|
|
9459
|
+
});
|
|
9460
|
+
}
|
|
9461
|
+
if (models.length > 0) return { models, baseUrl: root };
|
|
9462
|
+
}
|
|
9463
|
+
if (response.status === 401 || response.status === 403) {
|
|
9464
|
+
return { models: [], baseUrl: root, error: "API key was rejected.", hint: "Check your Anthropic-compatible API key." };
|
|
9465
|
+
}
|
|
9466
|
+
return {
|
|
9467
|
+
models: [],
|
|
9468
|
+
baseUrl: root,
|
|
9469
|
+
error: `Could not list models (HTTP ${response.status}).`,
|
|
9470
|
+
hint: "Verify the base URL supports Anthropic-compatible /v1/models or try the OpenAI-compatible option instead."
|
|
9471
|
+
};
|
|
9472
|
+
} catch {
|
|
9473
|
+
return {
|
|
9474
|
+
models: [],
|
|
9475
|
+
baseUrl: root,
|
|
9476
|
+
error: "Could not reach the Anthropic-compatible server.",
|
|
9477
|
+
hint: "Check the base URL and that the server is running."
|
|
9478
|
+
};
|
|
9479
|
+
} finally {
|
|
9480
|
+
clearTimeout(timer);
|
|
9481
|
+
}
|
|
9482
|
+
}
|
|
9483
|
+
|
|
9247
9484
|
// src/registry/fetch-template-models.ts
|
|
9248
9485
|
var TEST_TIMEOUT_MS = 1e4;
|
|
9249
9486
|
function modelFormatForNpm(npm) {
|
|
@@ -9604,77 +9841,28 @@ function npmForKind(kind) {
|
|
|
9604
9841
|
function modelFormatForKind(kind) {
|
|
9605
9842
|
return kind === "anthropic" ? "anthropic" : "openai";
|
|
9606
9843
|
}
|
|
9607
|
-
|
|
9608
|
-
|
|
9609
|
-
|
|
9610
|
-
|
|
9611
|
-
|
|
9612
|
-
|
|
9613
|
-
|
|
9614
|
-
|
|
9615
|
-
|
|
9616
|
-
|
|
9617
|
-
|
|
9618
|
-
|
|
9619
|
-
|
|
9620
|
-
|
|
9621
|
-
|
|
9622
|
-
|
|
9623
|
-
|
|
9624
|
-
|
|
9625
|
-
|
|
9626
|
-
|
|
9627
|
-
}
|
|
9628
|
-
const rawBodyText = await response.text().catch(() => "");
|
|
9629
|
-
if (logTrace) {
|
|
9630
|
-
logTrace(`[fetchAnthropicModels] HTTP ${response.status} from ${modelsUrl2}`);
|
|
9631
|
-
logTrace(`[fetchAnthropicModels] Body: ${rawBodyText}`);
|
|
9632
|
-
}
|
|
9633
|
-
if (response.ok) {
|
|
9634
|
-
let json = {};
|
|
9635
|
-
try {
|
|
9636
|
-
if (rawBodyText.trim()) {
|
|
9637
|
-
json = JSON.parse(rawBodyText);
|
|
9638
|
-
}
|
|
9639
|
-
} catch {
|
|
9640
|
-
}
|
|
9641
|
-
const models = [];
|
|
9642
|
-
for (const row of json.data ?? []) {
|
|
9643
|
-
const id = row.id?.trim();
|
|
9644
|
-
if (!id) continue;
|
|
9645
|
-
models.push({
|
|
9646
|
-
id,
|
|
9647
|
-
name: row.name?.trim() || id,
|
|
9648
|
-
upstreamModelId: id,
|
|
9649
|
-
family: id.split("-")[0] ?? id,
|
|
9650
|
-
brand: deriveBrand(id),
|
|
9651
|
-
contextWindow: resolveContextWindow(id),
|
|
9652
|
-
modelFormat: "anthropic",
|
|
9653
|
-
npm: "@ai-sdk/anthropic",
|
|
9654
|
-
apiUrl: root
|
|
9655
|
-
});
|
|
9656
|
-
}
|
|
9657
|
-
if (models.length > 0) return { models, baseUrl: root };
|
|
9658
|
-
}
|
|
9659
|
-
if (response.status === 401 || response.status === 403) {
|
|
9660
|
-
return { models: [], baseUrl: root, error: "API key was rejected.", hint: "Check your Anthropic-compatible API key." };
|
|
9661
|
-
}
|
|
9662
|
-
return {
|
|
9663
|
-
models: [],
|
|
9664
|
-
baseUrl: root,
|
|
9665
|
-
error: `Could not list models (HTTP ${response.status}).`,
|
|
9666
|
-
hint: "Verify the base URL supports Anthropic-compatible /v1/models or try the OpenAI-compatible option instead."
|
|
9667
|
-
};
|
|
9668
|
-
} catch {
|
|
9669
|
-
return {
|
|
9670
|
-
models: [],
|
|
9671
|
-
baseUrl: root,
|
|
9672
|
-
error: "Could not reach the Anthropic-compatible server.",
|
|
9673
|
-
hint: "Check the base URL and that the server is running."
|
|
9674
|
-
};
|
|
9675
|
-
} finally {
|
|
9676
|
-
clearTimeout(timer);
|
|
9844
|
+
function customEndpointKind(provider) {
|
|
9845
|
+
if (provider.templateId === "custom-anthropic") return "anthropic";
|
|
9846
|
+
if (provider.templateId === "custom-openai") return "openai";
|
|
9847
|
+
return null;
|
|
9848
|
+
}
|
|
9849
|
+
function sameHeaders(a, b) {
|
|
9850
|
+
const norm = (h) => JSON.stringify(Object.entries(h ?? {}).sort(([x], [y]) => x.localeCompare(y)));
|
|
9851
|
+
return norm(a) === norm(b);
|
|
9852
|
+
}
|
|
9853
|
+
function compareableUrl(url) {
|
|
9854
|
+
return url.replace(/\/v1\/?$/, "").replace(/\/$/, "");
|
|
9855
|
+
}
|
|
9856
|
+
async function findDuplicateCustomProvider(registry, normalizedUrl, apiKey, headers) {
|
|
9857
|
+
const target = compareableUrl(normalizedUrl);
|
|
9858
|
+
for (const existing of registry.providers) {
|
|
9859
|
+
if (!customEndpointKind(existing)) continue;
|
|
9860
|
+
if (compareableUrl(existing.api.url ?? "") !== target) continue;
|
|
9861
|
+
if (!sameHeaders(existing.api.headers, headers)) continue;
|
|
9862
|
+
const storedKey = await readStoredProviderCredential(existing.authRef);
|
|
9863
|
+
if ((storedKey ?? "") === apiKey) return existing.id;
|
|
9677
9864
|
}
|
|
9865
|
+
return null;
|
|
9678
9866
|
}
|
|
9679
9867
|
function uniqueProviderId(displayName, registry) {
|
|
9680
9868
|
let base = customProviderId(displayName);
|
|
@@ -9689,6 +9877,25 @@ function uniqueProviderId(displayName, registry) {
|
|
|
9689
9877
|
}
|
|
9690
9878
|
return `${base}-${Date.now()}`;
|
|
9691
9879
|
}
|
|
9880
|
+
async function fetchCustomEndpointModels(input) {
|
|
9881
|
+
if (input.kind === "anthropic") {
|
|
9882
|
+
return fetchAnthropicModels(input.normalizedBaseUrl, input.apiKey, input.headers);
|
|
9883
|
+
}
|
|
9884
|
+
return fetchTemplateModels(
|
|
9885
|
+
{
|
|
9886
|
+
id: input.providerId,
|
|
9887
|
+
name: input.displayName,
|
|
9888
|
+
authType: input.apiKey === "local" ? "none" : "api",
|
|
9889
|
+
npm: npmForKind(input.kind),
|
|
9890
|
+
defaultBaseUrl: input.normalizedBaseUrl,
|
|
9891
|
+
modelSource: "api-list",
|
|
9892
|
+
supported: true
|
|
9893
|
+
},
|
|
9894
|
+
input.apiKey,
|
|
9895
|
+
input.normalizedBaseUrl,
|
|
9896
|
+
input.headers
|
|
9897
|
+
);
|
|
9898
|
+
}
|
|
9692
9899
|
async function addCustomEndpointProvider(input) {
|
|
9693
9900
|
const urlCheck = await validateCustomEndpointUrl(input.baseUrl, {
|
|
9694
9901
|
allowInsecureLocal: input.allowInsecureLocal
|
|
@@ -9697,29 +9904,34 @@ async function addCustomEndpointProvider(input) {
|
|
|
9697
9904
|
return { added: false, error: urlCheck.error, hint: urlCheck.hint };
|
|
9698
9905
|
}
|
|
9699
9906
|
const registry = loadRegistry();
|
|
9700
|
-
const providerId = uniqueProviderId(input.displayName.trim(), registry);
|
|
9701
|
-
const npm = npmForKind(input.kind);
|
|
9702
9907
|
const apiKey = input.apiKey.trim() || "local";
|
|
9703
9908
|
const headers = input.headers && Object.keys(input.headers).length > 0 ? input.headers : void 0;
|
|
9704
|
-
|
|
9705
|
-
|
|
9706
|
-
|
|
9707
|
-
} else {
|
|
9708
|
-
fetched = await fetchTemplateModels(
|
|
9709
|
-
{
|
|
9710
|
-
id: providerId,
|
|
9711
|
-
name: input.displayName,
|
|
9712
|
-
authType: apiKey === "local" ? "none" : "api",
|
|
9713
|
-
npm,
|
|
9714
|
-
defaultBaseUrl: urlCheck.normalizedUrl,
|
|
9715
|
-
modelSource: "api-list",
|
|
9716
|
-
supported: true
|
|
9717
|
-
},
|
|
9718
|
-
apiKey,
|
|
9909
|
+
if (!input.confirmDuplicate) {
|
|
9910
|
+
const duplicateOf = await findDuplicateCustomProvider(
|
|
9911
|
+
registry,
|
|
9719
9912
|
urlCheck.normalizedUrl,
|
|
9913
|
+
apiKey,
|
|
9720
9914
|
headers
|
|
9721
9915
|
);
|
|
9916
|
+
if (duplicateOf) {
|
|
9917
|
+
return {
|
|
9918
|
+
added: false,
|
|
9919
|
+
duplicateOf,
|
|
9920
|
+
error: `A backend with the same URL, key and headers already exists (${duplicateOf}).`,
|
|
9921
|
+
hint: "Add it anyway to keep both, or cancel."
|
|
9922
|
+
};
|
|
9923
|
+
}
|
|
9722
9924
|
}
|
|
9925
|
+
const providerId = uniqueProviderId(input.displayName.trim(), registry);
|
|
9926
|
+
const npm = npmForKind(input.kind);
|
|
9927
|
+
const fetched = await fetchCustomEndpointModels({
|
|
9928
|
+
providerId,
|
|
9929
|
+
displayName: input.displayName,
|
|
9930
|
+
kind: input.kind,
|
|
9931
|
+
normalizedBaseUrl: urlCheck.normalizedUrl,
|
|
9932
|
+
apiKey,
|
|
9933
|
+
headers
|
|
9934
|
+
});
|
|
9723
9935
|
if (fetched.error || fetched.models.length === 0) {
|
|
9724
9936
|
return { added: false, error: fetched.error ?? "No models returned.", hint: fetched.hint };
|
|
9725
9937
|
}
|
|
@@ -9756,6 +9968,115 @@ async function addCustomEndpointProvider(input) {
|
|
|
9756
9968
|
saveRegistry(registry);
|
|
9757
9969
|
return { added: true, provider: entry, modelCount: fetched.models.length };
|
|
9758
9970
|
}
|
|
9971
|
+
async function updateCustomEndpointProvider(input) {
|
|
9972
|
+
const registry = loadRegistry();
|
|
9973
|
+
const provider = registry.providers.find((pr) => pr.id === input.providerId);
|
|
9974
|
+
if (!provider) {
|
|
9975
|
+
return { updated: false, error: `Provider not found: ${input.providerId}` };
|
|
9976
|
+
}
|
|
9977
|
+
const kind = customEndpointKind(provider);
|
|
9978
|
+
if (!kind) {
|
|
9979
|
+
return {
|
|
9980
|
+
updated: false,
|
|
9981
|
+
error: "Edit is only available for custom backends.",
|
|
9982
|
+
hint: "Template providers can only change their API key."
|
|
9983
|
+
};
|
|
9984
|
+
}
|
|
9985
|
+
const nextName = input.displayName?.trim();
|
|
9986
|
+
let nextBaseUrl = provider.api.url ?? "";
|
|
9987
|
+
let urlChanged = false;
|
|
9988
|
+
const requestedUrl = input.baseUrl?.trim();
|
|
9989
|
+
if (requestedUrl) {
|
|
9990
|
+
const urlCheck = await validateCustomEndpointUrl(requestedUrl, {
|
|
9991
|
+
allowInsecureLocal: input.allowInsecureLocal
|
|
9992
|
+
});
|
|
9993
|
+
if (!urlCheck.ok || !urlCheck.normalizedUrl) {
|
|
9994
|
+
return { updated: false, error: urlCheck.error, hint: urlCheck.hint };
|
|
9995
|
+
}
|
|
9996
|
+
urlChanged = urlCheck.normalizedUrl !== nextBaseUrl;
|
|
9997
|
+
nextBaseUrl = urlCheck.normalizedUrl;
|
|
9998
|
+
}
|
|
9999
|
+
const newKey = input.apiKey?.trim();
|
|
10000
|
+
const headersChanged = input.headers !== void 0 && !sameHeaders(input.headers, provider.api.headers);
|
|
10001
|
+
const nextHeaders = input.headers !== void 0 ? Object.keys(input.headers).length > 0 ? input.headers : void 0 : provider.api.headers;
|
|
10002
|
+
const nameChanged = Boolean(nextName) && nextName !== provider.name;
|
|
10003
|
+
const needsTest = urlChanged || Boolean(newKey) || headersChanged;
|
|
10004
|
+
if (!needsTest) {
|
|
10005
|
+
if (!nameChanged) return { updated: false, error: "Nothing to change." };
|
|
10006
|
+
provider.name = nextName;
|
|
10007
|
+
saveRegistry(registry);
|
|
10008
|
+
return {
|
|
10009
|
+
updated: true,
|
|
10010
|
+
provider,
|
|
10011
|
+
modelCount: provider.modelsCache?.models.length ?? 0
|
|
10012
|
+
};
|
|
10013
|
+
}
|
|
10014
|
+
const apiKey = newKey || await readStoredProviderCredential(provider.authRef) || "";
|
|
10015
|
+
if (!apiKey) {
|
|
10016
|
+
return {
|
|
10017
|
+
updated: false,
|
|
10018
|
+
error: "No stored API key was found for this backend.",
|
|
10019
|
+
hint: "Enter an API key to continue."
|
|
10020
|
+
};
|
|
10021
|
+
}
|
|
10022
|
+
const fetched = await fetchCustomEndpointModels({
|
|
10023
|
+
providerId: provider.id,
|
|
10024
|
+
displayName: nextName || provider.name,
|
|
10025
|
+
kind,
|
|
10026
|
+
normalizedBaseUrl: nextBaseUrl,
|
|
10027
|
+
apiKey,
|
|
10028
|
+
headers: nextHeaders
|
|
10029
|
+
});
|
|
10030
|
+
const testFailed = Boolean(fetched.error) || fetched.models.length === 0;
|
|
10031
|
+
if (testFailed && !input.saveAnyway) {
|
|
10032
|
+
return {
|
|
10033
|
+
updated: false,
|
|
10034
|
+
error: fetched.error ?? "No models returned.",
|
|
10035
|
+
hint: fetched.hint,
|
|
10036
|
+
canSaveAnyway: true
|
|
10037
|
+
};
|
|
10038
|
+
}
|
|
10039
|
+
if (newKey) {
|
|
10040
|
+
const saved = await saveProviderCredential(provider.authRef, newKey);
|
|
10041
|
+
if (!saved) {
|
|
10042
|
+
return {
|
|
10043
|
+
updated: false,
|
|
10044
|
+
error: "Could not save API key to credential store.",
|
|
10045
|
+
hint: "Grant Keychain access, or ensure RELAY_AI_HOME is writable (file fallback)."
|
|
10046
|
+
};
|
|
10047
|
+
}
|
|
10048
|
+
}
|
|
10049
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
10050
|
+
if (nameChanged) provider.name = nextName;
|
|
10051
|
+
const storedBaseUrl = kind === "anthropic" ? nextBaseUrl.replace(/\/v1\/?$/, "").replace(/\/$/, "") : nextBaseUrl;
|
|
10052
|
+
provider.api.url = testFailed ? storedBaseUrl : fetched.baseUrl || storedBaseUrl;
|
|
10053
|
+
if (nextHeaders) provider.api.headers = nextHeaders;
|
|
10054
|
+
else delete provider.api.headers;
|
|
10055
|
+
if (!testFailed) {
|
|
10056
|
+
provider.modelsCache = {
|
|
10057
|
+
fetchedAt: now,
|
|
10058
|
+
models: fetched.models.map((m) => ({
|
|
10059
|
+
...m,
|
|
10060
|
+
modelFormat: modelFormatForKind(kind),
|
|
10061
|
+
npm: npmForKind(kind),
|
|
10062
|
+
apiUrl: fetched.baseUrl || storedBaseUrl
|
|
10063
|
+
}))
|
|
10064
|
+
};
|
|
10065
|
+
provider.refreshedAt = now;
|
|
10066
|
+
} else if (provider.modelsCache) {
|
|
10067
|
+
provider.modelsCache = {
|
|
10068
|
+
...provider.modelsCache,
|
|
10069
|
+
models: provider.modelsCache.models.map((m) => ({ ...m, apiUrl: storedBaseUrl }))
|
|
10070
|
+
};
|
|
10071
|
+
}
|
|
10072
|
+
saveRegistry(registry);
|
|
10073
|
+
return {
|
|
10074
|
+
updated: true,
|
|
10075
|
+
provider,
|
|
10076
|
+
modelCount: provider.modelsCache?.models.length ?? 0,
|
|
10077
|
+
...testFailed ? { modelsStale: true } : {}
|
|
10078
|
+
};
|
|
10079
|
+
}
|
|
9759
10080
|
|
|
9760
10081
|
// src/registry/fetch-cline-pass-models.ts
|
|
9761
10082
|
var REQUEST_TIMEOUT_MS = 1e4;
|
|
@@ -10283,7 +10604,7 @@ async function refreshApiListProvider(provider, apiKey) {
|
|
|
10283
10604
|
const templateDefault = catalogTemplate?.defaultBaseUrl?.trim();
|
|
10284
10605
|
if (configuredUrl && configuredUrl !== templateDefault) {
|
|
10285
10606
|
const urlCheck = await validateCustomEndpointUrl(baseUrl, {
|
|
10286
|
-
allowInsecureLocal: catalogTemplate?.apiKeyOptional === true
|
|
10607
|
+
allowInsecureLocal: catalogTemplate?.apiKeyOptional === true || customEndpointKind(provider) !== null
|
|
10287
10608
|
});
|
|
10288
10609
|
if (!urlCheck.ok || !urlCheck.normalizedUrl) {
|
|
10289
10610
|
return { models: [], error: `${urlCheck.error ?? "Invalid API base URL."} ${urlCheck.hint ?? ""}`.trim() };
|
|
@@ -10291,8 +10612,9 @@ async function refreshApiListProvider(provider, apiKey) {
|
|
|
10291
10612
|
safeBaseUrl = urlCheck.normalizedUrl;
|
|
10292
10613
|
}
|
|
10293
10614
|
const template = catalogTemplate ?? syntheticTemplate(provider, safeBaseUrl);
|
|
10615
|
+
const extraHeaders = provider.api.headers && Object.keys(provider.api.headers).length > 0 ? provider.api.headers : void 0;
|
|
10294
10616
|
if (npm === "@ai-sdk/anthropic") {
|
|
10295
|
-
const fetched2 = await fetchAnthropicModels(safeBaseUrl, apiKey);
|
|
10617
|
+
const fetched2 = await fetchAnthropicModels(safeBaseUrl, apiKey, extraHeaders);
|
|
10296
10618
|
if (fetched2.error || fetched2.models.length === 0) {
|
|
10297
10619
|
return { models: [], error: fetched2.error ?? "No models returned.", baseUrl: fetched2.baseUrl };
|
|
10298
10620
|
}
|
|
@@ -10301,7 +10623,7 @@ async function refreshApiListProvider(provider, apiKey) {
|
|
|
10301
10623
|
baseUrl: fetched2.baseUrl
|
|
10302
10624
|
};
|
|
10303
10625
|
}
|
|
10304
|
-
const fetched = await fetchTemplateModels(template, apiKey, safeBaseUrl);
|
|
10626
|
+
const fetched = await fetchTemplateModels(template, apiKey, safeBaseUrl, extraHeaders);
|
|
10305
10627
|
if (fetched.error || fetched.models.length === 0) {
|
|
10306
10628
|
return { models: [], error: fetched.error ?? "No models returned." };
|
|
10307
10629
|
}
|
|
@@ -12234,7 +12556,7 @@ function removeFavorite(list, fav) {
|
|
|
12234
12556
|
// src/favorite-provider-display.ts
|
|
12235
12557
|
var OAUTH_FAVORITE_NAMES = {
|
|
12236
12558
|
"claude-code": "Claude Code OAuth (Anthropic subscription)",
|
|
12237
|
-
antigravity: "
|
|
12559
|
+
antigravity: "Cloud Code Assist OAuth (Google)",
|
|
12238
12560
|
"openai-oauth": "OpenAI OAuth (ChatGPT)",
|
|
12239
12561
|
"xai-oauth": "xAI OAuth (SuperGrok)"
|
|
12240
12562
|
};
|
|
@@ -12437,7 +12759,7 @@ var PROVIDER_DISPLAY = {
|
|
|
12437
12759
|
"openai-oauth": OPENAI_DISPLAY,
|
|
12438
12760
|
"github-copilot": "GitHub Copilot",
|
|
12439
12761
|
"claude-code": "Claude Code (Anthropic subscription)",
|
|
12440
|
-
antigravity: "
|
|
12762
|
+
antigravity: "Cloud Code Assist OAuth (Google)",
|
|
12441
12763
|
"cline-pass": "ClinePass"
|
|
12442
12764
|
};
|
|
12443
12765
|
function openBrowser(url) {
|
|
@@ -12540,7 +12862,7 @@ async function runNativeBrowserOAuth(providerId) {
|
|
|
12540
12862
|
p5.log.info(`Opening: ${pc6.cyan(url)}`);
|
|
12541
12863
|
spinner3.start("Waiting for authorization\u2026");
|
|
12542
12864
|
});
|
|
12543
|
-
spinner3.stop(pc6.green("Signed in to
|
|
12865
|
+
spinner3.stop(pc6.green("Signed in to Cloud Code Assist"));
|
|
12544
12866
|
const providerData = {};
|
|
12545
12867
|
if (projectId) providerData.projectId = projectId;
|
|
12546
12868
|
if (tierId) providerData.tier = tierId;
|
|
@@ -13414,14 +13736,13 @@ export {
|
|
|
13414
13736
|
makeTraceLogger,
|
|
13415
13737
|
writeSecureLogLine,
|
|
13416
13738
|
printTraceLog,
|
|
13417
|
-
fetchTemplateModels,
|
|
13418
|
-
validateCustomEndpointUrl,
|
|
13419
13739
|
fetchAnthropicModels,
|
|
13420
|
-
|
|
13740
|
+
fetchTemplateModels,
|
|
13421
13741
|
resolveProviderTemplate,
|
|
13422
13742
|
effectiveProviderBaseUrl,
|
|
13423
13743
|
syntheticTemplate,
|
|
13424
13744
|
resolveModelSource,
|
|
13745
|
+
validateCustomEndpointUrl,
|
|
13425
13746
|
readBody,
|
|
13426
13747
|
extractApiKey,
|
|
13427
13748
|
sendJson,
|
|
@@ -13476,6 +13797,9 @@ export {
|
|
|
13476
13797
|
routableModelsForTarget,
|
|
13477
13798
|
providersForTarget,
|
|
13478
13799
|
providersForCodexSubagents,
|
|
13800
|
+
customEndpointKind,
|
|
13801
|
+
addCustomEndpointProvider,
|
|
13802
|
+
updateCustomEndpointProvider,
|
|
13479
13803
|
refreshProviderModels,
|
|
13480
13804
|
refreshAllProviderModels,
|
|
13481
13805
|
removeProviderFromRegistry,
|
|
@@ -13520,4 +13844,4 @@ export {
|
|
|
13520
13844
|
supportsClaudeTransparentMode,
|
|
13521
13845
|
buildHttpProxyRoutes
|
|
13522
13846
|
};
|
|
13523
|
-
//# sourceMappingURL=chunk-
|
|
13847
|
+
//# sourceMappingURL=chunk-KDIY732Q.js.map
|