@juspay/neurolink 12.15.0 → 12.16.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/CHANGELOG.md +2 -2
- package/dist/browser/neurolink.min.js +377 -377
- package/dist/cli/commands/auth.js +6 -2
- package/dist/cli/commands/proxy.d.ts +4 -0
- package/dist/cli/commands/proxy.js +119 -39
- package/dist/cli/index.js +8 -18
- package/dist/cli/lifecycle.d.ts +8 -0
- package/dist/cli/lifecycle.js +90 -0
- package/dist/constants/enums.d.ts +11 -0
- package/dist/constants/enums.js +12 -0
- package/dist/providers/catalog/api-route.json +130 -0
- package/dist/providers/catalog/index.generated.d.ts +1 -1
- package/dist/providers/catalog/index.generated.js +3 -0
- package/dist/providers/catalog/schema.d.ts +1 -1
- package/dist/proxy/codexAccountUsage.js +18 -6
- package/dist/proxy/codexFallback.js +17 -10
- package/dist/proxy/codexUsage.js +3 -0
- package/dist/proxy/proxyAnalysis.js +14 -11
- package/dist/proxy/proxyTokenUsage.d.ts +7 -0
- package/dist/proxy/proxyTokenUsage.js +21 -0
- package/dist/proxy/proxyTracer.d.ts +2 -0
- package/dist/proxy/proxyTracer.js +21 -40
- package/dist/proxy/quotaHeaders.js +7 -3
- package/dist/server/routes/claudeProxyRoutes.js +35 -1
- package/dist/server/routes/codexProxyRoutes.d.ts +5 -9
- package/dist/server/routes/codexProxyRoutes.js +106 -18
- package/dist/types/codex.d.ts +10 -0
- package/dist/types/providerCatalog.generated.d.ts +2 -2
- package/dist/types/providers.d.ts +4 -0
- package/dist/types/proxy.d.ts +24 -6
- package/dist/utils/pricing.js +4 -2
- package/docs-site/static/search-index.json +1 -1
- package/package.json +1 -1
|
@@ -248,8 +248,12 @@ function formatQuotaColumns(quota) {
|
|
|
248
248
|
return chalk.green(text);
|
|
249
249
|
};
|
|
250
250
|
return {
|
|
251
|
-
sessionText:
|
|
252
|
-
|
|
251
|
+
sessionText: quota.sessionStatus === "unknown"
|
|
252
|
+
? chalk.gray("unknown")
|
|
253
|
+
: colorize(sessionRemaining, `${sessionRemaining}% left`),
|
|
254
|
+
weeklyText: quota.weeklyStatus === "unknown"
|
|
255
|
+
? chalk.gray("unknown")
|
|
256
|
+
: colorize(weeklyRemaining, `${weeklyRemaining}% left`),
|
|
253
257
|
sessionReset: quota.sessionResetAt > 0
|
|
254
258
|
? chalk.gray(`resets ${formatTimeUntil(quota.sessionResetAt)}`)
|
|
255
259
|
: "",
|
|
@@ -82,6 +82,10 @@ export declare function createProxyStartApp(params: {
|
|
|
82
82
|
app: Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
|
|
83
83
|
readiness: ProxyReadinessState;
|
|
84
84
|
}>;
|
|
85
|
+
/** Reload only the ready serving worker. A candidate still starting must not
|
|
86
|
+
* receive SIGHUP before it has installed its handlers. It loads fresh config
|
|
87
|
+
* during startup; draining workers already hold their request snapshots. */
|
|
88
|
+
export declare function registerProxySupervisorReload(activeWorkerPid: () => number | undefined): () => void;
|
|
85
89
|
export declare const proxyStartCommand: CommandModule<object, ProxyStartArgs>;
|
|
86
90
|
export declare function sanitizeProxyStatusTerminalErrorMessage(message: string): string;
|
|
87
91
|
export declare const proxyStatusCommand: CommandModule<object, ProxyStatusArgs>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { initializeProxyOtelLogs, routeProxyConsoleToOtel, flushProxyOtelLogs, shutdownProxyOtelLogs, isProxyOtelOnly, withProxyOtelLogShutdown, } from "../../proxy/otelLogSink.js";
|
|
2
|
+
import { registerCliShutdownOwner } from "../lifecycle.js";
|
|
2
3
|
import { writeFileAtomic } from "../proxy-clients/snapshot.js";
|
|
3
4
|
import { spawn } from "node:child_process";
|
|
4
5
|
import { homedir } from "node:os";
|
|
@@ -60,6 +61,9 @@ const PROXY_TELEMETRY_SCRIPT_PATH = fileURLToPath(new URL("../../../scripts/obse
|
|
|
60
61
|
*/
|
|
61
62
|
const gatedShareRequests = new WeakSet();
|
|
62
63
|
const PROXY_LIFECYCLE_SHUTDOWN_TIMEOUT_MS = 5_000;
|
|
64
|
+
// Allow the normal 30-second worker drain plus startup settlement, leaving
|
|
65
|
+
// telemetry and scheduling headroom inside launchd's 45-second stop budget.
|
|
66
|
+
const PROXY_SUPERVISOR_DRAIN_TIMEOUT_MS = 35_000;
|
|
63
67
|
/** How long shutdown waits on the share listener before moving on. */
|
|
64
68
|
const SHARE_LISTENER_CLOSE_TIMEOUT_MS = 10_000;
|
|
65
69
|
const PROXY_STATUS_RECONCILE_TIMEOUT_MS = 750;
|
|
@@ -2504,10 +2508,12 @@ function registerProxyShutdownHandlers(params) {
|
|
|
2504
2508
|
}
|
|
2505
2509
|
try {
|
|
2506
2510
|
const { flushOpenTelemetry, shutdownOpenTelemetry } = await import("../../services/server/ai/observability/instrumentation.js");
|
|
2507
|
-
await
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
+
await withShutdownDeadline((async () => {
|
|
2512
|
+
await flushProxyOtelLogs();
|
|
2513
|
+
await shutdownProxyOtelLogs();
|
|
2514
|
+
await flushOpenTelemetry();
|
|
2515
|
+
await shutdownOpenTelemetry();
|
|
2516
|
+
})(), PROXY_LIFECYCLE_SHUTDOWN_TIMEOUT_MS, "telemetry shutdown");
|
|
2511
2517
|
}
|
|
2512
2518
|
catch {
|
|
2513
2519
|
// non-fatal — proxy shutdown must not block on OTel
|
|
@@ -2535,12 +2541,7 @@ function registerProxyShutdownHandlers(params) {
|
|
|
2535
2541
|
process.exit(1);
|
|
2536
2542
|
};
|
|
2537
2543
|
if (params.registerSignals ?? true) {
|
|
2538
|
-
|
|
2539
|
-
void shutdown("SIGTERM").catch(forceExitAfterShutdownFailure);
|
|
2540
|
-
});
|
|
2541
|
-
process.on("SIGINT", () => {
|
|
2542
|
-
void shutdown("SIGINT").catch(forceExitAfterShutdownFailure);
|
|
2543
|
-
});
|
|
2544
|
+
registerCliShutdownOwner((signal) => shutdown(signal).catch(forceExitAfterShutdownFailure));
|
|
2544
2545
|
}
|
|
2545
2546
|
return shutdown;
|
|
2546
2547
|
}
|
|
@@ -2866,10 +2867,81 @@ async function startProxyRuntime(params) {
|
|
|
2866
2867
|
* serving-process exits.
|
|
2867
2868
|
*/
|
|
2868
2869
|
async function runLaunchdProxySupervisor(argv, spinner) {
|
|
2870
|
+
let stopping = false;
|
|
2871
|
+
let activeWorkerPid;
|
|
2872
|
+
let rollingServerTask;
|
|
2873
|
+
let restartControlTask;
|
|
2874
|
+
let updaterSupervisor;
|
|
2875
|
+
let activatePendingUpdate;
|
|
2876
|
+
let otelInitialized = false;
|
|
2877
|
+
/** Own startup as well as serving: await resources already being created,
|
|
2878
|
+
* then drain each independently so one cleanup failure cannot skip the rest. */
|
|
2879
|
+
const shutdown = async (signal) => {
|
|
2880
|
+
if (stopping) {
|
|
2881
|
+
return;
|
|
2882
|
+
}
|
|
2883
|
+
stopping = true;
|
|
2884
|
+
logger.always(`[proxy-supervisor] shutting down (${signal})`);
|
|
2885
|
+
if (activatePendingUpdate) {
|
|
2886
|
+
process.off("SIGUSR2", activatePendingUpdate);
|
|
2887
|
+
}
|
|
2888
|
+
let exitCode = process.exitCode ?? 0;
|
|
2889
|
+
const reportCleanupFailure = (error) => {
|
|
2890
|
+
exitCode = 1;
|
|
2891
|
+
logger.warn(`[proxy-supervisor] cleanup failed: ${String(error)}`);
|
|
2892
|
+
};
|
|
2893
|
+
const cleanup = async (operation, timeoutMs = PROXY_LIFECYCLE_SHUTDOWN_TIMEOUT_MS, timeoutMessage = "supervisor resource shutdown deadline exceeded") => {
|
|
2894
|
+
try {
|
|
2895
|
+
await withTimeout(Promise.resolve().then(operation), timeoutMs, timeoutMessage);
|
|
2896
|
+
}
|
|
2897
|
+
catch (error) {
|
|
2898
|
+
reportCleanupFailure(error);
|
|
2899
|
+
}
|
|
2900
|
+
};
|
|
2901
|
+
// Close independently: a stalled control must not delay the worker drain.
|
|
2902
|
+
// The operations retain their continuations after a deadline, so a resource
|
|
2903
|
+
// returned during telemetry cleanup is still closed without extending exit.
|
|
2904
|
+
await Promise.all([
|
|
2905
|
+
cleanup(async () => {
|
|
2906
|
+
// Setup errors are already reported by the control-plane startup path.
|
|
2907
|
+
const control = await restartControlTask?.catch(() => undefined);
|
|
2908
|
+
await control?.close();
|
|
2909
|
+
}),
|
|
2910
|
+
cleanup(() => updaterSupervisor?.stop()),
|
|
2911
|
+
cleanup(async () => {
|
|
2912
|
+
const server = await rollingServerTask;
|
|
2913
|
+
await server?.close();
|
|
2914
|
+
}, PROXY_SUPERVISOR_DRAIN_TIMEOUT_MS),
|
|
2915
|
+
]);
|
|
2916
|
+
if (otelInitialized) {
|
|
2917
|
+
await cleanup(async () => {
|
|
2918
|
+
await flushProxyLifecycleEvents().catch(reportCleanupFailure);
|
|
2919
|
+
await flushProxyOtelLogs().catch(reportCleanupFailure);
|
|
2920
|
+
await shutdownProxyOtelLogs();
|
|
2921
|
+
}, PROXY_LIFECYCLE_SHUTDOWN_TIMEOUT_MS, "supervisor telemetry shutdown deadline exceeded");
|
|
2922
|
+
}
|
|
2923
|
+
await cleanup(() => {
|
|
2924
|
+
const supervisorState = loadProxySupervisorState();
|
|
2925
|
+
if (supervisorState?.pid === process.pid) {
|
|
2926
|
+
clearProxySupervisorState();
|
|
2927
|
+
}
|
|
2928
|
+
});
|
|
2929
|
+
process.exit(exitCode);
|
|
2930
|
+
};
|
|
2931
|
+
registerCliShutdownOwner(shutdown);
|
|
2932
|
+
// Protect every startup await; a worker loads fresh config before readiness.
|
|
2933
|
+
registerProxySupervisorReload(() => stopping ? undefined : activeWorkerPid?.());
|
|
2869
2934
|
await loadProxyStartEnv(argv, spinner);
|
|
2935
|
+
if (stopping) {
|
|
2936
|
+
return;
|
|
2937
|
+
}
|
|
2870
2938
|
initializeProxyOtelLogs("supervisor");
|
|
2939
|
+
otelInitialized = true;
|
|
2871
2940
|
routeProxyConsoleToOtel();
|
|
2872
2941
|
await ensureProxyStartAllowed(spinner);
|
|
2942
|
+
if (stopping) {
|
|
2943
|
+
return;
|
|
2944
|
+
}
|
|
2873
2945
|
const entryScript = process.argv[1];
|
|
2874
2946
|
if (!entryScript) {
|
|
2875
2947
|
throw new Error("proxy supervisor cannot resolve the CLI entry script");
|
|
@@ -2885,7 +2957,7 @@ async function runLaunchdProxySupervisor(argv, spinner) {
|
|
|
2885
2957
|
});
|
|
2886
2958
|
let currentUpdaterPid;
|
|
2887
2959
|
let restartControl;
|
|
2888
|
-
|
|
2960
|
+
rollingServerTask = startRollingProxyServer({
|
|
2889
2961
|
onEvent: (event) => logProxyLifecycleEvent({
|
|
2890
2962
|
event: "supervisor_event",
|
|
2891
2963
|
requestId: "-",
|
|
@@ -2920,9 +2992,20 @@ async function runLaunchdProxySupervisor(argv, spinner) {
|
|
|
2920
2992
|
},
|
|
2921
2993
|
log: (message) => logger.always(message),
|
|
2922
2994
|
});
|
|
2995
|
+
// Retain the creation promise before awaiting it so shutdown owns late results.
|
|
2996
|
+
const rollingServer = await rollingServerTask.catch((error) => {
|
|
2997
|
+
if (!stopping) {
|
|
2998
|
+
throw error;
|
|
2999
|
+
}
|
|
3000
|
+
return undefined;
|
|
3001
|
+
});
|
|
3002
|
+
if (stopping || !rollingServer) {
|
|
3003
|
+
return;
|
|
3004
|
+
}
|
|
3005
|
+
activeWorkerPid = () => rollingServer.snapshot().active?.pid;
|
|
2923
3006
|
let rollingReplacement = null;
|
|
2924
|
-
|
|
2925
|
-
if (rollingReplacement) {
|
|
3007
|
+
activatePendingUpdate = () => {
|
|
3008
|
+
if (stopping || rollingReplacement) {
|
|
2926
3009
|
return;
|
|
2927
3010
|
}
|
|
2928
3011
|
let pendingVersion;
|
|
@@ -2953,7 +3036,7 @@ async function runLaunchdProxySupervisor(argv, spinner) {
|
|
|
2953
3036
|
process.on("SIGUSR2", activatePendingUpdate);
|
|
2954
3037
|
const readinessHost = host === "0.0.0.0" ? "127.0.0.1" : host;
|
|
2955
3038
|
try {
|
|
2956
|
-
|
|
3039
|
+
restartControlTask = startProxyRestartControl({
|
|
2957
3040
|
stateDir: join(homedir(), ".neurolink"),
|
|
2958
3041
|
server: rollingServer,
|
|
2959
3042
|
getTelemetry: getProxyProcessTelemetry,
|
|
@@ -2977,11 +3060,15 @@ async function runLaunchdProxySupervisor(argv, spinner) {
|
|
|
2977
3060
|
return response.json();
|
|
2978
3061
|
},
|
|
2979
3062
|
});
|
|
3063
|
+
restartControl = await restartControlTask;
|
|
2980
3064
|
}
|
|
2981
3065
|
catch (error) {
|
|
2982
3066
|
// A control-plane setup failure must not stop an otherwise serving proxy.
|
|
2983
3067
|
logger.warn(`[proxy-supervisor] safe restart control unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
2984
3068
|
}
|
|
3069
|
+
if (stopping) {
|
|
3070
|
+
return;
|
|
3071
|
+
}
|
|
2985
3072
|
const updatePersistedUpdaterPid = (updaterPid) => {
|
|
2986
3073
|
currentUpdaterPid = updaterPid;
|
|
2987
3074
|
const state = loadProxySupervisorState();
|
|
@@ -2994,7 +3081,7 @@ async function runLaunchdProxySupervisor(argv, spinner) {
|
|
|
2994
3081
|
restartControl: restartControl?.identity,
|
|
2995
3082
|
});
|
|
2996
3083
|
};
|
|
2997
|
-
|
|
3084
|
+
updaterSupervisor = startUpdaterWorkerSupervisor({
|
|
2998
3085
|
spawnWorker: () => spawnProxyUpdater(readinessHost, rollingServer.address.port, process.pid, true),
|
|
2999
3086
|
isProcessRunning,
|
|
3000
3087
|
stopWorker: (pid) => process.kill(pid, "SIGTERM"),
|
|
@@ -3006,34 +3093,27 @@ async function runLaunchdProxySupervisor(argv, spinner) {
|
|
|
3006
3093
|
spinner.succeed(chalk.green("Claude proxy supervisor started successfully"));
|
|
3007
3094
|
}
|
|
3008
3095
|
logger.always(`[proxy-supervisor] listening on ${host}:${rollingServer.address.port} workerPid=${rollingServer.snapshot().active?.pid ?? "unknown"} version=${PROXY_VERSION}`);
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3096
|
+
await new Promise(() => undefined);
|
|
3097
|
+
}
|
|
3098
|
+
/** Reload only the ready serving worker. A candidate still starting must not
|
|
3099
|
+
* receive SIGHUP before it has installed its handlers. It loads fresh config
|
|
3100
|
+
* during startup; draining workers already hold their request snapshots. */
|
|
3101
|
+
export function registerProxySupervisorReload(activeWorkerPid) {
|
|
3102
|
+
const reload = () => {
|
|
3103
|
+
const pid = activeWorkerPid();
|
|
3104
|
+
if (!pid) {
|
|
3013
3105
|
return;
|
|
3014
3106
|
}
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
await flushProxyLifecycleEvents().catch((error) => logger.warn(String(error)));
|
|
3022
|
-
await flushProxyOtelLogs().catch(() => undefined);
|
|
3023
|
-
await shutdownProxyOtelLogs().catch(() => undefined);
|
|
3024
|
-
const supervisorState = loadProxySupervisorState();
|
|
3025
|
-
if (supervisorState?.pid === process.pid) {
|
|
3026
|
-
clearProxySupervisorState();
|
|
3107
|
+
try {
|
|
3108
|
+
process.kill(pid, "SIGHUP");
|
|
3109
|
+
logger.always(`[proxy-supervisor] forwarded configuration reload to worker pid=${pid}`);
|
|
3110
|
+
}
|
|
3111
|
+
catch (error) {
|
|
3112
|
+
logger.warn(`[proxy-supervisor] configuration reload failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
3027
3113
|
}
|
|
3028
|
-
process.exit(0);
|
|
3029
3114
|
};
|
|
3030
|
-
process.
|
|
3031
|
-
|
|
3032
|
-
});
|
|
3033
|
-
process.once("SIGINT", () => {
|
|
3034
|
-
void shutdown("SIGINT");
|
|
3035
|
-
});
|
|
3036
|
-
await new Promise(() => undefined);
|
|
3115
|
+
process.on("SIGHUP", reload);
|
|
3116
|
+
return () => process.off("SIGHUP", reload);
|
|
3037
3117
|
}
|
|
3038
3118
|
async function startProxyCommandHandler(argv) {
|
|
3039
3119
|
const spinner = argv.quiet ? null : ora("Starting Claude proxy...").start();
|
package/dist/cli/index.js
CHANGED
|
@@ -9,6 +9,7 @@ process.env.DOTENV_CONFIG_QUIET = "true";
|
|
|
9
9
|
*/
|
|
10
10
|
import { initializeCliParser } from "./parser.js";
|
|
11
11
|
import chalk from "chalk";
|
|
12
|
+
import { initializeCliLifecycle, cleanupCliLifecycle } from "./lifecycle.js";
|
|
12
13
|
// Clean up pnpm-specific environment variables that cause npm warnings
|
|
13
14
|
// These variables are set by pnpm but cause "Unknown env config" warnings in npm
|
|
14
15
|
if (process.env.npm_config_verify_deps_before_run) {
|
|
@@ -30,34 +31,23 @@ catch {
|
|
|
30
31
|
}
|
|
31
32
|
// Enhanced CLI with Professional UX
|
|
32
33
|
const cli = initializeCliParser();
|
|
34
|
+
initializeCliLifecycle(async () => {
|
|
35
|
+
const { flushOpenTelemetry } = await import("../services/server/ai/observability/instrumentation.js");
|
|
36
|
+
await flushOpenTelemetry();
|
|
37
|
+
});
|
|
33
38
|
// Execute CLI
|
|
34
39
|
(async () => {
|
|
35
40
|
try {
|
|
36
41
|
// Parse and execute commands
|
|
37
42
|
await cli.parse();
|
|
38
|
-
await
|
|
43
|
+
await cleanupCliLifecycle();
|
|
39
44
|
}
|
|
40
45
|
catch (error) {
|
|
41
46
|
// Global error handler - should not reach here due to fail() handler
|
|
42
47
|
process.stderr.write(chalk.red(`Unexpected CLI _error: ${error.message}\n`));
|
|
43
|
-
|
|
48
|
+
process.exitCode = 1;
|
|
49
|
+
await cleanupCliLifecycle();
|
|
44
50
|
process.exit(1);
|
|
45
51
|
}
|
|
46
52
|
})();
|
|
47
|
-
// Cleanup on exit
|
|
48
|
-
process.on("SIGINT", async () => {
|
|
49
|
-
await cleanup();
|
|
50
|
-
process.exit(0);
|
|
51
|
-
});
|
|
52
|
-
process.on("SIGTERM", async () => {
|
|
53
|
-
await cleanup();
|
|
54
|
-
process.exit(0);
|
|
55
|
-
});
|
|
56
|
-
process.on("beforeExit", async () => {
|
|
57
|
-
await cleanup();
|
|
58
|
-
});
|
|
59
|
-
async function cleanup() {
|
|
60
|
-
const { flushOpenTelemetry } = await import("../services/server/ai/observability/instrumentation.js");
|
|
61
|
-
await flushOpenTelemetry();
|
|
62
|
-
}
|
|
63
53
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Install ordinary-command cleanup and its maximum flush duration. */
|
|
2
|
+
export declare function initializeCliLifecycle(cleanup: () => Promise<void>, timeoutMs?: number): void;
|
|
3
|
+
/** Delegate signals to a long-lived command that owns draining and exit. */
|
|
4
|
+
export declare function registerCliShutdownOwner(handler: (signal: NodeJS.Signals) => Promise<void>): void;
|
|
5
|
+
/** Flush ordinary commands once; an active shutdown owner manages its own cleanup. */
|
|
6
|
+
export declare function cleanupCliLifecycle(): Promise<void>;
|
|
7
|
+
/** Bound best-effort cleanup without allowing rejection to escape a signal handler. */
|
|
8
|
+
export declare function runBoundedCliCleanup(cleanup: () => Promise<unknown>, timeoutMs?: number, onTimeout?: () => void): Promise<void>;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/** One signal owner per CLI process. Proxy lifecycles own their drain and exit;
|
|
2
|
+
* ordinary commands only need a bounded, best-effort telemetry flush. */
|
|
3
|
+
let owner;
|
|
4
|
+
let cleanupTask;
|
|
5
|
+
let cleanupPromise;
|
|
6
|
+
let shutdownPromise;
|
|
7
|
+
let installed = false;
|
|
8
|
+
let cleanupTimeoutMs = 5_000;
|
|
9
|
+
/** Install ordinary-command cleanup and its maximum flush duration. */
|
|
10
|
+
export function initializeCliLifecycle(cleanup, timeoutMs = 5_000) {
|
|
11
|
+
cleanupTask = cleanup;
|
|
12
|
+
cleanupTimeoutMs = timeoutMs;
|
|
13
|
+
installSignals();
|
|
14
|
+
}
|
|
15
|
+
/** Delegate signals to a long-lived command that owns draining and exit. */
|
|
16
|
+
export function registerCliShutdownOwner(handler) {
|
|
17
|
+
owner = handler;
|
|
18
|
+
installSignals();
|
|
19
|
+
}
|
|
20
|
+
/** Flush ordinary commands once; an active shutdown owner manages its own cleanup. */
|
|
21
|
+
export function cleanupCliLifecycle() {
|
|
22
|
+
if (owner) {
|
|
23
|
+
return Promise.resolve();
|
|
24
|
+
}
|
|
25
|
+
cleanupPromise ??= runBoundedCliCleanup(() => cleanupTask?.() ?? Promise.resolve(), cleanupTimeoutMs, () => {
|
|
26
|
+
// A stalled exporter may retain handles after Promise.race settles.
|
|
27
|
+
// A newly registered owner still retains control of draining and exit.
|
|
28
|
+
if (!owner) {
|
|
29
|
+
process.exit(process.exitCode ?? 0);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
return cleanupPromise;
|
|
33
|
+
}
|
|
34
|
+
/** Bound best-effort cleanup without allowing rejection to escape a signal handler. */
|
|
35
|
+
export async function runBoundedCliCleanup(cleanup, timeoutMs = 5_000, onTimeout) {
|
|
36
|
+
let timer;
|
|
37
|
+
let timedOut = false;
|
|
38
|
+
try {
|
|
39
|
+
await Promise.race([
|
|
40
|
+
Promise.resolve().then(cleanup),
|
|
41
|
+
new Promise((_, reject) => {
|
|
42
|
+
timer = setTimeout(() => {
|
|
43
|
+
timedOut = true;
|
|
44
|
+
reject(new Error("telemetry flush deadline exceeded"));
|
|
45
|
+
}, timeoutMs);
|
|
46
|
+
}),
|
|
47
|
+
]);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
process.stderr.write(`[cli] cleanup failed: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
clearTimeout(timer);
|
|
54
|
+
}
|
|
55
|
+
if (timedOut) {
|
|
56
|
+
onTimeout?.();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** Install one process-wide signal dispatcher and reuse the first shutdown promise. */
|
|
60
|
+
function installSignals() {
|
|
61
|
+
if (installed) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
installed = true;
|
|
65
|
+
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
66
|
+
process.on(signal, () => {
|
|
67
|
+
shutdownPromise ??= Promise.resolve()
|
|
68
|
+
.then(async () => {
|
|
69
|
+
if (!owner) {
|
|
70
|
+
await cleanupCliLifecycle();
|
|
71
|
+
}
|
|
72
|
+
// Startup may register the proxy owner while the flush is pending.
|
|
73
|
+
if (owner) {
|
|
74
|
+
await owner(signal);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
process.exit(process.exitCode ?? 0);
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
.catch((error) => {
|
|
81
|
+
process.stderr.write(`[cli] shutdown failed: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
process.once("beforeExit", () => {
|
|
87
|
+
void cleanupCliLifecycle();
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=lifecycle.js.map
|
|
@@ -18,6 +18,7 @@ export declare enum AIProviderName {
|
|
|
18
18
|
NVIDIA_NIM = "nvidia-nim",
|
|
19
19
|
LM_STUDIO = "lm-studio",
|
|
20
20
|
LLAMACPP = "llamacpp",
|
|
21
|
+
API_ROUTE = "api-route",
|
|
21
22
|
BASETEN = "baseten",
|
|
22
23
|
CEREBRAS = "cerebras",
|
|
23
24
|
CLOUDFLARE = "cloudflare",
|
|
@@ -816,6 +817,16 @@ export declare enum ReplicateModels {
|
|
|
816
817
|
* authentication failures during ongoing operations
|
|
817
818
|
*/
|
|
818
819
|
export declare const TOKEN_EXPIRY_BUFFER_MS: number;
|
|
820
|
+
export declare enum ApiRouteModels {
|
|
821
|
+
CLAUDE_SONNET_4_6 = "claude-sonnet-4-6",
|
|
822
|
+
CLAUDE_HAIKU_4_5 = "claude-haiku-4-5",
|
|
823
|
+
DEEPSEEK_V4_FLASH = "deepseek-v4-flash",
|
|
824
|
+
DEEPSEEK_V4_PRO = "deepseek-v4-pro",
|
|
825
|
+
GEMINI_3_8_FLASH = "gemini-3.8-flash",
|
|
826
|
+
QWEN3_8_FLASH = "qwen3.8-flash",
|
|
827
|
+
KIMI_K2_7_CODE = "kimi-k2.7-code",
|
|
828
|
+
GLM_5_3_FLASH = "glm-5.3-flash"
|
|
829
|
+
}
|
|
819
830
|
export declare enum BasetenModels {
|
|
820
831
|
OPENAI_GPT_OSS_120B = "openai/gpt-oss-120b",
|
|
821
832
|
ZAI_ORG_GLM_4_7 = "zai-org/GLM-4.7",
|
package/dist/constants/enums.js
CHANGED
|
@@ -23,6 +23,7 @@ export var AIProviderName;
|
|
|
23
23
|
AIProviderName["LM_STUDIO"] = "lm-studio";
|
|
24
24
|
AIProviderName["LLAMACPP"] = "llamacpp";
|
|
25
25
|
// ── BEGIN GENERATED(provider-members): provider catalog (pnpm run codegen:catalog) ──
|
|
26
|
+
AIProviderName["API_ROUTE"] = "api-route";
|
|
26
27
|
AIProviderName["BASETEN"] = "baseten";
|
|
27
28
|
AIProviderName["CEREBRAS"] = "cerebras";
|
|
28
29
|
AIProviderName["CLOUDFLARE"] = "cloudflare";
|
|
@@ -1070,6 +1071,17 @@ export var ReplicateModels;
|
|
|
1070
1071
|
*/
|
|
1071
1072
|
export const TOKEN_EXPIRY_BUFFER_MS = 5 * 60 * 1000; // 5 minutes
|
|
1072
1073
|
// ── BEGIN GENERATED(models-enums): provider catalog (pnpm run codegen:catalog) ──
|
|
1074
|
+
export var ApiRouteModels;
|
|
1075
|
+
(function (ApiRouteModels) {
|
|
1076
|
+
ApiRouteModels["CLAUDE_SONNET_4_6"] = "claude-sonnet-4-6";
|
|
1077
|
+
ApiRouteModels["CLAUDE_HAIKU_4_5"] = "claude-haiku-4-5";
|
|
1078
|
+
ApiRouteModels["DEEPSEEK_V4_FLASH"] = "deepseek-v4-flash";
|
|
1079
|
+
ApiRouteModels["DEEPSEEK_V4_PRO"] = "deepseek-v4-pro";
|
|
1080
|
+
ApiRouteModels["GEMINI_3_8_FLASH"] = "gemini-3.8-flash";
|
|
1081
|
+
ApiRouteModels["QWEN3_8_FLASH"] = "qwen3.8-flash";
|
|
1082
|
+
ApiRouteModels["KIMI_K2_7_CODE"] = "kimi-k2.7-code";
|
|
1083
|
+
ApiRouteModels["GLM_5_3_FLASH"] = "glm-5.3-flash";
|
|
1084
|
+
})(ApiRouteModels || (ApiRouteModels = {}));
|
|
1073
1085
|
export var BasetenModels;
|
|
1074
1086
|
(function (BasetenModels) {
|
|
1075
1087
|
BasetenModels["OPENAI_GPT_OSS_120B"] = "openai/gpt-oss-120b";
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./provider-catalog.schema.json",
|
|
3
|
+
"id": "api-route",
|
|
4
|
+
"displayName": "API Route",
|
|
5
|
+
"aliases": ["apiroute"],
|
|
6
|
+
"tier": 2,
|
|
7
|
+
"wire": {
|
|
8
|
+
"baseURL": "https://global.api-route.com/v1"
|
|
9
|
+
},
|
|
10
|
+
"models": {
|
|
11
|
+
"default": "claude-sonnet-4-6",
|
|
12
|
+
"fallbacks": ["claude-haiku-4-5", "deepseek-v4-flash", "gemini-3.8-flash"],
|
|
13
|
+
"defaultContextWindow": 1000000,
|
|
14
|
+
"defaultMaxOutputTokens": 64000,
|
|
15
|
+
"catalog": {
|
|
16
|
+
"claude-sonnet-4-6": {
|
|
17
|
+
"contextWindow": 1000000,
|
|
18
|
+
"maxOutputTokens": 64000,
|
|
19
|
+
"vision": true,
|
|
20
|
+
"status": "production",
|
|
21
|
+
"description": "Claude Sonnet 4.6 — flagship model with 1M context, tool calling, and multimodal vision"
|
|
22
|
+
},
|
|
23
|
+
"claude-haiku-4-5": {
|
|
24
|
+
"contextWindow": 200000,
|
|
25
|
+
"maxOutputTokens": 32768,
|
|
26
|
+
"vision": true,
|
|
27
|
+
"status": "production",
|
|
28
|
+
"description": "Claude Haiku 4.5 — fast, high-efficiency model with 200K context, tool calling, and vision"
|
|
29
|
+
},
|
|
30
|
+
"deepseek-v4-flash": {
|
|
31
|
+
"contextWindow": 1000000,
|
|
32
|
+
"maxOutputTokens": 64000,
|
|
33
|
+
"vision": false,
|
|
34
|
+
"status": "production",
|
|
35
|
+
"description": "DeepSeek V4 Flash — fast, cost-effective reasoning model with 1M context"
|
|
36
|
+
},
|
|
37
|
+
"deepseek-v4-pro": {
|
|
38
|
+
"contextWindow": 1000000,
|
|
39
|
+
"maxOutputTokens": 64000,
|
|
40
|
+
"vision": false,
|
|
41
|
+
"status": "production",
|
|
42
|
+
"description": "DeepSeek V4 Pro — powerful reasoning model with 1M context"
|
|
43
|
+
},
|
|
44
|
+
"gemini-3.8-flash": {
|
|
45
|
+
"contextWindow": 1000000,
|
|
46
|
+
"maxOutputTokens": 64000,
|
|
47
|
+
"vision": true,
|
|
48
|
+
"status": "production",
|
|
49
|
+
"description": "Gemini 3.8 Flash — high-speed multimodal model supporting image input and streaming"
|
|
50
|
+
},
|
|
51
|
+
"qwen3.8-flash": {
|
|
52
|
+
"contextWindow": 128000,
|
|
53
|
+
"maxOutputTokens": 16384,
|
|
54
|
+
"vision": false,
|
|
55
|
+
"status": "production",
|
|
56
|
+
"description": "Qwen 3.8 Flash — versatile, fast model for general instruction and coding tasks"
|
|
57
|
+
},
|
|
58
|
+
"kimi-k2.7-code": {
|
|
59
|
+
"contextWindow": 256000,
|
|
60
|
+
"maxOutputTokens": 32768,
|
|
61
|
+
"vision": false,
|
|
62
|
+
"status": "production",
|
|
63
|
+
"description": "Kimi K2.7 Code — specialized for coding and multi-step reasoning"
|
|
64
|
+
},
|
|
65
|
+
"glm-5.3-flash": {
|
|
66
|
+
"contextWindow": 128000,
|
|
67
|
+
"maxOutputTokens": 16384,
|
|
68
|
+
"vision": false,
|
|
69
|
+
"status": "production",
|
|
70
|
+
"description": "GLM 5.3 Flash — lightweight, fast conversational and instruction-following model"
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"visionModel": "claude-sonnet-4-6",
|
|
74
|
+
"topModels": [
|
|
75
|
+
"claude-sonnet-4-6",
|
|
76
|
+
"claude-haiku-4-5",
|
|
77
|
+
"deepseek-v4-flash",
|
|
78
|
+
"gemini-3.8-flash",
|
|
79
|
+
"qwen3.8-flash"
|
|
80
|
+
]
|
|
81
|
+
},
|
|
82
|
+
"capabilities": {
|
|
83
|
+
"text": true,
|
|
84
|
+
"streaming": true,
|
|
85
|
+
"tools": true,
|
|
86
|
+
"toolsWithStreaming": true,
|
|
87
|
+
"structuredOutput": true,
|
|
88
|
+
"structuredOutputWithTools": true,
|
|
89
|
+
"embeddings": false,
|
|
90
|
+
"thinking": false
|
|
91
|
+
},
|
|
92
|
+
"errorRules": [
|
|
93
|
+
{
|
|
94
|
+
"pattern": "该分组下模型无可用渠道|model_not_found",
|
|
95
|
+
"class": "invalid-model",
|
|
96
|
+
"message": "Model '{model}' not found or has no available channels on API Route."
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"pattern": "无效的令牌",
|
|
100
|
+
"class": "authentication",
|
|
101
|
+
"message": "Invalid API Route API key. Check {apiKeyEnvVar}."
|
|
102
|
+
}
|
|
103
|
+
],
|
|
104
|
+
"setup": {
|
|
105
|
+
"url": "https://api-route.com",
|
|
106
|
+
"apiKeyFormat": null,
|
|
107
|
+
"billingPolicy": "free-tier",
|
|
108
|
+
"instructions": [
|
|
109
|
+
"1. Visit: https://api-route.com and create an account",
|
|
110
|
+
"2. Generate an API key in your console / dashboard",
|
|
111
|
+
"3. Set {apiKeyEnvVar} in your .env file"
|
|
112
|
+
]
|
|
113
|
+
},
|
|
114
|
+
"evidence": {
|
|
115
|
+
"rosterVerified": {
|
|
116
|
+
"date": "2026-09-17",
|
|
117
|
+
"method": "authenticated GET /v1/models",
|
|
118
|
+
"status": 200
|
|
119
|
+
},
|
|
120
|
+
"authProbe": {
|
|
121
|
+
"date": "2026-09-17",
|
|
122
|
+
"status": 401
|
|
123
|
+
},
|
|
124
|
+
"liveMatrix": {
|
|
125
|
+
"date": "2026-09-17",
|
|
126
|
+
"result": "Verified live against https://global.api-route.com/v1: GET /v1/models returned 70 models. Chat completions, streaming, function calling, streamed function calling, and structured JSON output (json_object and json_schema) verified. Multimodal vision verified on claude-sonnet-4-6, claude-haiku-4-5, and gemini-3.8-flash. Invalid API key returns 401 (无效的令牌) and invalid model returns 503 (model_not_found)."
|
|
127
|
+
},
|
|
128
|
+
"addedInPR": "pending"
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { ProviderCatalogJson } from "../../types/index.js";
|
|
2
2
|
export declare const CATALOG_JSON_ENTRIES: ProviderCatalogJson[];
|
|
3
|
-
export declare const CATALOG_PROVIDER_IDS: readonly ["baseten", "cerebras", "cloudflare", "fireworks", "gmicloud", "groq", "inception-labs", "io-intelligence", "mancer", "mistral", "perplexity", "sambanova", "together-ai", "upstage", "xai"];
|
|
3
|
+
export declare const CATALOG_PROVIDER_IDS: readonly ["api-route", "baseten", "cerebras", "cloudflare", "fireworks", "gmicloud", "groq", "inception-labs", "io-intelligence", "mancer", "mistral", "perplexity", "sambanova", "together-ai", "upstage", "xai"];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// GENERATED FILE — do not edit. Regenerate with `pnpm run codegen:catalog`.
|
|
2
2
|
// Source of truth: the per-provider JSON files in this directory.
|
|
3
|
+
import apiRouteJson from "./api-route.json" with { type: "json" };
|
|
3
4
|
import basetenJson from "./baseten.json" with { type: "json" };
|
|
4
5
|
import cerebrasJson from "./cerebras.json" with { type: "json" };
|
|
5
6
|
import cloudflareJson from "./cloudflare.json" with { type: "json" };
|
|
@@ -16,6 +17,7 @@ import togetherAiJson from "./together-ai.json" with { type: "json" };
|
|
|
16
17
|
import upstageJson from "./upstage.json" with { type: "json" };
|
|
17
18
|
import xaiJson from "./xai.json" with { type: "json" };
|
|
18
19
|
export const CATALOG_JSON_ENTRIES = [
|
|
20
|
+
apiRouteJson,
|
|
19
21
|
basetenJson,
|
|
20
22
|
cerebrasJson,
|
|
21
23
|
cloudflareJson,
|
|
@@ -33,6 +35,7 @@ export const CATALOG_JSON_ENTRIES = [
|
|
|
33
35
|
xaiJson,
|
|
34
36
|
];
|
|
35
37
|
export const CATALOG_PROVIDER_IDS = [
|
|
38
|
+
"api-route",
|
|
36
39
|
"baseten",
|
|
37
40
|
"cerebras",
|
|
38
41
|
"cloudflare",
|
|
@@ -73,8 +73,8 @@ export declare const providerCatalogJsonSchema: z.ZodObject<{
|
|
|
73
73
|
class: z.ZodEnum<{
|
|
74
74
|
network: "network";
|
|
75
75
|
provider: "provider";
|
|
76
|
-
authentication: "authentication";
|
|
77
76
|
"invalid-model": "invalid-model";
|
|
77
|
+
authentication: "authentication";
|
|
78
78
|
"rate-limit": "rate-limit";
|
|
79
79
|
}>;
|
|
80
80
|
message: z.ZodString;
|