@velum-labs/routekit-daemon 0.17.2 → 0.17.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/index.js +106 -12
- package/dist/test/daemon.test.js +105 -11
- package/package.json +9 -9
package/dist/index.js
CHANGED
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, rmSync } from "node:fs";
|
|
10
10
|
import { basename, dirname, join } from "node:path";
|
|
11
|
-
import { AccountActivityCoordinator, accountStoreEntries, CLIPROXY_API_KEY_ENV, CLIPROXY_BASE_URL_ENV, cliproxyAccountEntries, cliproxyAccountMatchesKind, cliproxyApiKey, cliproxyAuthDirectory, cliproxyBaseUrl, cliproxyCredentialValid, defaultSubscriptionAccountDirectory, RateLimitTracker, removeCliproxyAccount, removeSubscriptionAccount, renameSubscriptionAccount, sanitizeSubscriptionLabel, subscriptionAccountIdentity } from "@velum-labs/routekit-accounts";
|
|
11
|
+
import { AccountActivityCoordinator, AccountAuthCoordinator, accountStoreEntries, CLIPROXY_API_KEY_ENV, CLIPROXY_BASE_URL_ENV, cliproxyAccountEntries, cliproxyAccountMatchesKind, cliproxyApiKey, cliproxyAuthDirectory, cliproxyBaseUrl, cliproxyCredentialValid, defaultSubscriptionAccountDirectory, RateLimitTracker, removeCliproxyAccount, removeSubscriptionAccount, renameSubscriptionAccount, sanitizeSubscriptionLabel, subscriptionAccountIdentity, subscriptionCredentialFingerprint } from "@velum-labs/routekit-accounts";
|
|
12
12
|
import { configuredProviderIds, globalRouterConfigPath, parseRouterConfigDocument, routekitHome, writeRouterConfig } from "@velum-labs/routekit-config";
|
|
13
13
|
import { createRouteKitControlHandler, ROUTEKIT_CONTROL_CAPABILITY } from "@velum-labs/routekit-control";
|
|
14
|
-
import { resolveLeaderboardConfig, startSwitchingGatewayProxy } from "@velum-labs/routekit-gateway";
|
|
14
|
+
import { resolveCodexStartupModel, resolveLeaderboardConfig, startSwitchingGatewayProxy } from "@velum-labs/routekit-gateway";
|
|
15
15
|
import { accountKindForCliproxyAuthType, PROVIDERS, resolveAccountConnector } from "@velum-labs/routekit-registry";
|
|
16
16
|
import { startRouter } from "@velum-labs/routekit-router";
|
|
17
17
|
import { acquireLifecycleLock, CONTROL_PROTOCOL_VERSION, ControlClient, ControlError, createPortlessSession, createServiceRecordStore, createTokenStore, encodeJoinCredential, extendCleanupGrace, generateControlToken, nextServiceGeneration, processIdentity, registerCleanup, SERVICE_HOME_MODE, startControlServer, supervisorFromEnv, writeFileAtomic } from "@velum-labs/routekit-runtime";
|
|
@@ -276,6 +276,7 @@ export async function startRouteKitDaemon(options) {
|
|
|
276
276
|
let sidecarRef;
|
|
277
277
|
let activeRouter;
|
|
278
278
|
let accountActivity;
|
|
279
|
+
let accountAuth;
|
|
279
280
|
let daemonTelemetry;
|
|
280
281
|
let gatewayTelemetry;
|
|
281
282
|
let record;
|
|
@@ -352,6 +353,18 @@ export async function startRouteKitDaemon(options) {
|
|
|
352
353
|
accountActivity = new AccountActivityCoordinator({
|
|
353
354
|
statePath: join(home, "usage", "account-activity.v1.json")
|
|
354
355
|
});
|
|
356
|
+
mkdirSync(join(home, "subscriptions"), { recursive: true, mode: 0o700 });
|
|
357
|
+
accountAuth = new AccountAuthCoordinator({
|
|
358
|
+
statePath: join(home, "subscriptions", "account-auth.v1.json")
|
|
359
|
+
});
|
|
360
|
+
const activeCredentialFingerprints = () => new Map(accountStoreEntries(env).flatMap((entry) => entry.connector === "native"
|
|
361
|
+
? [
|
|
362
|
+
[
|
|
363
|
+
subscriptionAccountIdentity(entry.subscriptionKind, entry.label),
|
|
364
|
+
subscriptionCredentialFingerprint(entry.path)
|
|
365
|
+
]
|
|
366
|
+
]
|
|
367
|
+
: []));
|
|
355
368
|
const applyLeaderboardConfig = (config) => {
|
|
356
369
|
leaderboardConfig = resolveLeaderboardConfig(config);
|
|
357
370
|
callAttributions.configureBudget({
|
|
@@ -395,10 +408,12 @@ export async function startRouteKitDaemon(options) {
|
|
|
395
408
|
env: routerEnv(),
|
|
396
409
|
provenance,
|
|
397
410
|
activity: accountActivity,
|
|
411
|
+
authHealth: accountAuth,
|
|
398
412
|
drainGraceMs
|
|
399
413
|
});
|
|
400
414
|
await sidecar.reconcile(wantsCliproxySidecar(currentConfig));
|
|
401
415
|
activeRouter = await startGeneration(currentConfig);
|
|
416
|
+
accountAuth.reconcileActiveCredentials(activeCredentialFingerprints());
|
|
402
417
|
proxy = await startSwitchingGatewayProxy({
|
|
403
418
|
target: activeRouter.url,
|
|
404
419
|
host: options.host ?? "127.0.0.1",
|
|
@@ -471,6 +486,7 @@ export async function startRouteKitDaemon(options) {
|
|
|
471
486
|
currentDocument = input.write ? readFileSync(configPath, "utf8") : nextDocument;
|
|
472
487
|
revisions = nextRevisions;
|
|
473
488
|
applyLeaderboardConfig(currentConfig);
|
|
489
|
+
accountAuth?.reconcileActiveCredentials(activeCredentialFingerprints());
|
|
474
490
|
if (previousRouter !== undefined) {
|
|
475
491
|
try {
|
|
476
492
|
if (previousTarget !== undefined) {
|
|
@@ -625,7 +641,10 @@ export async function startRouteKitDaemon(options) {
|
|
|
625
641
|
models,
|
|
626
642
|
...(currentConfig.defaultModel !== undefined
|
|
627
643
|
? { defaultModel: currentConfig.defaultModel }
|
|
628
|
-
:
|
|
644
|
+
: typeof body.default_model === "string" &&
|
|
645
|
+
models.some((model) => model.id === body.default_model)
|
|
646
|
+
? { defaultModel: body.default_model }
|
|
647
|
+
: {}),
|
|
629
648
|
revision: revisions.config
|
|
630
649
|
};
|
|
631
650
|
writeSnapshot(home, "catalog", "models", {
|
|
@@ -745,6 +764,9 @@ export async function startRouteKitDaemon(options) {
|
|
|
745
764
|
label: entry.label,
|
|
746
765
|
connector: entry.connector,
|
|
747
766
|
credentialValid: member?.credentialValid ?? false,
|
|
767
|
+
...(member?.upstreamAuthState !== undefined
|
|
768
|
+
? { upstreamAuthState: member.upstreamAuthState }
|
|
769
|
+
: {}),
|
|
748
770
|
configured: currentConfig.providers[entry.subscriptionKind] !== undefined,
|
|
749
771
|
relayOpen: member?.relayReady === true &&
|
|
750
772
|
currentConfig.providers[entry.subscriptionKind] !== undefined,
|
|
@@ -936,8 +958,16 @@ export async function startRouteKitDaemon(options) {
|
|
|
936
958
|
const transaction = prepareAccountTransaction({
|
|
937
959
|
home,
|
|
938
960
|
configPath,
|
|
939
|
-
accountPaths:
|
|
940
|
-
|
|
961
|
+
accountPaths: [
|
|
962
|
+
...prepared.map((entry) => entry.path),
|
|
963
|
+
...(connector === "native"
|
|
964
|
+
? [join(home, "subscriptions", "account-auth.v1.json")]
|
|
965
|
+
: [])
|
|
966
|
+
],
|
|
967
|
+
accountRoots: [
|
|
968
|
+
...prepared.map((entry) => entry.directory),
|
|
969
|
+
...(connector === "native" ? [join(home, "subscriptions")] : [])
|
|
970
|
+
],
|
|
941
971
|
kind,
|
|
942
972
|
provider,
|
|
943
973
|
labels: prepared.map((entry) => entry.label)
|
|
@@ -957,6 +987,11 @@ export async function startRouteKitDaemon(options) {
|
|
|
957
987
|
configRevision: true,
|
|
958
988
|
accountRevision: true,
|
|
959
989
|
beforeSwap: async () => {
|
|
990
|
+
if (connector === "native") {
|
|
991
|
+
for (const entry of prepared) {
|
|
992
|
+
accountAuth.activateFingerprint(subscriptionAccountIdentity(kind, entry.label), subscriptionCredentialFingerprint(entry.path));
|
|
993
|
+
}
|
|
994
|
+
}
|
|
960
995
|
markAccountTransactionCommitted(transaction);
|
|
961
996
|
if (connector === "cliproxy")
|
|
962
997
|
await sidecar.refresh();
|
|
@@ -976,6 +1011,7 @@ export async function startRouteKitDaemon(options) {
|
|
|
976
1011
|
const rollbackFailures = [];
|
|
977
1012
|
try {
|
|
978
1013
|
rollbackAccountTransaction(transaction, home);
|
|
1014
|
+
accountAuth?.reload();
|
|
979
1015
|
}
|
|
980
1016
|
catch (rollbackError) {
|
|
981
1017
|
rollbackFailures.push(rollbackError);
|
|
@@ -1067,11 +1103,12 @@ export async function startRouteKitDaemon(options) {
|
|
|
1067
1103
|
const nextDocument = disableProvider ? stringifyYaml(raw) : currentDocument;
|
|
1068
1104
|
const nextConfig = disableProvider ? parseConfigDocument(nextDocument) : currentConfig;
|
|
1069
1105
|
const activityPath = join(home, "usage", "account-activity.v1.json");
|
|
1106
|
+
const authPath = join(home, "subscriptions", "account-auth.v1.json");
|
|
1070
1107
|
const transaction = prepareAccountTransaction({
|
|
1071
1108
|
home,
|
|
1072
1109
|
configPath,
|
|
1073
|
-
accountPaths: [nativePath, activityPath],
|
|
1074
|
-
accountRoots: [activeNativeDirectory, home],
|
|
1110
|
+
accountPaths: [nativePath, activityPath, authPath],
|
|
1111
|
+
accountRoots: [activeNativeDirectory, home, join(home, "subscriptions")],
|
|
1075
1112
|
kind: nativeKind,
|
|
1076
1113
|
provider: nativeKind,
|
|
1077
1114
|
labels: [params.label]
|
|
@@ -1091,6 +1128,7 @@ export async function startRouteKitDaemon(options) {
|
|
|
1091
1128
|
accountRevision: true,
|
|
1092
1129
|
beforeSwap: () => {
|
|
1093
1130
|
accountActivity.remove(subscriptionAccountIdentity(nativeKind, params.label));
|
|
1131
|
+
accountAuth.remove(subscriptionAccountIdentity(nativeKind, params.label));
|
|
1094
1132
|
markAccountTransactionCommitted(transaction);
|
|
1095
1133
|
}
|
|
1096
1134
|
});
|
|
@@ -1106,6 +1144,7 @@ export async function startRouteKitDaemon(options) {
|
|
|
1106
1144
|
try {
|
|
1107
1145
|
rollbackAccountTransaction(transaction, home);
|
|
1108
1146
|
accountActivity?.reload();
|
|
1147
|
+
accountAuth?.reload();
|
|
1109
1148
|
}
|
|
1110
1149
|
catch (rollbackError) {
|
|
1111
1150
|
rollbackFailures.push(rollbackError);
|
|
@@ -1175,6 +1214,7 @@ export async function startRouteKitDaemon(options) {
|
|
|
1175
1214
|
const targetPath = join(directory, `${params.target}.json`);
|
|
1176
1215
|
const trackerPath = join(directory, ".state.json");
|
|
1177
1216
|
const activityPath = join(home, "usage", "account-activity.v1.json");
|
|
1217
|
+
const authPath = join(home, "subscriptions", "account-auth.v1.json");
|
|
1178
1218
|
if (!existsSync(sourcePath)) {
|
|
1179
1219
|
throw new ControlError({
|
|
1180
1220
|
code: "not_found",
|
|
@@ -1200,8 +1240,8 @@ export async function startRouteKitDaemon(options) {
|
|
|
1200
1240
|
const transaction = prepareAccountTransaction({
|
|
1201
1241
|
home,
|
|
1202
1242
|
configPath,
|
|
1203
|
-
accountPaths: [sourcePath, targetPath, trackerPath, activityPath],
|
|
1204
|
-
accountRoots: [directory, home],
|
|
1243
|
+
accountPaths: [sourcePath, targetPath, trackerPath, activityPath, authPath],
|
|
1244
|
+
accountRoots: [directory, home, join(home, "subscriptions")],
|
|
1205
1245
|
kind,
|
|
1206
1246
|
provider: kind,
|
|
1207
1247
|
labels: [params.source, params.target]
|
|
@@ -1217,6 +1257,7 @@ export async function startRouteKitDaemon(options) {
|
|
|
1217
1257
|
accountRevision: true,
|
|
1218
1258
|
beforeSwap: () => {
|
|
1219
1259
|
accountActivity.rename(subscriptionAccountIdentity(kind, params.source), subscriptionAccountIdentity(kind, params.target));
|
|
1260
|
+
accountAuth.rename(subscriptionAccountIdentity(kind, params.source), subscriptionAccountIdentity(kind, params.target));
|
|
1220
1261
|
markAccountTransactionCommitted(transaction);
|
|
1221
1262
|
}
|
|
1222
1263
|
});
|
|
@@ -1232,6 +1273,7 @@ export async function startRouteKitDaemon(options) {
|
|
|
1232
1273
|
try {
|
|
1233
1274
|
rollbackAccountTransaction(transaction, home);
|
|
1234
1275
|
accountActivity?.reload();
|
|
1276
|
+
accountAuth?.reload();
|
|
1235
1277
|
}
|
|
1236
1278
|
catch (rollbackError) {
|
|
1237
1279
|
rollbackFailures.push(rollbackError);
|
|
@@ -1447,10 +1489,59 @@ export async function startRouteKitDaemon(options) {
|
|
|
1447
1489
|
},
|
|
1448
1490
|
"launcher.prepare": async (params, context) => {
|
|
1449
1491
|
const listed = await handlers["models.list"]({}, {
|
|
1450
|
-
signal:
|
|
1492
|
+
signal: context.signal,
|
|
1451
1493
|
requestId: "internal"
|
|
1452
1494
|
});
|
|
1453
|
-
|
|
1495
|
+
let model = params.model ?? listed.defaultModel ?? listed.models[0]?.id;
|
|
1496
|
+
let codexSelection;
|
|
1497
|
+
if (params.tool === "codex") {
|
|
1498
|
+
const candidates = listed.models.flatMap((entry) => {
|
|
1499
|
+
const info = activeRouter.modelInfo(entry.id);
|
|
1500
|
+
if (info === undefined)
|
|
1501
|
+
return [];
|
|
1502
|
+
return [{
|
|
1503
|
+
id: info.id,
|
|
1504
|
+
nativeId: info.nativeModel,
|
|
1505
|
+
provider: info.provider,
|
|
1506
|
+
billingScope: info.billingMode,
|
|
1507
|
+
...(info.createdAt !== undefined ? { createdAt: info.createdAt } : {}),
|
|
1508
|
+
...(info.providerPriority !== undefined
|
|
1509
|
+
? { providerPriority: info.providerPriority }
|
|
1510
|
+
: {}),
|
|
1511
|
+
...(info.metadata?.architecture !== undefined
|
|
1512
|
+
? { architecture: info.metadata.architecture }
|
|
1513
|
+
: {}),
|
|
1514
|
+
...(info.metadata?.supportedParameters !== undefined
|
|
1515
|
+
? { supportedParameters: info.metadata.supportedParameters }
|
|
1516
|
+
: {}),
|
|
1517
|
+
...(info.reasoning !== null ? { reasoning: info.reasoning } : {})
|
|
1518
|
+
}];
|
|
1519
|
+
});
|
|
1520
|
+
try {
|
|
1521
|
+
const selected = await resolveCodexStartupModel({
|
|
1522
|
+
models: candidates,
|
|
1523
|
+
...(listed.defaultModel !== undefined
|
|
1524
|
+
? { preferredModel: listed.defaultModel }
|
|
1525
|
+
: {}),
|
|
1526
|
+
...(params.model !== undefined ? { requestedModel: params.model } : {}),
|
|
1527
|
+
signal: context.signal
|
|
1528
|
+
});
|
|
1529
|
+
model = selected.model;
|
|
1530
|
+
codexSelection = {
|
|
1531
|
+
compatibleModelIds: [...selected.compatibleModelIds],
|
|
1532
|
+
models: [...selected.models]
|
|
1533
|
+
};
|
|
1534
|
+
}
|
|
1535
|
+
catch (error) {
|
|
1536
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1537
|
+
throw new ControlError({
|
|
1538
|
+
code: params.model !== undefined && message.startsWith("unknown model")
|
|
1539
|
+
? "not_found"
|
|
1540
|
+
: "unavailable",
|
|
1541
|
+
message
|
|
1542
|
+
});
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1454
1545
|
if (model === undefined || !listed.models.some((entry) => entry.id === model)) {
|
|
1455
1546
|
throw new ControlError({
|
|
1456
1547
|
code: "not_found",
|
|
@@ -1464,7 +1555,8 @@ export async function startRouteKitDaemon(options) {
|
|
|
1464
1555
|
model,
|
|
1465
1556
|
gatewayUrl: dataUrl,
|
|
1466
1557
|
authToken: dataTokenForPrincipal(tokens, dataTokenCache, dataAuth.token, context.principal),
|
|
1467
|
-
env: {}
|
|
1558
|
+
env: {},
|
|
1559
|
+
...(codexSelection !== undefined ? { codexSelection } : {})
|
|
1468
1560
|
};
|
|
1469
1561
|
},
|
|
1470
1562
|
"tokens.issue": async (params, context) => {
|
|
@@ -1652,6 +1744,7 @@ export async function startRouteKitDaemon(options) {
|
|
|
1652
1744
|
await proxy?.drain(drainGraceMs);
|
|
1653
1745
|
await activeRouter?.close();
|
|
1654
1746
|
accountActivity?.close();
|
|
1747
|
+
accountAuth?.close();
|
|
1655
1748
|
await sidecar.close();
|
|
1656
1749
|
await control?.close();
|
|
1657
1750
|
if (portless?.enabled)
|
|
@@ -1691,6 +1784,7 @@ export async function startRouteKitDaemon(options) {
|
|
|
1691
1784
|
await proxy?.close();
|
|
1692
1785
|
await activeRouter?.close();
|
|
1693
1786
|
accountActivity?.close();
|
|
1787
|
+
accountAuth?.close();
|
|
1694
1788
|
await sidecarRef?.close();
|
|
1695
1789
|
await control?.close();
|
|
1696
1790
|
if (portless?.enabled)
|
package/dist/test/daemon.test.js
CHANGED
|
@@ -10,19 +10,19 @@ import { ControlClient, ControlError, createServiceRecordStore } from "@velum-la
|
|
|
10
10
|
import { parse as parseYaml } from "yaml";
|
|
11
11
|
import { prepareAccountTransaction } from "../account-transaction.js";
|
|
12
12
|
import { startRouteKitDaemon } from "../index.js";
|
|
13
|
-
async function mockProvider(
|
|
13
|
+
async function mockProvider(models = [
|
|
14
|
+
{
|
|
15
|
+
id: "mock-model",
|
|
16
|
+
object: "model",
|
|
17
|
+
capabilities: { streaming: "supported", tools: "degraded" },
|
|
18
|
+
supported_reasoning_levels: ["high"]
|
|
19
|
+
}
|
|
20
|
+
]) {
|
|
14
21
|
const server = createServer((req, res) => {
|
|
15
22
|
if (req.url === "/v1/models") {
|
|
16
23
|
res.setHeader("content-type", "application/json");
|
|
17
24
|
res.end(JSON.stringify({
|
|
18
|
-
data:
|
|
19
|
-
{
|
|
20
|
-
id: "mock-model",
|
|
21
|
-
object: "model",
|
|
22
|
-
capabilities: { streaming: "supported", tools: "degraded" },
|
|
23
|
-
supported_reasoning_levels: ["high"]
|
|
24
|
-
}
|
|
25
|
-
]
|
|
25
|
+
data: models
|
|
26
26
|
}));
|
|
27
27
|
return;
|
|
28
28
|
}
|
|
@@ -339,6 +339,94 @@ test("singleton daemon exposes authenticated control and a stable reloadable dat
|
|
|
339
339
|
rmSync(root, { recursive: true, force: true });
|
|
340
340
|
}
|
|
341
341
|
});
|
|
342
|
+
test("local Codex preparation ranks an incompatible OpenAI default by native recency", async () => {
|
|
343
|
+
const root = mkdtempSync(join(tmpdir(), "routekit-daemon-codex-ranking-"));
|
|
344
|
+
const stateHome = join(root, "state");
|
|
345
|
+
const configPath = join(root, "router.yaml");
|
|
346
|
+
writeFileSync(configPath, "providers:\n openai: {}\ndefaultModel: openai/text-embedding-test\n");
|
|
347
|
+
const upstream = await mockProvider([
|
|
348
|
+
{ id: "text-embedding-test", object: "model", created: 50 },
|
|
349
|
+
{ id: "older-generation", object: "model", created: 100 },
|
|
350
|
+
{ id: "newer-generation", object: "model", created: 200 }
|
|
351
|
+
]);
|
|
352
|
+
const originalFetch = globalThis.fetch;
|
|
353
|
+
let openRouterFetches = 0;
|
|
354
|
+
globalThis.fetch = async (input, init) => {
|
|
355
|
+
const url = new URL(input instanceof Request ? input.url : input.toString());
|
|
356
|
+
if (url.hostname === "openrouter.ai") {
|
|
357
|
+
openRouterFetches += 1;
|
|
358
|
+
if (url.pathname === "/api/v1/models") {
|
|
359
|
+
return Response.json({
|
|
360
|
+
data: [
|
|
361
|
+
{
|
|
362
|
+
id: "openai/older-generation",
|
|
363
|
+
created: 900,
|
|
364
|
+
architecture: {
|
|
365
|
+
input_modalities: ["text"],
|
|
366
|
+
output_modalities: ["text"]
|
|
367
|
+
},
|
|
368
|
+
supported_parameters: ["tools"]
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
id: "openai/newer-generation",
|
|
372
|
+
created: 800,
|
|
373
|
+
architecture: {
|
|
374
|
+
input_modalities: ["text"],
|
|
375
|
+
output_modalities: ["text"]
|
|
376
|
+
},
|
|
377
|
+
supported_parameters: ["tools"]
|
|
378
|
+
}
|
|
379
|
+
]
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
if (url.pathname === "/api/v1/embeddings/models") {
|
|
383
|
+
return Response.json({ data: [{ id: "openai/text-embedding-test" }] });
|
|
384
|
+
}
|
|
385
|
+
return Response.json({ data: [] });
|
|
386
|
+
}
|
|
387
|
+
return await originalFetch(input, init);
|
|
388
|
+
};
|
|
389
|
+
let daemon;
|
|
390
|
+
try {
|
|
391
|
+
daemon = await startRouteKitDaemon({
|
|
392
|
+
packageVersion: "1.2.3",
|
|
393
|
+
stateHome,
|
|
394
|
+
configPath,
|
|
395
|
+
port: 0,
|
|
396
|
+
portless: false,
|
|
397
|
+
env: {
|
|
398
|
+
...process.env,
|
|
399
|
+
HOME: root,
|
|
400
|
+
ROUTEKIT_HOME: stateHome,
|
|
401
|
+
OPENAI_API_KEY: "test-key",
|
|
402
|
+
OPENAI_BASE_URL: upstream.url,
|
|
403
|
+
ROUTEKIT_PORTLESS: "0"
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
const client = new RouteKitControlClient({
|
|
407
|
+
url: daemon.record.url,
|
|
408
|
+
token: daemon.record.controlToken
|
|
409
|
+
});
|
|
410
|
+
const explicit = await client.call("launcher.prepare", {
|
|
411
|
+
tool: "codex",
|
|
412
|
+
model: "openai/older-generation"
|
|
413
|
+
});
|
|
414
|
+
assert.equal(explicit.model, "openai/older-generation");
|
|
415
|
+
assert.equal(openRouterFetches, 0);
|
|
416
|
+
const implicit = await client.call("launcher.prepare", { tool: "codex" });
|
|
417
|
+
assert.equal(implicit.model, "openai/newer-generation");
|
|
418
|
+
assert.equal(openRouterFetches, 4);
|
|
419
|
+
assert.equal(implicit.codexSelection?.models.find((model) => model.id === "openai/older-generation")?.createdAt, 100, "native OpenAI creation time wins over OpenRouter");
|
|
420
|
+
assert.equal(implicit.codexSelection?.models.find((model) => model.id === "openai/newer-generation")?.createdAt, 200);
|
|
421
|
+
}
|
|
422
|
+
finally {
|
|
423
|
+
globalThis.fetch = originalFetch;
|
|
424
|
+
if (daemon !== undefined)
|
|
425
|
+
await daemon.close();
|
|
426
|
+
await upstream.close();
|
|
427
|
+
rmSync(root, { recursive: true, force: true });
|
|
428
|
+
}
|
|
429
|
+
});
|
|
342
430
|
test("daemon account activity persists last selection independently of leaderboard rollups", async () => {
|
|
343
431
|
const root = mkdtempSync(join(tmpdir(), "routekit-daemon-activity-"));
|
|
344
432
|
const stateHome = join(root, "state");
|
|
@@ -518,6 +606,12 @@ test("cleared persisted cooldown remains absent and eligible after daemon reload
|
|
|
518
606
|
url: daemon.record.url,
|
|
519
607
|
token: daemon.record.controlToken
|
|
520
608
|
});
|
|
609
|
+
const prepared = await client.call("launcher.prepare", { tool: "codex" });
|
|
610
|
+
assert.equal(prepared.model, "codex/gpt-test-model");
|
|
611
|
+
assert.deepEqual(prepared.codexSelection?.compatibleModelIds, [
|
|
612
|
+
"codex/gpt-test-model"
|
|
613
|
+
]);
|
|
614
|
+
assert.deepEqual(prepared.codexSelection?.models[0]?.architecture?.outputModalities, ["text"]);
|
|
521
615
|
const before = await client.call("accounts.status", {});
|
|
522
616
|
assert.equal(before.accounts[0]?.relayOpen, false);
|
|
523
617
|
assert.deepEqual(before.accounts[0]?.readinessReasons, [
|
|
@@ -779,8 +873,8 @@ for (const kind of ["claude-code", "codex"]) {
|
|
|
779
873
|
});
|
|
780
874
|
assert.equal(doctor.checks.find((check) => check.name === "account/provider consistency")?.ok, true);
|
|
781
875
|
await assert.rejects(client.call("launcher.prepare", { tool: "codex" }), (error) => error instanceof ControlError &&
|
|
782
|
-
error.code === "
|
|
783
|
-
/no model
|
|
876
|
+
error.code === "unavailable" &&
|
|
877
|
+
/no advertised model with text output and tool support/.test(error.message));
|
|
784
878
|
assert.equal((await fetch(`${daemon.dataUrl}/health`)).status, 200);
|
|
785
879
|
const dataToken = readFileSync(daemon.record.authTokenFile, "utf8").trim();
|
|
786
880
|
const gatewayModels = await fetch(`${daemon.dataUrl}/v1/models`, {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velum-labs/routekit-daemon",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.17.
|
|
4
|
+
"version": "0.17.4",
|
|
5
5
|
"description": "Singleton RouteKit control daemon and stable model gateway.",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -35,14 +35,14 @@
|
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"posthog-node": "5.46.1",
|
|
37
37
|
"yaml": "2.9.0",
|
|
38
|
-
"@velum-labs/routekit-accounts": "0.17.
|
|
39
|
-
"@velum-labs/routekit-config": "0.17.
|
|
40
|
-
"@velum-labs/routekit-control": "0.17.
|
|
41
|
-
"@velum-labs/routekit-gateway": "0.17.
|
|
42
|
-
"@velum-labs/routekit-registry": "0.17.
|
|
43
|
-
"@velum-labs/routekit-router": "0.17.
|
|
44
|
-
"@velum-labs/routekit-runtime": "0.17.
|
|
45
|
-
"@velum-labs/routekit-telemetry-core": "0.17.
|
|
38
|
+
"@velum-labs/routekit-accounts": "0.17.4",
|
|
39
|
+
"@velum-labs/routekit-config": "0.17.4",
|
|
40
|
+
"@velum-labs/routekit-control": "0.17.4",
|
|
41
|
+
"@velum-labs/routekit-gateway": "0.17.4",
|
|
42
|
+
"@velum-labs/routekit-registry": "0.17.4",
|
|
43
|
+
"@velum-labs/routekit-router": "0.17.4",
|
|
44
|
+
"@velum-labs/routekit-runtime": "0.17.4",
|
|
45
|
+
"@velum-labs/routekit-telemetry-core": "0.17.4"
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
48
48
|
"build": "tsc -b",
|