@jacobbd/relay-ai 0.6.2 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -4
- package/dist/{chunk-I3I5LKZI.js → chunk-IYYLLN5T.js} +62 -14
- package/dist/chunk-IYYLLN5T.js.map +1 -0
- package/dist/cli.js +320 -148
- package/dist/cli.js.map +1 -1
- package/dist/core/index.d.ts +91 -0
- package/dist/core/index.js +1854 -0
- package/dist/core/index.js.map +1 -0
- package/dist/{ui-command-GNCQS7F7.js → ui-command-3CWMSARO.js} +2 -2
- package/package.json +10 -2
- package/dist/chunk-I3I5LKZI.js.map +0 -1
- /package/dist/{ui-command-GNCQS7F7.js.map → ui-command-3CWMSARO.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -47,7 +47,6 @@ import {
|
|
|
47
47
|
fetchAnthropicModels,
|
|
48
48
|
fetchProviderCatalog,
|
|
49
49
|
fetchTemplateModels,
|
|
50
|
-
filterServerModelsByFavorites,
|
|
51
50
|
findBinaryOnPath,
|
|
52
51
|
findClaudeBinary,
|
|
53
52
|
fmtCommand,
|
|
@@ -87,7 +86,6 @@ import {
|
|
|
87
86
|
listCredentialSkippedProviders,
|
|
88
87
|
loadPreferences,
|
|
89
88
|
loadRegistry,
|
|
90
|
-
loadServerModels,
|
|
91
89
|
logActiveModel,
|
|
92
90
|
logConnected,
|
|
93
91
|
logProxy,
|
|
@@ -167,7 +165,7 @@ import {
|
|
|
167
165
|
validateCustomEndpointUrl,
|
|
168
166
|
writeSecureLogLine,
|
|
169
167
|
zenRegistryStub
|
|
170
|
-
} from "./chunk-
|
|
168
|
+
} from "./chunk-IYYLLN5T.js";
|
|
171
169
|
import {
|
|
172
170
|
filterTemplates,
|
|
173
171
|
init_provider_templates,
|
|
@@ -4305,6 +4303,14 @@ async function buildFavoritesList(starting, favorites, ctx, max = 20, options =
|
|
|
4305
4303
|
}
|
|
4306
4304
|
return { resolved: out, droppedFavorites, capacitySkippedFavorites };
|
|
4307
4305
|
}
|
|
4306
|
+
function resolveFirstAvailableFavorite(favorites, providers) {
|
|
4307
|
+
for (const fav of favorites) {
|
|
4308
|
+
const provider = providers.find((lp) => lp.id === fav.providerId);
|
|
4309
|
+
const model = provider?.models.find((m) => m.id === fav.modelId);
|
|
4310
|
+
if (provider && model) return { provider, model };
|
|
4311
|
+
}
|
|
4312
|
+
return void 0;
|
|
4313
|
+
}
|
|
4308
4314
|
|
|
4309
4315
|
// src/codex/favorites-launch.ts
|
|
4310
4316
|
var identityProvider = (provider) => provider;
|
|
@@ -4627,6 +4633,9 @@ function planLaunchWizard(opts) {
|
|
|
4627
4633
|
}
|
|
4628
4634
|
return { skip: false, target: null };
|
|
4629
4635
|
}
|
|
4636
|
+
function launchAllowsNonTty(plan, bypassWizard = false) {
|
|
4637
|
+
return bypassWizard || !!(plan.skip && plan.target);
|
|
4638
|
+
}
|
|
4630
4639
|
function nonInteractiveLaunchError(agent) {
|
|
4631
4640
|
if (agent === "claude") return "Print mode requires --provider and --model, or saved preferences from a prior launch.";
|
|
4632
4641
|
if (agent === "codex") return "Non-interactive Codex launch requires --provider and --model, or saved preferences from a prior launch.";
|
|
@@ -10569,26 +10578,187 @@ function writeRelayAiConfig(proxyPort) {
|
|
|
10569
10578
|
return uuid;
|
|
10570
10579
|
}
|
|
10571
10580
|
|
|
10581
|
+
// src/claude-desktop/model-catalog.ts
|
|
10582
|
+
async function resolveClaudeAppCatalog(selectedProvider, selectedModel, compatibleProviders, favorites, max = MAX_MODEL_CATALOG) {
|
|
10583
|
+
const providersById = new Map(
|
|
10584
|
+
compatibleProviders.map((provider) => [provider.id, provider])
|
|
10585
|
+
);
|
|
10586
|
+
const context = {
|
|
10587
|
+
agent: "codex-app",
|
|
10588
|
+
localProviders: compatibleProviders,
|
|
10589
|
+
findLocalModel: (providerId, modelId) => {
|
|
10590
|
+
const provider = providersById.get(providerId);
|
|
10591
|
+
const model = provider?.models.find((candidate) => candidate.id === modelId);
|
|
10592
|
+
return provider && model ? { provider, model } : void 0;
|
|
10593
|
+
}
|
|
10594
|
+
};
|
|
10595
|
+
const starting = await resolveFavorite(
|
|
10596
|
+
{ providerId: selectedProvider.id, modelId: selectedModel.id },
|
|
10597
|
+
context
|
|
10598
|
+
);
|
|
10599
|
+
if (!starting) {
|
|
10600
|
+
return {
|
|
10601
|
+
ok: false,
|
|
10602
|
+
error: `Model ${selectedModel.id} is no longer available on ${selectedProvider.name}.`
|
|
10603
|
+
};
|
|
10604
|
+
}
|
|
10605
|
+
if (!starting.apiKey.trim()) {
|
|
10606
|
+
return {
|
|
10607
|
+
ok: false,
|
|
10608
|
+
error: `No credential for ${selectedProvider.name}. Run relay-ai providers auth ${selectedProvider.id}.`
|
|
10609
|
+
};
|
|
10610
|
+
}
|
|
10611
|
+
const {
|
|
10612
|
+
resolved,
|
|
10613
|
+
droppedFavorites,
|
|
10614
|
+
capacitySkippedFavorites
|
|
10615
|
+
} = await buildFavoritesList(starting, favorites, context, max, {
|
|
10616
|
+
dropEmptyApiKey: true,
|
|
10617
|
+
trackCapacitySkipped: true
|
|
10618
|
+
});
|
|
10619
|
+
return {
|
|
10620
|
+
ok: true,
|
|
10621
|
+
entries: resolved,
|
|
10622
|
+
providersById,
|
|
10623
|
+
droppedFavorites,
|
|
10624
|
+
capacitySkippedFavorites
|
|
10625
|
+
};
|
|
10626
|
+
}
|
|
10627
|
+
function modelToServerModelInfo(model, provider, overrides = {}) {
|
|
10628
|
+
return {
|
|
10629
|
+
id: model.id,
|
|
10630
|
+
name: model.name,
|
|
10631
|
+
isFree: model.isFree ?? false,
|
|
10632
|
+
freeStatus: model.freeStatus,
|
|
10633
|
+
brand: model.brand ?? "",
|
|
10634
|
+
providerLabel: provider.name,
|
|
10635
|
+
providerId: provider.id,
|
|
10636
|
+
sourceBackend: provider.id,
|
|
10637
|
+
modelFormat: model.modelFormat,
|
|
10638
|
+
upstreamModelId: model.upstreamModelId,
|
|
10639
|
+
cost: model.cost,
|
|
10640
|
+
baseUrl: model.baseUrl,
|
|
10641
|
+
completionsUrl: model.completionsUrl,
|
|
10642
|
+
npm: model.npm,
|
|
10643
|
+
apiBaseUrl: model.apiBaseUrl,
|
|
10644
|
+
apiKey: provider.apiKey,
|
|
10645
|
+
authType: provider.authType,
|
|
10646
|
+
oauthAccountId: provider.oauthAccountId,
|
|
10647
|
+
contextWindow: model.contextWindow,
|
|
10648
|
+
supportedParameters: model.supportedParameters,
|
|
10649
|
+
reasoning: model.reasoning,
|
|
10650
|
+
interleavedReasoningField: model.interleavedReasoningField,
|
|
10651
|
+
useResponsesLite: model.useResponsesLite,
|
|
10652
|
+
preferWebSockets: model.preferWebSockets,
|
|
10653
|
+
headers: provider.headers,
|
|
10654
|
+
providerData: provider.providerData,
|
|
10655
|
+
...overrides
|
|
10656
|
+
};
|
|
10657
|
+
}
|
|
10658
|
+
function entryKey(entry) {
|
|
10659
|
+
return `${entry.providerId}::${entry.model.id}`;
|
|
10660
|
+
}
|
|
10661
|
+
async function buildClaudeAppServerCatalog(entries, providersById, trace) {
|
|
10662
|
+
const convertedByKey = /* @__PURE__ */ new Map();
|
|
10663
|
+
const cloudCodeEntries = entries.filter((entry) => entry.model.modelFormat === "cloud-code");
|
|
10664
|
+
const regularEntries = entries.filter((entry) => entry.model.modelFormat !== "cloud-code");
|
|
10665
|
+
for (const entry of regularEntries) {
|
|
10666
|
+
const provider = providersById.get(entry.providerId);
|
|
10667
|
+
if (!provider) {
|
|
10668
|
+
throw new Error(`Internal error: provider ${entry.providerId} is missing from the Claude App catalog.`);
|
|
10669
|
+
}
|
|
10670
|
+
const resolvedProvider = { ...provider, apiKey: entry.apiKey };
|
|
10671
|
+
convertedByKey.set(
|
|
10672
|
+
entryKey(entry),
|
|
10673
|
+
modelToServerModelInfo(entry.model, resolvedProvider)
|
|
10674
|
+
);
|
|
10675
|
+
}
|
|
10676
|
+
const { backendItems, backend } = await partitionAndStartCloudCodeBackend(
|
|
10677
|
+
cloudCodeEntries.map((entry) => ({
|
|
10678
|
+
providerId: entry.providerId,
|
|
10679
|
+
model: entry.model,
|
|
10680
|
+
apiKey: entry.apiKey,
|
|
10681
|
+
providerData: entry.providerData,
|
|
10682
|
+
entry
|
|
10683
|
+
})),
|
|
10684
|
+
(proxyRoute, cloudCodeBackend, original) => {
|
|
10685
|
+
const provider = providersById.get(original.providerId);
|
|
10686
|
+
if (!provider) {
|
|
10687
|
+
throw new Error(`Internal error: provider ${original.providerId} is missing from the Claude App catalog.`);
|
|
10688
|
+
}
|
|
10689
|
+
const converted = modelToServerModelInfo(original.model, {
|
|
10690
|
+
...provider,
|
|
10691
|
+
apiKey: original.apiKey
|
|
10692
|
+
}, {
|
|
10693
|
+
modelFormat: "anthropic",
|
|
10694
|
+
upstreamModelId: proxyRoute.aliasId,
|
|
10695
|
+
baseUrl: `http://127.0.0.1:${cloudCodeBackend.port}`,
|
|
10696
|
+
completionsUrl: void 0,
|
|
10697
|
+
npm: void 0,
|
|
10698
|
+
apiBaseUrl: void 0,
|
|
10699
|
+
apiKey: cloudCodeBackend.token,
|
|
10700
|
+
authType: void 0,
|
|
10701
|
+
oauthAccountId: void 0,
|
|
10702
|
+
headers: void 0
|
|
10703
|
+
});
|
|
10704
|
+
return { key: entryKey(original.entry), converted };
|
|
10705
|
+
},
|
|
10706
|
+
trace
|
|
10707
|
+
);
|
|
10708
|
+
for (const item of backendItems) {
|
|
10709
|
+
convertedByKey.set(item.key, item.converted);
|
|
10710
|
+
}
|
|
10711
|
+
const serverModels = entries.map((entry) => {
|
|
10712
|
+
const converted = convertedByKey.get(entryKey(entry));
|
|
10713
|
+
if (!converted) {
|
|
10714
|
+
throw new Error(`Internal error: model ${entry.providerId}/${entry.model.id} was not converted for Claude App.`);
|
|
10715
|
+
}
|
|
10716
|
+
return converted;
|
|
10717
|
+
});
|
|
10718
|
+
return { serverModels, backend };
|
|
10719
|
+
}
|
|
10720
|
+
|
|
10572
10721
|
// src/claude-desktop/app-session.ts
|
|
10573
|
-
import {
|
|
10574
|
-
|
|
10722
|
+
import {
|
|
10723
|
+
copyFileSync as copyFileSync3,
|
|
10724
|
+
existsSync as existsSync11,
|
|
10725
|
+
mkdirSync as mkdirSync6,
|
|
10726
|
+
readFileSync as readFileSync6,
|
|
10727
|
+
renameSync as renameSync2,
|
|
10728
|
+
rmSync as rmSync5,
|
|
10729
|
+
unlinkSync as unlinkSync2,
|
|
10730
|
+
writeFileSync as writeFileSync5
|
|
10731
|
+
} from "fs";
|
|
10732
|
+
import { dirname as dirname4, join as join12 } from "path";
|
|
10575
10733
|
function getSessionLockPath2() {
|
|
10576
10734
|
return join12(getClaudeDesktopHome(), ".relay-ai.lock");
|
|
10577
10735
|
}
|
|
10578
|
-
function
|
|
10736
|
+
function inspectSessionLock() {
|
|
10579
10737
|
const path2 = getSessionLockPath2();
|
|
10580
|
-
if (!existsSync11(path2)) return
|
|
10738
|
+
if (!existsSync11(path2)) return { status: "missing" };
|
|
10581
10739
|
try {
|
|
10582
10740
|
const parsed = JSON.parse(readFileSync6(path2, "utf8"));
|
|
10583
|
-
if (typeof parsed.pid === "number" && typeof parsed.startedAt === "string"
|
|
10741
|
+
if (typeof parsed.pid === "number" && typeof parsed.startedAt === "string" && typeof parsed.uuid === "string" && typeof parsed.proxyPort === "number") {
|
|
10742
|
+
return { status: "valid", lock: parsed };
|
|
10743
|
+
}
|
|
10584
10744
|
} catch {
|
|
10585
10745
|
}
|
|
10586
|
-
return
|
|
10746
|
+
return { status: "unreadable" };
|
|
10587
10747
|
}
|
|
10588
10748
|
function writeSessionLock2(lock) {
|
|
10589
10749
|
const path2 = getSessionLockPath2();
|
|
10590
|
-
|
|
10750
|
+
const tempPath = `${path2}.tmp.${process.pid}`;
|
|
10751
|
+
mkdirSync6(dirname4(path2), { recursive: true });
|
|
10752
|
+
try {
|
|
10753
|
+
writeFileSync5(tempPath, `${JSON.stringify(lock, null, 2)}
|
|
10591
10754
|
`, "utf8");
|
|
10755
|
+
renameSync2(tempPath, path2);
|
|
10756
|
+
} finally {
|
|
10757
|
+
try {
|
|
10758
|
+
rmSync5(tempPath, { force: true });
|
|
10759
|
+
} catch {
|
|
10760
|
+
}
|
|
10761
|
+
}
|
|
10592
10762
|
}
|
|
10593
10763
|
function isProcessAlive3(pid) {
|
|
10594
10764
|
if (pid <= 0) return false;
|
|
@@ -10602,7 +10772,7 @@ function isProcessAlive3(pid) {
|
|
|
10602
10772
|
function backupMetaJson() {
|
|
10603
10773
|
const metaPath = getMetaJsonPath();
|
|
10604
10774
|
const backupPath = `${metaPath}.bak`;
|
|
10605
|
-
if (existsSync11(metaPath)) {
|
|
10775
|
+
if (existsSync11(metaPath) && !existsSync11(backupPath)) {
|
|
10606
10776
|
copyFileSync3(metaPath, backupPath);
|
|
10607
10777
|
}
|
|
10608
10778
|
}
|
|
@@ -10624,20 +10794,36 @@ function removeRelayAiConfig(uuid) {
|
|
|
10624
10794
|
}
|
|
10625
10795
|
}
|
|
10626
10796
|
function hasStaleSession() {
|
|
10627
|
-
const
|
|
10628
|
-
if (
|
|
10629
|
-
|
|
10630
|
-
return true;
|
|
10631
|
-
}
|
|
10632
|
-
return false;
|
|
10797
|
+
const state = inspectSessionLock();
|
|
10798
|
+
if (state.status === "unreadable") return true;
|
|
10799
|
+
return state.status === "valid" && !isProcessAlive3(state.lock.pid);
|
|
10633
10800
|
}
|
|
10634
10801
|
function isConcurrentLiveSession() {
|
|
10635
|
-
const
|
|
10636
|
-
|
|
10637
|
-
|
|
10802
|
+
const state = inspectSessionLock();
|
|
10803
|
+
return state.status === "valid" && isProcessAlive3(state.lock.pid);
|
|
10804
|
+
}
|
|
10805
|
+
function lockHeldByAnotherLiveProcess(lock) {
|
|
10806
|
+
return lock !== null && lock.pid !== process.pid && isProcessAlive3(lock.pid);
|
|
10638
10807
|
}
|
|
10639
10808
|
function recoverSession() {
|
|
10640
|
-
const
|
|
10809
|
+
const state = inspectSessionLock();
|
|
10810
|
+
if (state.status === "unreadable") {
|
|
10811
|
+
restoreMetaJson();
|
|
10812
|
+
try {
|
|
10813
|
+
rmSync5(getSessionLockPath2(), { force: true });
|
|
10814
|
+
} catch {
|
|
10815
|
+
}
|
|
10816
|
+
return { recovered: true, message: "Cleared a corrupt claude-app session lock and restored shared config." };
|
|
10817
|
+
}
|
|
10818
|
+
const lock = state.status === "valid" ? state.lock : null;
|
|
10819
|
+
if (lockHeldByAnotherLiveProcess(lock)) {
|
|
10820
|
+
return {
|
|
10821
|
+
recovered: false,
|
|
10822
|
+
blocked: true,
|
|
10823
|
+
liveSession: true,
|
|
10824
|
+
message: `Another relay-ai claude-app session is running (pid ${lock.pid}). Ctrl+C it first, then run --restore.`
|
|
10825
|
+
};
|
|
10826
|
+
}
|
|
10641
10827
|
if (lock) {
|
|
10642
10828
|
restoreMetaJson();
|
|
10643
10829
|
removeRelayAiConfig(lock.uuid);
|
|
@@ -10648,6 +10834,7 @@ function recoverSession() {
|
|
|
10648
10834
|
} else {
|
|
10649
10835
|
restoreMetaJson();
|
|
10650
10836
|
}
|
|
10837
|
+
return { recovered: true, message: "Restored Claude Desktop relay-ai config." };
|
|
10651
10838
|
}
|
|
10652
10839
|
function waitForShutdown3() {
|
|
10653
10840
|
return new Promise((resolve2) => {
|
|
@@ -10668,11 +10855,20 @@ function waitForShutdown3() {
|
|
|
10668
10855
|
});
|
|
10669
10856
|
}
|
|
10670
10857
|
function cleanupSession(uuid) {
|
|
10671
|
-
|
|
10672
|
-
|
|
10673
|
-
|
|
10674
|
-
|
|
10675
|
-
|
|
10858
|
+
const state = inspectSessionLock();
|
|
10859
|
+
const lock = state.status === "valid" ? state.lock : null;
|
|
10860
|
+
const sharedStateIsOwnedElsewhere = lockHeldByAnotherLiveProcess(lock);
|
|
10861
|
+
if (!sharedStateIsOwnedElsewhere) {
|
|
10862
|
+
restoreMetaJson();
|
|
10863
|
+
try {
|
|
10864
|
+
rmSync5(getSessionLockPath2(), { force: true });
|
|
10865
|
+
} catch {
|
|
10866
|
+
}
|
|
10867
|
+
}
|
|
10868
|
+
const meta = readMetaJson();
|
|
10869
|
+
const configIsReferenced = meta === null ? existsSync11(getMetaJsonPath()) : meta.appliedId === uuid || meta.entries.some((entry) => entry.id === uuid);
|
|
10870
|
+
if (!sharedStateIsOwnedElsewhere || !configIsReferenced) {
|
|
10871
|
+
removeRelayAiConfig(uuid);
|
|
10676
10872
|
}
|
|
10677
10873
|
}
|
|
10678
10874
|
function setupExitCleanup(uuid) {
|
|
@@ -10680,6 +10876,10 @@ function setupExitCleanup(uuid) {
|
|
|
10680
10876
|
}
|
|
10681
10877
|
|
|
10682
10878
|
// src/claude-app.ts
|
|
10879
|
+
var CLAUDE_APP_GATEWAY_OPTIONS = {
|
|
10880
|
+
maskGatewayIds: true,
|
|
10881
|
+
longContextDisplay: "single-1m"
|
|
10882
|
+
};
|
|
10683
10883
|
function claudeAppHelpText() {
|
|
10684
10884
|
return `${pc11.bold("relay-ai claude-app")} \u2014 launch Claude Desktop app in 3P mode with your registry providers
|
|
10685
10885
|
|
|
@@ -10697,9 +10897,10 @@ ${pc11.bold("Options:")}
|
|
|
10697
10897
|
--version Show version
|
|
10698
10898
|
|
|
10699
10899
|
${pc11.bold("Description:")}
|
|
10700
|
-
Picks a provider and model from ~/.relay-ai/providers.json,
|
|
10701
|
-
|
|
10702
|
-
|
|
10900
|
+
Picks a provider and model from ~/.relay-ai/providers.json, combines the selected model
|
|
10901
|
+
with your available saved favorites, patches Claude Desktop config (with backup + restore
|
|
10902
|
+
on Ctrl+C), starts a local Responses proxy, and opens the Claude Desktop app.
|
|
10903
|
+
Keep this terminal open while using Claude.
|
|
10703
10904
|
|
|
10704
10905
|
${pc11.bold("Platforms:")}
|
|
10705
10906
|
macOS and Windows. Linux is not supported.
|
|
@@ -10712,42 +10913,15 @@ ${pc11.bold("Cleanup:")}
|
|
|
10712
10913
|
function providerForClaudePicker(provider) {
|
|
10713
10914
|
return { ...provider, models: routableModelsForProvider(provider, "claude-app") };
|
|
10714
10915
|
}
|
|
10715
|
-
function modelToServerModelInfo(model, provider, overrides = {}) {
|
|
10716
|
-
return {
|
|
10717
|
-
id: model.id,
|
|
10718
|
-
name: model.name,
|
|
10719
|
-
isFree: model.isFree ?? false,
|
|
10720
|
-
brand: model.brand ?? "",
|
|
10721
|
-
providerLabel: provider.name,
|
|
10722
|
-
providerId: provider.id,
|
|
10723
|
-
sourceBackend: provider.id,
|
|
10724
|
-
modelFormat: model.modelFormat,
|
|
10725
|
-
upstreamModelId: model.upstreamModelId,
|
|
10726
|
-
cost: model.cost,
|
|
10727
|
-
baseUrl: model.baseUrl,
|
|
10728
|
-
completionsUrl: model.completionsUrl,
|
|
10729
|
-
npm: model.npm,
|
|
10730
|
-
apiBaseUrl: model.apiBaseUrl,
|
|
10731
|
-
apiKey: provider.apiKey,
|
|
10732
|
-
authType: provider.authType,
|
|
10733
|
-
oauthAccountId: provider.oauthAccountId,
|
|
10734
|
-
contextWindow: model.contextWindow,
|
|
10735
|
-
supportedParameters: model.supportedParameters,
|
|
10736
|
-
reasoning: model.reasoning,
|
|
10737
|
-
interleavedReasoningField: model.interleavedReasoningField,
|
|
10738
|
-
headers: provider.headers,
|
|
10739
|
-
...overrides
|
|
10740
|
-
};
|
|
10741
|
-
}
|
|
10742
10916
|
async function runClaudeAppCommand(args, boot) {
|
|
10743
10917
|
if (args.includes("--help") || args.includes("-h")) {
|
|
10744
10918
|
console.log(claudeAppHelpText());
|
|
10745
10919
|
return 0;
|
|
10746
10920
|
}
|
|
10747
10921
|
if (args.includes("--restore")) {
|
|
10748
|
-
recoverSession();
|
|
10749
|
-
console.log(
|
|
10750
|
-
return 0;
|
|
10922
|
+
const result = recoverSession();
|
|
10923
|
+
console.log(result.message);
|
|
10924
|
+
return result.blocked || result.liveSession ? 1 : 0;
|
|
10751
10925
|
}
|
|
10752
10926
|
const trace = args.includes("--trace");
|
|
10753
10927
|
const debugLogPath = trace ? getProxyDebugLogPath() : void 0;
|
|
@@ -10812,87 +10986,54 @@ async function runClaudeAppCommand(args, boot) {
|
|
|
10812
10986
|
if (!pickedProvider) return 0;
|
|
10813
10987
|
if (pickedProvider === "__favorites__") {
|
|
10814
10988
|
useFavorites = true;
|
|
10989
|
+
const firstFavorite = resolveFirstAvailableFavorite(favorites, compatible);
|
|
10990
|
+
if (!firstFavorite) {
|
|
10991
|
+
p13.log.warn("No saved Claude App favorites are currently available.");
|
|
10992
|
+
return 0;
|
|
10993
|
+
}
|
|
10994
|
+
activeProvider = firstFavorite.provider;
|
|
10995
|
+
selectedModel = firstFavorite.model;
|
|
10815
10996
|
} else {
|
|
10816
10997
|
activeProvider = providerForClaudePicker(pickedProvider);
|
|
10817
10998
|
const pickedModel = await pickCodexModel(activeProvider, prefs);
|
|
10818
|
-
if (!pickedModel) return 0;
|
|
10999
|
+
if (!pickedModel || pickedModel === "back") return 0;
|
|
10819
11000
|
selectedModel = pickedModel;
|
|
10820
11001
|
}
|
|
10821
11002
|
}
|
|
10822
|
-
if (activeProvider) {
|
|
10823
|
-
|
|
10824
|
-
|
|
10825
|
-
p13.log.error(`No credential for ${activeProvider.name}. Run relay-ai providers auth ${activeProvider.id}.`);
|
|
10826
|
-
return 1;
|
|
10827
|
-
}
|
|
10828
|
-
activeProvider.apiKey = apiKey;
|
|
11003
|
+
if (!activeProvider || !selectedModel) {
|
|
11004
|
+
p13.log.error("No Claude App launch model was selected.");
|
|
11005
|
+
return 1;
|
|
10829
11006
|
}
|
|
10830
|
-
|
|
10831
|
-
|
|
10832
|
-
|
|
10833
|
-
|
|
10834
|
-
|
|
10835
|
-
|
|
10836
|
-
|
|
10837
|
-
|
|
10838
|
-
|
|
10839
|
-
|
|
10840
|
-
|
|
10841
|
-
|
|
10842
|
-
);
|
|
10843
|
-
|
|
10844
|
-
|
|
10845
|
-
|
|
10846
|
-
|
|
10847
|
-
model,
|
|
10848
|
-
antigravityProvider.apiKey,
|
|
10849
|
-
antigravityProvider.providerData ?? {}
|
|
10850
|
-
)
|
|
10851
|
-
);
|
|
10852
|
-
const startingAlias = cloudRoutes[0].aliasId;
|
|
10853
|
-
cloudCodeFavBackend = await startCloudCodeCatalogBackend(cloudRoutes, startingAlias, trace);
|
|
10854
|
-
const favBackend = cloudCodeFavBackend;
|
|
10855
|
-
cloudCodeServerModels = cloudCodeFavoriteModels.map((model) => modelToServerModelInfo(model, antigravityProvider, {
|
|
10856
|
-
isFree: false,
|
|
10857
|
-
providerId: "antigravity",
|
|
10858
|
-
sourceBackend: "antigravity",
|
|
10859
|
-
modelFormat: "anthropic",
|
|
10860
|
-
cost: void 0,
|
|
10861
|
-
baseUrl: `http://127.0.0.1:${favBackend.port}`,
|
|
10862
|
-
completionsUrl: void 0,
|
|
10863
|
-
npm: void 0,
|
|
10864
|
-
apiBaseUrl: void 0,
|
|
10865
|
-
apiKey: favBackend.token,
|
|
10866
|
-
authType: void 0,
|
|
10867
|
-
oauthAccountId: void 0,
|
|
10868
|
-
headers: void 0
|
|
10869
|
-
}));
|
|
10870
|
-
}
|
|
10871
|
-
const allModels = await loadServerModels();
|
|
10872
|
-
const regularServerModels = filterServerModelsByFavorites(allModels, regularFavorites);
|
|
10873
|
-
serverModels = [...cloudCodeServerModels, ...regularServerModels];
|
|
10874
|
-
} else if (selectedModel.modelFormat === "cloud-code") {
|
|
10875
|
-
const providerData = activeProvider.providerData ?? {};
|
|
10876
|
-
const cloudRoute = buildCloudCodeProxyRoute(selectedModel, activeProvider.apiKey, providerData);
|
|
10877
|
-
cloudCodeBackend = await startCloudCodeCatalogBackend([cloudRoute], cloudRoute.aliasId, trace);
|
|
10878
|
-
serverModels = [modelToServerModelInfo(selectedModel, activeProvider, {
|
|
10879
|
-
modelFormat: "anthropic",
|
|
10880
|
-
baseUrl: `http://127.0.0.1:${cloudCodeBackend.port}`,
|
|
10881
|
-
completionsUrl: void 0,
|
|
10882
|
-
npm: void 0,
|
|
10883
|
-
apiBaseUrl: void 0,
|
|
10884
|
-
apiKey: cloudCodeBackend.token,
|
|
10885
|
-
authType: void 0,
|
|
10886
|
-
oauthAccountId: void 0,
|
|
10887
|
-
headers: void 0
|
|
10888
|
-
})];
|
|
10889
|
-
} else {
|
|
10890
|
-
serverModels = [modelToServerModelInfo(selectedModel, activeProvider)];
|
|
11007
|
+
const catalogResolution = await resolveClaudeAppCatalog(
|
|
11008
|
+
activeProvider,
|
|
11009
|
+
selectedModel,
|
|
11010
|
+
compatible,
|
|
11011
|
+
favorites
|
|
11012
|
+
);
|
|
11013
|
+
if (!catalogResolution.ok) {
|
|
11014
|
+
p13.log.error(catalogResolution.error);
|
|
11015
|
+
return 1;
|
|
11016
|
+
}
|
|
11017
|
+
if (catalogResolution.droppedFavorites.length > 0) {
|
|
11018
|
+
const skipped = catalogResolution.droppedFavorites.map((favorite) => `${favorite.providerId}/${favorite.modelId}`).join(", ");
|
|
11019
|
+
p13.log.warn(`Skipped unavailable or unauthorized favorite(s): ${skipped}`);
|
|
11020
|
+
}
|
|
11021
|
+
if (catalogResolution.capacitySkippedFavorites.length > 0) {
|
|
11022
|
+
const skipped = catalogResolution.capacitySkippedFavorites.map((favorite) => `${favorite.providerId}/${favorite.modelId}`).join(", ");
|
|
11023
|
+
p13.log.warn(`Skipped favorite(s) beyond the 20-model catalog limit: ${skipped}`);
|
|
10891
11024
|
}
|
|
11025
|
+
let cloudCodeBackend = null;
|
|
10892
11026
|
let proxyHandle = null;
|
|
10893
11027
|
let sessionActive = false;
|
|
10894
11028
|
let uuid = "";
|
|
10895
11029
|
try {
|
|
11030
|
+
const builtCatalog = await buildClaudeAppServerCatalog(
|
|
11031
|
+
catalogResolution.entries,
|
|
11032
|
+
catalogResolution.providersById,
|
|
11033
|
+
trace
|
|
11034
|
+
);
|
|
11035
|
+
const serverModels = builtCatalog.serverModels;
|
|
11036
|
+
cloudCodeBackend = builtCatalog.backend;
|
|
10896
11037
|
backupMetaJson();
|
|
10897
11038
|
proxyHandle = await startServer({
|
|
10898
11039
|
host: "127.0.0.1",
|
|
@@ -10900,9 +11041,9 @@ async function runClaudeAppCommand(args, boot) {
|
|
|
10900
11041
|
// random port
|
|
10901
11042
|
apiKey: "dummy",
|
|
10902
11043
|
serverPassword: null,
|
|
10903
|
-
catalog: createGatewayModelCatalog(serverModels,
|
|
11044
|
+
catalog: createGatewayModelCatalog(serverModels, CLAUDE_APP_GATEWAY_OPTIONS),
|
|
10904
11045
|
backends: BACKENDS,
|
|
10905
|
-
gateway:
|
|
11046
|
+
gateway: CLAUDE_APP_GATEWAY_OPTIONS,
|
|
10906
11047
|
debugLogPath
|
|
10907
11048
|
});
|
|
10908
11049
|
uuid = writeRelayAiConfig(proxyHandle.port);
|
|
@@ -10932,11 +11073,10 @@ ${pc11.green("\u2714")} Proxy started on port ${proxyHandle.port}`);
|
|
|
10932
11073
|
}
|
|
10933
11074
|
console.log(`
|
|
10934
11075
|
${pc11.bold("Claude Desktop 3P Mode Active")}`);
|
|
10935
|
-
|
|
10936
|
-
|
|
10937
|
-
|
|
10938
|
-
console.log(`${pc11.dim("
|
|
10939
|
-
console.log(`${pc11.dim("Provider:")} ${activeProvider.name}`);
|
|
11076
|
+
console.log(`${pc11.dim("Model:")} ${selectedModel.id}`);
|
|
11077
|
+
console.log(`${pc11.dim("Provider:")} ${activeProvider.name}`);
|
|
11078
|
+
if (serverModels.length > 1) {
|
|
11079
|
+
console.log(`${pc11.dim("Catalog:")} ${serverModels.length} models (selected + favorites)`);
|
|
10940
11080
|
}
|
|
10941
11081
|
console.log(`${pc11.cyan("Press Ctrl+C to stop and restore config.")}`);
|
|
10942
11082
|
await waitForShutdown3();
|
|
@@ -10944,7 +11084,6 @@ ${pc11.bold("Claude Desktop 3P Mode Active")}`);
|
|
|
10944
11084
|
cleanupSession(uuid);
|
|
10945
11085
|
sessionActive = false;
|
|
10946
11086
|
if (cloudCodeBackend) cloudCodeBackend.handle.close();
|
|
10947
|
-
if (cloudCodeFavBackend) cloudCodeFavBackend.handle.close();
|
|
10948
11087
|
if (isClaudeAppRunning()) {
|
|
10949
11088
|
const shouldClose = await p13.confirm({ message: "Claude Desktop is still running. Close it?" });
|
|
10950
11089
|
if (shouldClose && !p13.isCancel(shouldClose)) {
|
|
@@ -10958,13 +11097,13 @@ ${pc11.bold("Claude Desktop 3P Mode Active")}`);
|
|
|
10958
11097
|
cleanupSession(uuid);
|
|
10959
11098
|
}
|
|
10960
11099
|
if (cloudCodeBackend) cloudCodeBackend.handle.close();
|
|
10961
|
-
|
|
11100
|
+
p13.log.error(String(err instanceof Error ? err.message : err));
|
|
10962
11101
|
return 1;
|
|
10963
11102
|
}
|
|
10964
11103
|
}
|
|
10965
11104
|
|
|
10966
11105
|
// src/ai-doc.ts
|
|
10967
|
-
import { existsSync as existsSync12, mkdirSync as
|
|
11106
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
|
|
10968
11107
|
import { homedir as homedir10 } from "os";
|
|
10969
11108
|
import { join as join13 } from "path";
|
|
10970
11109
|
var SKILL_DIR_NAME = "relay-ai-cli";
|
|
@@ -11496,7 +11635,7 @@ function installAiDoc(opts = {}) {
|
|
|
11496
11635
|
result.skipped.push(skillPath);
|
|
11497
11636
|
continue;
|
|
11498
11637
|
}
|
|
11499
|
-
|
|
11638
|
+
mkdirSync7(skillDir, { recursive: true });
|
|
11500
11639
|
writeFileSync6(skillPath, doc, "utf-8");
|
|
11501
11640
|
if (previous) {
|
|
11502
11641
|
result.updated.push({ path: skillPath, fromVersion: previous });
|
|
@@ -11622,17 +11761,18 @@ import { randomBytes, randomUUID as randomUUID3 } from "crypto";
|
|
|
11622
11761
|
import {
|
|
11623
11762
|
chmodSync,
|
|
11624
11763
|
existsSync as existsSync13,
|
|
11625
|
-
mkdirSync as
|
|
11764
|
+
mkdirSync as mkdirSync8,
|
|
11626
11765
|
readFileSync as readFileSync8,
|
|
11627
11766
|
readdirSync as readdirSync3,
|
|
11628
11767
|
rmSync as rmSync6,
|
|
11629
11768
|
statSync as statSync2,
|
|
11630
11769
|
writeFileSync as writeFileSync7
|
|
11631
11770
|
} from "fs";
|
|
11632
|
-
import { dirname as
|
|
11771
|
+
import { dirname as dirname5, join as join14, resolve } from "path";
|
|
11633
11772
|
import forge from "node-forge";
|
|
11634
11773
|
var SESSION_ROOT = "http-proxy-sessions";
|
|
11635
11774
|
var OWNER_FILE = "owner.pid";
|
|
11775
|
+
var MID_CREATION_GRACE_MS = 3e4;
|
|
11636
11776
|
function serialNumber() {
|
|
11637
11777
|
const bytes = randomBytes(16);
|
|
11638
11778
|
bytes[0] &= 127;
|
|
@@ -11650,24 +11790,40 @@ function processIsRunning(pid) {
|
|
|
11650
11790
|
function cleanupStaleHttpProxySessions(appHome = getAppHome()) {
|
|
11651
11791
|
const root = join14(appHome, SESSION_ROOT);
|
|
11652
11792
|
if (!existsSync13(root)) return;
|
|
11793
|
+
const now = Date.now();
|
|
11653
11794
|
for (const name of readdirSync3(root)) {
|
|
11654
11795
|
const sessionDir = join14(root, name);
|
|
11655
11796
|
try {
|
|
11656
|
-
|
|
11657
|
-
|
|
11797
|
+
const stat = statSync2(sessionDir);
|
|
11798
|
+
if (!stat.isDirectory()) continue;
|
|
11799
|
+
const ownerPath = join14(sessionDir, OWNER_FILE);
|
|
11800
|
+
if (!existsSync13(ownerPath)) {
|
|
11801
|
+
if (now - stat.mtimeMs > MID_CREATION_GRACE_MS) {
|
|
11802
|
+
rmSync6(sessionDir, { recursive: true, force: true });
|
|
11803
|
+
}
|
|
11804
|
+
continue;
|
|
11805
|
+
}
|
|
11806
|
+
const pid = Number(readFileSync8(ownerPath, "utf8").trim());
|
|
11807
|
+
if (!Number.isSafeInteger(pid) || pid <= 0) {
|
|
11808
|
+
const ownerStat = statSync2(ownerPath);
|
|
11809
|
+
const newestMtimeMs = Math.max(stat.mtimeMs, ownerStat.mtimeMs);
|
|
11810
|
+
if (now - newestMtimeMs > MID_CREATION_GRACE_MS) {
|
|
11811
|
+
rmSync6(sessionDir, { recursive: true, force: true });
|
|
11812
|
+
}
|
|
11813
|
+
continue;
|
|
11814
|
+
}
|
|
11658
11815
|
if (!processIsRunning(pid)) rmSync6(sessionDir, { recursive: true, force: true });
|
|
11659
11816
|
} catch {
|
|
11660
|
-
rmSync6(sessionDir, { recursive: true, force: true });
|
|
11661
11817
|
}
|
|
11662
11818
|
}
|
|
11663
11819
|
}
|
|
11664
11820
|
function createHttpProxyCertificates(appHome = getAppHome()) {
|
|
11665
11821
|
cleanupStaleHttpProxySessions(appHome);
|
|
11666
11822
|
const root = join14(appHome, SESSION_ROOT);
|
|
11667
|
-
|
|
11823
|
+
mkdirSync8(root, { recursive: true, mode: 448 });
|
|
11668
11824
|
chmodSync(root, 448);
|
|
11669
11825
|
const sessionDir = join14(root, randomUUID3());
|
|
11670
|
-
|
|
11826
|
+
mkdirSync8(sessionDir, { mode: 448 });
|
|
11671
11827
|
chmodSync(sessionDir, 448);
|
|
11672
11828
|
writeFileSync7(join14(sessionDir, OWNER_FILE), `${process.pid}
|
|
11673
11829
|
`, { mode: 384 });
|
|
@@ -11750,7 +11906,7 @@ function createHttpProxyCaBundle(relayCaCertPath, additionalCaCertPath) {
|
|
|
11750
11906
|
const relayCa = readFileSync8(relayCaCertPath, "utf8").trimEnd();
|
|
11751
11907
|
const additionalCa = readFileSync8(additionalCaCertPath, "utf8").trim();
|
|
11752
11908
|
if (!additionalCa) return relayCaCertPath;
|
|
11753
|
-
const combinedPath = join14(
|
|
11909
|
+
const combinedPath = join14(dirname5(relayCaCertPath), "combined-ca.pem");
|
|
11754
11910
|
writeFileSync7(
|
|
11755
11911
|
combinedPath,
|
|
11756
11912
|
`${relayCa}
|
|
@@ -13155,6 +13311,14 @@ Error: ${launchPlan.error}
|
|
|
13155
13311
|
return 1;
|
|
13156
13312
|
}
|
|
13157
13313
|
const switchMenuActive = favorites.length > 0 && !launchPlan.skip;
|
|
13314
|
+
if (!launchAllowsNonTty(launchPlan, httpProxyOnly) && !process.stdin.isTTY) {
|
|
13315
|
+
console.error(
|
|
13316
|
+
pc12.red(
|
|
13317
|
+
"relay-ai claude requires an interactive terminal (or use --provider and --model for non-interactive launch)."
|
|
13318
|
+
)
|
|
13319
|
+
);
|
|
13320
|
+
return 1;
|
|
13321
|
+
}
|
|
13158
13322
|
if (!agentStdout) relayIntro("Claude Code");
|
|
13159
13323
|
if (setup && !dryRun && !agentStdout) {
|
|
13160
13324
|
p14.log.info("Provider setup now lives in relay-ai providers \u2014 opening that next is recommended.");
|
|
@@ -13599,7 +13763,7 @@ Options:
|
|
|
13599
13763
|
--trace Write debug logs under ~/.relay-ai/logs/`);
|
|
13600
13764
|
return 0;
|
|
13601
13765
|
}
|
|
13602
|
-
const { runUiCommand } = await import("./ui-command-
|
|
13766
|
+
const { runUiCommand } = await import("./ui-command-3CWMSARO.js");
|
|
13603
13767
|
return runUiCommand({ trace: parsed.trace, serverMode: parsed.uiServerMode });
|
|
13604
13768
|
}
|
|
13605
13769
|
if (parsed.command === "models") {
|
|
@@ -13632,6 +13796,10 @@ Options:
|
|
|
13632
13796
|
console.log(VERSION);
|
|
13633
13797
|
return 0;
|
|
13634
13798
|
}
|
|
13799
|
+
if (parsed.showHelp) {
|
|
13800
|
+
console.log(codexAppHelpText());
|
|
13801
|
+
return 0;
|
|
13802
|
+
}
|
|
13635
13803
|
return runCodexAppCommand(parsed.claudeArgs, { vertex: parsed.vertex, launchProvider: parsed.launchProvider, launchModel: parsed.launchModel });
|
|
13636
13804
|
}
|
|
13637
13805
|
if (parsed.command === "claude-app") {
|
|
@@ -13639,6 +13807,10 @@ Options:
|
|
|
13639
13807
|
console.log(VERSION);
|
|
13640
13808
|
return 0;
|
|
13641
13809
|
}
|
|
13810
|
+
if (parsed.showHelp) {
|
|
13811
|
+
console.log(claudeAppHelpText());
|
|
13812
|
+
return 0;
|
|
13813
|
+
}
|
|
13642
13814
|
return runClaudeAppCommand(parsed.claudeArgs, { launchProvider: parsed.launchProvider, launchModel: parsed.launchModel });
|
|
13643
13815
|
}
|
|
13644
13816
|
if (parsed.command === "codex") {
|