@juspay/neurolink 9.91.0 → 9.92.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 +12 -0
- package/dist/browser/neurolink.min.js +352 -351
- package/dist/cli/commands/auth.js +25 -15
- package/dist/cli/commands/proxy.d.ts +4 -3
- package/dist/cli/commands/proxy.js +187 -130
- package/dist/lib/providers/sagemaker/client.d.ts +9 -0
- package/dist/lib/providers/sagemaker/client.js +75 -30
- package/dist/lib/proxy/proxyConfig.js +66 -0
- package/dist/lib/proxy/runtimeConfig.d.ts +26 -0
- package/dist/lib/proxy/runtimeConfig.js +428 -0
- package/dist/lib/server/routes/claudeProxyRoutes.d.ts +5 -6
- package/dist/lib/server/routes/claudeProxyRoutes.js +68 -37
- package/dist/lib/server/routes/openaiProxyRoutes.d.ts +2 -3
- package/dist/lib/server/routes/openaiProxyRoutes.js +11 -5
- package/dist/lib/types/cli.d.ts +8 -0
- package/dist/lib/types/proxy.d.ts +67 -1
- package/dist/lib/types/subscription.d.ts +6 -0
- package/dist/providers/sagemaker/client.d.ts +9 -0
- package/dist/providers/sagemaker/client.js +75 -30
- package/dist/proxy/proxyConfig.js +66 -0
- package/dist/proxy/runtimeConfig.d.ts +26 -0
- package/dist/proxy/runtimeConfig.js +427 -0
- package/dist/server/routes/claudeProxyRoutes.d.ts +5 -6
- package/dist/server/routes/claudeProxyRoutes.js +68 -37
- package/dist/server/routes/openaiProxyRoutes.d.ts +2 -3
- package/dist/server/routes/openaiProxyRoutes.js +11 -5
- package/dist/types/cli.d.ts +8 -0
- package/dist/types/proxy.d.ts +67 -1
- package/dist/types/subscription.d.ts +6 -0
- package/package.json +2 -2
|
@@ -855,7 +855,7 @@ function readPrimaryFromRouting(routing) {
|
|
|
855
855
|
}
|
|
856
856
|
/** Best-effort detection of a running proxy. Mirrors `proxy status` semantics
|
|
857
857
|
* without importing the proxy module. */
|
|
858
|
-
function
|
|
858
|
+
function detectRunningProxyState() {
|
|
859
859
|
try {
|
|
860
860
|
const stateFile = path.join(NEUROLINK_CONFIG_DIR, "proxy-state.json");
|
|
861
861
|
if (!fs.existsSync(stateFile)) {
|
|
@@ -866,12 +866,33 @@ function detectRunningProxyPid() {
|
|
|
866
866
|
return undefined;
|
|
867
867
|
}
|
|
868
868
|
process.kill(parsed.pid, 0);
|
|
869
|
-
return parsed
|
|
869
|
+
return parsed;
|
|
870
870
|
}
|
|
871
871
|
catch {
|
|
872
872
|
return undefined;
|
|
873
873
|
}
|
|
874
874
|
}
|
|
875
|
+
function reportRunningProxyConfigUpdate(filePath) {
|
|
876
|
+
const state = detectRunningProxyState();
|
|
877
|
+
if (!state) {
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
logger.always("");
|
|
881
|
+
const editedPath = path.resolve(filePath);
|
|
882
|
+
const watchedPath = state.configFile
|
|
883
|
+
? path.resolve(state.configFile)
|
|
884
|
+
: undefined;
|
|
885
|
+
if (watchedPath === editedPath) {
|
|
886
|
+
logger.always(chalk.green(`✓ Running proxy PID ${state.pid} is watching this file and will apply the change automatically.`));
|
|
887
|
+
logger.always(chalk.gray(" Verify the active generation with `neurolink proxy status`."));
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
if (watchedPath) {
|
|
891
|
+
logger.always(chalk.yellow(`⚠ Running proxy PID ${state.pid} watches ${watchedPath}, not ${editedPath}; this change will not affect that process.`));
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
logger.always(chalk.yellow(`⚠ Running proxy PID ${state.pid} does not report hot-reload support. Restart it to pick up the change.`));
|
|
895
|
+
}
|
|
875
896
|
/**
|
|
876
897
|
* Handle the set-primary subcommand
|
|
877
898
|
* `neurolink auth set-primary <email> [--config <path>]`
|
|
@@ -910,13 +931,7 @@ export async function handleSetPrimary(argv) {
|
|
|
910
931
|
" `neurolink auth login --add` to add it. The proxy will fall\n" +
|
|
911
932
|
" back to the first enabled account until then."));
|
|
912
933
|
}
|
|
913
|
-
|
|
914
|
-
const pid = detectRunningProxyPid();
|
|
915
|
-
if (pid) {
|
|
916
|
-
logger.always("");
|
|
917
|
-
logger.always(chalk.yellow(`⚠ A proxy is currently running (PID ${pid}). Restart it to pick\n` +
|
|
918
|
-
" up the change: `neurolink proxy stop && neurolink proxy start`."));
|
|
919
|
-
}
|
|
934
|
+
reportRunningProxyConfigUpdate(filePath);
|
|
920
935
|
}
|
|
921
936
|
catch (err) {
|
|
922
937
|
logger.error(chalk.red("Failed to set primary account:"));
|
|
@@ -991,12 +1006,7 @@ export async function handleClearPrimary(argv) {
|
|
|
991
1006
|
await writeProxyConfigFile(filePath, doc);
|
|
992
1007
|
logger.always(chalk.green(`✓ Cleared primary account (was: ${before})`));
|
|
993
1008
|
logger.always(chalk.green(`✓ Saved to ${filePath}`));
|
|
994
|
-
|
|
995
|
-
if (pid) {
|
|
996
|
-
logger.always("");
|
|
997
|
-
logger.always(chalk.yellow(`⚠ A proxy is currently running (PID ${pid}). Restart it to pick\n` +
|
|
998
|
-
" up the change: `neurolink proxy stop && neurolink proxy start`."));
|
|
999
|
-
}
|
|
1009
|
+
reportRunningProxyConfigUpdate(filePath);
|
|
1000
1010
|
}
|
|
1001
1011
|
catch (err) {
|
|
1002
1012
|
logger.error(chalk.red("Failed to clear primary account:"));
|
|
@@ -10,12 +10,12 @@
|
|
|
10
10
|
* (generate/stream), with an optional ModelRouter for model remapping.
|
|
11
11
|
*/
|
|
12
12
|
import type { CommandModule } from "yargs";
|
|
13
|
-
import type { AccountAllowlist, LoadedProxyConfig, ProxyGuardArgs, ProxyNeurolinkRuntime, ProxyStartArgs, ProxyStartStrategy, ProxyStatusArgs, ProxyTelemetryArgs } from "../../lib/types/index.js";
|
|
14
|
-
import
|
|
13
|
+
import type { AccountAllowlist, LoadedProxyConfig, ModelRouterInterface, ProxyGuardArgs, ProxyNeurolinkRuntime, ProxyStartArgs, ProxyStartStrategy, ProxyStatusArgs, ProxyTelemetryArgs } from "../../lib/types/index.js";
|
|
14
|
+
import { ProxyRuntimeConfigStore } from "../../lib/proxy/runtimeConfig.js";
|
|
15
15
|
export declare function mapClaudeErrorTypeToStatus(errorType?: string): number;
|
|
16
16
|
export declare function createProxyStartApp(params: {
|
|
17
17
|
neurolink: ProxyNeurolinkRuntime["neurolink"];
|
|
18
|
-
modelRouter:
|
|
18
|
+
modelRouter: ModelRouterInterface | undefined;
|
|
19
19
|
strategy: ProxyStartStrategy;
|
|
20
20
|
passthrough: boolean;
|
|
21
21
|
port: number;
|
|
@@ -23,6 +23,7 @@ export declare function createProxyStartApp(params: {
|
|
|
23
23
|
proxyConfig: LoadedProxyConfig | null;
|
|
24
24
|
primaryAccountKey: string | undefined;
|
|
25
25
|
accountAllowlist: AccountAllowlist | undefined;
|
|
26
|
+
runtimeConfigStore?: ProxyRuntimeConfigStore;
|
|
26
27
|
}): Promise<{
|
|
27
28
|
app: import("hono").Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
|
|
28
29
|
readiness: import("../../lib/types/proxy.js").ProxyReadinessState;
|
|
@@ -18,6 +18,7 @@ import { buildProxyHealthResponse, createProxyReadinessState, markProxyReady, wa
|
|
|
18
18
|
import { logger } from "../../lib/utils/logger.js";
|
|
19
19
|
import { formatUptime, isProcessRunning, StateFileManager, } from "../utils/serverUtils.js";
|
|
20
20
|
import { configureProxyKeepAliveDispatcher } from "../../lib/proxy/proxyDispatcher.js";
|
|
21
|
+
import { ProxyRuntimeConfigStore } from "../../lib/proxy/runtimeConfig.js";
|
|
21
22
|
import { anthropicAccountKeysEqual, createAccountAllowlist, ENV_ANTHROPIC_ACCOUNT_KEY, isAccountAllowed, LEGACY_ANTHROPIC_ACCOUNT_KEY, normalizeAnthropicAccountKey, shouldLoadFallbackCredential, } from "../../lib/proxy/accountSelection.js";
|
|
22
23
|
import { beginProxyRequest, getProxyActivitySnapshot, trackProxyResponse, } from "../../lib/proxy/proxyActivity.js";
|
|
23
24
|
import { describeInstallFailure, getGlobalInstallArgs, resolveGlobalInstaller, validateInstalledVersion, } from "../../lib/proxy/globalInstaller.js";
|
|
@@ -709,107 +710,6 @@ async function createProxyNeurolinkRuntime(logsDir) {
|
|
|
709
710
|
cleanupLogs(7, 500);
|
|
710
711
|
return { neurolink, cleanupLogs };
|
|
711
712
|
}
|
|
712
|
-
async function loadProxyStartConfiguration(argv, spinner) {
|
|
713
|
-
const configPath = argv.config ?? join(homedir(), ".neurolink", "proxy-config.yaml");
|
|
714
|
-
let proxyConfig = null;
|
|
715
|
-
try {
|
|
716
|
-
const { loadProxyConfig } = await import("../../lib/proxy/proxyConfig.js");
|
|
717
|
-
proxyConfig = (await loadProxyConfig(configPath));
|
|
718
|
-
if (spinner) {
|
|
719
|
-
spinner.text = `Loaded proxy config from ${configPath}`;
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
catch (configError) {
|
|
723
|
-
const isNotFound = configError instanceof Error &&
|
|
724
|
-
"code" in configError &&
|
|
725
|
-
configError.code === "ENOENT";
|
|
726
|
-
if (argv.config || !isNotFound) {
|
|
727
|
-
if (spinner) {
|
|
728
|
-
spinner.fail(chalk.red(`Failed to load proxy config: ${configPath}`));
|
|
729
|
-
}
|
|
730
|
-
throw configError;
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
const strategy = (argv.strategy ??
|
|
734
|
-
proxyConfig?.routing?.strategy ??
|
|
735
|
-
"fill-first");
|
|
736
|
-
let modelRouter;
|
|
737
|
-
if (proxyConfig?.routing) {
|
|
738
|
-
const { ModelRouter } = await import("../../lib/proxy/modelRouter.js");
|
|
739
|
-
modelRouter = new ModelRouter({
|
|
740
|
-
strategy,
|
|
741
|
-
modelMappings: proxyConfig.routing.modelMappings ?? [],
|
|
742
|
-
fallbackChain: proxyConfig.routing.fallbackChain ?? [],
|
|
743
|
-
passthroughModels: proxyConfig.routing.passthroughModels,
|
|
744
|
-
});
|
|
745
|
-
}
|
|
746
|
-
const primaryAccountKey = await resolveBootPrimaryAccountKey(proxyConfig?.routing?.primaryAccount);
|
|
747
|
-
const accountAllowlist = await resolveBootAccountAllowlist(proxyConfig?.routing?.accountAllowlist);
|
|
748
|
-
if (primaryAccountKey &&
|
|
749
|
-
!isAccountAllowed(primaryAccountKey, accountAllowlist)) {
|
|
750
|
-
throw new Error(`Configured routing.primaryAccount=${proxyConfig?.routing?.primaryAccount} is excluded by routing.accountAllowlist`);
|
|
751
|
-
}
|
|
752
|
-
return {
|
|
753
|
-
configPath,
|
|
754
|
-
proxyConfig,
|
|
755
|
-
strategy,
|
|
756
|
-
modelRouter,
|
|
757
|
-
passthrough: argv.passthrough ?? false,
|
|
758
|
-
primaryAccountKey,
|
|
759
|
-
accountAllowlist,
|
|
760
|
-
};
|
|
761
|
-
}
|
|
762
|
-
async function resolveBootAccountAllowlist(configuredAccounts) {
|
|
763
|
-
const allowlist = createAccountAllowlist(configuredAccounts);
|
|
764
|
-
if (allowlist === undefined) {
|
|
765
|
-
return undefined;
|
|
766
|
-
}
|
|
767
|
-
if (allowlist.size === 0) {
|
|
768
|
-
logger.warn("[proxy] routing.accountAllowlist is empty; all stored Anthropic credentials are denied");
|
|
769
|
-
return allowlist;
|
|
770
|
-
}
|
|
771
|
-
try {
|
|
772
|
-
const { tokenStore } = await import("../../lib/auth/tokenStore.js");
|
|
773
|
-
const known = new Set((await tokenStore.listByPrefix("anthropic:")).map(normalizeAnthropicAccountKey));
|
|
774
|
-
known.add(LEGACY_ANTHROPIC_ACCOUNT_KEY);
|
|
775
|
-
known.add(ENV_ANTHROPIC_ACCOUNT_KEY);
|
|
776
|
-
for (const key of allowlist) {
|
|
777
|
-
if (!known.has(key)) {
|
|
778
|
-
logger.warn(`[proxy] WARN: routing.accountAllowlist entry ${key} is allowed but not currently authenticated`);
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
catch (err) {
|
|
783
|
-
logger.debug(`[proxy] could not validate account allowlist against token store: ${err instanceof Error ? err.message : String(err)}`);
|
|
784
|
-
}
|
|
785
|
-
return allowlist;
|
|
786
|
-
}
|
|
787
|
-
/** Resolve the operator's configured primary email to a stable token-store
|
|
788
|
-
* key (anthropic:<email>). Cross-checks the token store and emits a one-time
|
|
789
|
-
* startup warning if the configured account isn't authenticated — but still
|
|
790
|
-
* returns the key so it activates automatically once the user runs
|
|
791
|
-
* `auth login --add`. */
|
|
792
|
-
async function resolveBootPrimaryAccountKey(primaryEmail) {
|
|
793
|
-
const trimmed = primaryEmail?.trim();
|
|
794
|
-
if (!trimmed) {
|
|
795
|
-
return undefined;
|
|
796
|
-
}
|
|
797
|
-
const key = normalizeAnthropicAccountKey(trimmed);
|
|
798
|
-
try {
|
|
799
|
-
const { tokenStore } = await import("../../lib/auth/tokenStore.js");
|
|
800
|
-
const known = await tokenStore.listByPrefix("anthropic:");
|
|
801
|
-
if (!known.some((knownKey) => anthropicAccountKeysEqual(knownKey, key))) {
|
|
802
|
-
logger.warn(`[proxy] WARN: configured routing.primaryAccount=${trimmed} not ` +
|
|
803
|
-
`found in token store; falling back to first enabled account. ` +
|
|
804
|
-
`Run \`neurolink auth login --add\` to authenticate it, or ` +
|
|
805
|
-
`\`neurolink auth clear-primary\` to remove the setting.`);
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
catch (err) {
|
|
809
|
-
logger.debug(`[proxy] could not validate primary account against token store: ${err instanceof Error ? err.message : String(err)}`);
|
|
810
|
-
}
|
|
811
|
-
return key;
|
|
812
|
-
}
|
|
813
713
|
export async function createProxyStartApp(params) {
|
|
814
714
|
const { createClaudeProxyRoutes } = await import("../../lib/server/routes/claudeProxyRoutes.js");
|
|
815
715
|
const { createOpenAIProxyRoutes } = await import("../../lib/server/routes/openaiProxyRoutes.js");
|
|
@@ -904,8 +804,17 @@ export async function createProxyStartApp(params) {
|
|
|
904
804
|
throw error;
|
|
905
805
|
}
|
|
906
806
|
});
|
|
907
|
-
const
|
|
908
|
-
const
|
|
807
|
+
const runtimeConfigStore = params.runtimeConfigStore;
|
|
808
|
+
const runtimeConfigProvider = runtimeConfigStore
|
|
809
|
+
? () => runtimeConfigStore.getSnapshot()
|
|
810
|
+
: undefined;
|
|
811
|
+
const routeGroup = createClaudeProxyRoutes(params.modelRouter, "", params.strategy, params.passthrough, params.primaryAccountKey, runtimeConfigProvider
|
|
812
|
+
? {
|
|
813
|
+
accountAllowlist: params.accountAllowlist,
|
|
814
|
+
runtimeConfigProvider,
|
|
815
|
+
}
|
|
816
|
+
: params.accountAllowlist);
|
|
817
|
+
const openaiRouteGroup = createOpenAIProxyRoutes(params.modelRouter, "", params.port, runtimeConfigProvider);
|
|
909
818
|
const allProxyRoutes = [...routeGroup.routes, ...openaiRouteGroup.routes];
|
|
910
819
|
for (const route of allProxyRoutes) {
|
|
911
820
|
const method = route.method.toLowerCase();
|
|
@@ -1032,12 +941,31 @@ export async function createProxyStartApp(params) {
|
|
|
1032
941
|
return c.json(result ?? {});
|
|
1033
942
|
});
|
|
1034
943
|
}
|
|
1035
|
-
app.get("/health", (c) =>
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
944
|
+
app.get("/health", (c) => {
|
|
945
|
+
const runtimeConfig = params.runtimeConfigStore?.getSnapshot();
|
|
946
|
+
return c.json(buildProxyHealthResponse(readiness, {
|
|
947
|
+
strategy: runtimeConfig ? runtimeConfig.strategy : params.strategy,
|
|
948
|
+
passthrough: runtimeConfig
|
|
949
|
+
? runtimeConfig.passthrough
|
|
950
|
+
: params.passthrough,
|
|
951
|
+
version: PROXY_VERSION,
|
|
952
|
+
}));
|
|
953
|
+
});
|
|
1040
954
|
app.get("/status", async (c) => {
|
|
955
|
+
const runtimeConfig = params.runtimeConfigStore?.getSnapshot();
|
|
956
|
+
const runtimeConfigStatus = params.runtimeConfigStore?.getStatus();
|
|
957
|
+
const activeStrategy = runtimeConfig
|
|
958
|
+
? runtimeConfig.strategy
|
|
959
|
+
: params.strategy;
|
|
960
|
+
const activePassthrough = runtimeConfig
|
|
961
|
+
? runtimeConfig.passthrough
|
|
962
|
+
: params.passthrough;
|
|
963
|
+
const activeProxyConfig = runtimeConfig
|
|
964
|
+
? runtimeConfig.proxyConfig
|
|
965
|
+
: params.proxyConfig;
|
|
966
|
+
const activeAccountAllowlist = runtimeConfig
|
|
967
|
+
? runtimeConfig.accountAllowlist
|
|
968
|
+
: params.accountAllowlist;
|
|
1041
969
|
const { getStats } = await import("../../lib/proxy/usageStats.js");
|
|
1042
970
|
const { loadAccountCooldowns } = await import("../../lib/proxy/accountCooldown.js");
|
|
1043
971
|
const stats = getStats();
|
|
@@ -1056,11 +984,11 @@ export async function createProxyStartApp(params) {
|
|
|
1056
984
|
}
|
|
1057
985
|
const now = Date.now();
|
|
1058
986
|
const health = buildProxyHealthResponse(readiness, {
|
|
1059
|
-
strategy:
|
|
1060
|
-
passthrough:
|
|
987
|
+
strategy: activeStrategy,
|
|
988
|
+
passthrough: activePassthrough,
|
|
1061
989
|
version: PROXY_VERSION,
|
|
1062
990
|
});
|
|
1063
|
-
const primaryAccount = await resolveStatusPrimaryAccount(
|
|
991
|
+
const primaryAccount = await resolveStatusPrimaryAccount(activeProxyConfig);
|
|
1064
992
|
return c.json({
|
|
1065
993
|
status: "running",
|
|
1066
994
|
ready: health.ready,
|
|
@@ -1069,7 +997,7 @@ export async function createProxyStartApp(params) {
|
|
|
1069
997
|
pid: process.pid,
|
|
1070
998
|
port: params.port,
|
|
1071
999
|
host: params.host,
|
|
1072
|
-
strategy:
|
|
1000
|
+
strategy: activeStrategy,
|
|
1073
1001
|
uptime: process.uptime(),
|
|
1074
1002
|
version: PROXY_VERSION,
|
|
1075
1003
|
health,
|
|
@@ -1134,14 +1062,30 @@ export async function createProxyStartApp(params) {
|
|
|
1134
1062
|
}
|
|
1135
1063
|
: null,
|
|
1136
1064
|
},
|
|
1137
|
-
config: params.
|
|
1065
|
+
config: params.runtimeConfigStore
|
|
1138
1066
|
? {
|
|
1139
|
-
hasRouting: !!
|
|
1140
|
-
accountAllowlist:
|
|
1141
|
-
? [...
|
|
1067
|
+
hasRouting: !!activeProxyConfig?.routing,
|
|
1068
|
+
accountAllowlist: activeAccountAllowlist
|
|
1069
|
+
? [...activeAccountAllowlist]
|
|
1142
1070
|
: null,
|
|
1071
|
+
generation: runtimeConfig?.generation ?? null,
|
|
1072
|
+
loadedAt: runtimeConfig?.loadedAt ?? null,
|
|
1073
|
+
hash: runtimeConfig?.configHash ?? null,
|
|
1074
|
+
watching: runtimeConfigStatus?.watching ?? false,
|
|
1075
|
+
lastReloadAttemptAt: runtimeConfigStatus?.lastReloadAttemptAt ?? null,
|
|
1076
|
+
lastReloadAt: runtimeConfigStatus?.lastReloadAt ?? null,
|
|
1077
|
+
lastReloadSource: runtimeConfigStatus?.lastReloadSource ?? null,
|
|
1078
|
+
lastReloadError: runtimeConfigStatus?.lastReloadError ?? null,
|
|
1079
|
+
consecutiveFailures: runtimeConfigStatus?.consecutiveFailures ?? 0,
|
|
1143
1080
|
}
|
|
1144
|
-
:
|
|
1081
|
+
: params.proxyConfig
|
|
1082
|
+
? {
|
|
1083
|
+
hasRouting: !!params.proxyConfig.routing,
|
|
1084
|
+
accountAllowlist: params.accountAllowlist
|
|
1085
|
+
? [...params.accountAllowlist]
|
|
1086
|
+
: null,
|
|
1087
|
+
}
|
|
1088
|
+
: null,
|
|
1145
1089
|
});
|
|
1146
1090
|
});
|
|
1147
1091
|
return { app, readiness };
|
|
@@ -1321,13 +1265,13 @@ async function refreshProxyTokensInBackground(accountAllowlist) {
|
|
|
1321
1265
|
// non-fatal
|
|
1322
1266
|
}
|
|
1323
1267
|
}
|
|
1324
|
-
function startProxyBackgroundMaintenance(cleanupLogs,
|
|
1268
|
+
function startProxyBackgroundMaintenance(cleanupLogs, getAccountAllowlist) {
|
|
1325
1269
|
const refreshInterval = setInterval(() => {
|
|
1326
1270
|
if (backgroundRefreshInProgress) {
|
|
1327
1271
|
return;
|
|
1328
1272
|
}
|
|
1329
1273
|
backgroundRefreshInProgress = true;
|
|
1330
|
-
void refreshProxyTokensInBackground(
|
|
1274
|
+
void refreshProxyTokensInBackground(getAccountAllowlist())
|
|
1331
1275
|
.catch((error) => {
|
|
1332
1276
|
logger.debug(`[proxy] background token refresh cycle failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1333
1277
|
})
|
|
@@ -1385,6 +1329,7 @@ function registerProxyShutdownHandlers(params) {
|
|
|
1385
1329
|
clearInterval(params.refreshInterval);
|
|
1386
1330
|
clearInterval(params.logCleanupInterval);
|
|
1387
1331
|
params.updaterSupervisor?.stop();
|
|
1332
|
+
params.stopRuntimeConfig?.();
|
|
1388
1333
|
logger.always(`\nShutting down proxy (${signal})...`);
|
|
1389
1334
|
let exitCode = signal === "SIGINT" ? 0 : 1;
|
|
1390
1335
|
try {
|
|
@@ -1485,15 +1430,29 @@ async function startProxyRuntime(params) {
|
|
|
1485
1430
|
})
|
|
1486
1431
|
: undefined;
|
|
1487
1432
|
const updaterPid = updaterSupervisor?.currentPid();
|
|
1488
|
-
const
|
|
1433
|
+
const initialRuntimeConfig = params.runtimeConfigStore?.getSnapshot();
|
|
1434
|
+
const activeStrategy = initialRuntimeConfig
|
|
1435
|
+
? initialRuntimeConfig.strategy
|
|
1436
|
+
: params.strategy;
|
|
1437
|
+
const activeProxyConfig = initialRuntimeConfig
|
|
1438
|
+
? initialRuntimeConfig.proxyConfig
|
|
1439
|
+
: params.proxyConfig;
|
|
1440
|
+
const activeAccountAllowlist = initialRuntimeConfig
|
|
1441
|
+
? initialRuntimeConfig.accountAllowlist
|
|
1442
|
+
: params.accountAllowlist;
|
|
1443
|
+
const activePassthrough = initialRuntimeConfig
|
|
1444
|
+
? initialRuntimeConfig.passthrough
|
|
1445
|
+
: params.passthrough;
|
|
1446
|
+
const fallbackChain = activeProxyConfig?.routing?.fallbackChain?.map((entry) => ({
|
|
1489
1447
|
provider: entry.provider,
|
|
1490
1448
|
model: entry.model,
|
|
1491
1449
|
}));
|
|
1450
|
+
const initialConfigStatus = params.runtimeConfigStore?.getStatus();
|
|
1492
1451
|
saveProxyState({
|
|
1493
1452
|
pid: process.pid,
|
|
1494
1453
|
port: params.port,
|
|
1495
1454
|
host: params.host,
|
|
1496
|
-
strategy:
|
|
1455
|
+
strategy: activeStrategy,
|
|
1497
1456
|
startTime: new Date().toISOString(),
|
|
1498
1457
|
ready: true,
|
|
1499
1458
|
readyAt: params.readiness.readyAtMs
|
|
@@ -1503,28 +1462,73 @@ async function startProxyRuntime(params) {
|
|
|
1503
1462
|
statusPath: "/status",
|
|
1504
1463
|
envFile: params.loadedEnvFile,
|
|
1505
1464
|
fallbackChain,
|
|
1506
|
-
accountAllowlist:
|
|
1507
|
-
? [...
|
|
1465
|
+
accountAllowlist: activeAccountAllowlist
|
|
1466
|
+
? [...activeAccountAllowlist]
|
|
1508
1467
|
: undefined,
|
|
1509
1468
|
guardPid,
|
|
1510
1469
|
updaterPid,
|
|
1511
1470
|
managedBy: managedByLaunchd ? "launchd" : "manual",
|
|
1512
|
-
passthrough:
|
|
1471
|
+
passthrough: activePassthrough,
|
|
1472
|
+
configGeneration: initialRuntimeConfig?.generation,
|
|
1473
|
+
configLoadedAt: initialRuntimeConfig?.loadedAt,
|
|
1474
|
+
lastConfigReloadError: initialConfigStatus?.lastReloadError,
|
|
1475
|
+
configFile: initialConfigStatus?.configPath,
|
|
1513
1476
|
});
|
|
1477
|
+
const persistRuntimeConfig = (snapshot) => {
|
|
1478
|
+
const state = loadProxyState();
|
|
1479
|
+
if (!state || state.pid !== process.pid) {
|
|
1480
|
+
return;
|
|
1481
|
+
}
|
|
1482
|
+
const status = params.runtimeConfigStore?.getStatus();
|
|
1483
|
+
const currentFallbackChain = snapshot.proxyConfig?.routing?.fallbackChain?.map((entry) => ({
|
|
1484
|
+
provider: entry.provider,
|
|
1485
|
+
model: entry.model,
|
|
1486
|
+
}));
|
|
1487
|
+
saveProxyState({
|
|
1488
|
+
...state,
|
|
1489
|
+
strategy: snapshot.strategy,
|
|
1490
|
+
fallbackChain: currentFallbackChain,
|
|
1491
|
+
accountAllowlist: snapshot.accountAllowlist
|
|
1492
|
+
? [...snapshot.accountAllowlist]
|
|
1493
|
+
: undefined,
|
|
1494
|
+
passthrough: snapshot.passthrough,
|
|
1495
|
+
configGeneration: snapshot.generation,
|
|
1496
|
+
configLoadedAt: snapshot.loadedAt,
|
|
1497
|
+
lastConfigReloadError: status?.lastReloadError,
|
|
1498
|
+
});
|
|
1499
|
+
};
|
|
1500
|
+
let stopRuntimeConfig;
|
|
1501
|
+
if (params.runtimeConfigStore) {
|
|
1502
|
+
const runtimeConfigStore = params.runtimeConfigStore;
|
|
1503
|
+
const unsubscribeReload = runtimeConfigStore.subscribeReload(() => {
|
|
1504
|
+
persistRuntimeConfig(runtimeConfigStore.getSnapshot());
|
|
1505
|
+
});
|
|
1506
|
+
const reloadOnSighup = () => {
|
|
1507
|
+
void runtimeConfigStore.reload("sighup");
|
|
1508
|
+
};
|
|
1509
|
+
runtimeConfigStore.startWatching();
|
|
1510
|
+
process.on("SIGHUP", reloadOnSighup);
|
|
1511
|
+
stopRuntimeConfig = () => {
|
|
1512
|
+
process.off("SIGHUP", reloadOnSighup);
|
|
1513
|
+
unsubscribeReload();
|
|
1514
|
+
runtimeConfigStore.stopWatching();
|
|
1515
|
+
};
|
|
1516
|
+
logger.always(`[proxy] watching configuration generation ${initialRuntimeConfig?.generation ?? 1}; send SIGHUP to reload immediately`);
|
|
1517
|
+
}
|
|
1514
1518
|
if (params.spinner) {
|
|
1515
1519
|
params.spinner.succeed(chalk.green("Claude proxy started successfully"));
|
|
1516
1520
|
}
|
|
1517
1521
|
const isDev = params.argv.dev ?? false;
|
|
1518
1522
|
const normalizedHost = params.host === "0.0.0.0" ? "localhost" : params.host;
|
|
1519
1523
|
const url = `http://${normalizedHost}:${params.port}`;
|
|
1520
|
-
printProxyBanner(url,
|
|
1524
|
+
printProxyBanner(url, activeStrategy);
|
|
1521
1525
|
if (isDev) {
|
|
1522
1526
|
logger.always(` ${chalk.bold("Mode:")} ${chalk.magenta("dev (isolated — state in .neurolink-dev/)")}`);
|
|
1523
1527
|
}
|
|
1524
1528
|
else {
|
|
1525
|
-
logger.always(` ${chalk.bold("Mode:")} ${chalk.cyan(
|
|
1529
|
+
logger.always(` ${chalk.bold("Mode:")} ${chalk.cyan(activePassthrough ? "passthrough" : "full")}`);
|
|
1526
1530
|
}
|
|
1527
|
-
if (
|
|
1531
|
+
if (activePassthrough) {
|
|
1528
1532
|
logger.always(chalk.yellow(" ! Passthrough mode forwards client auth directly to Anthropic"));
|
|
1529
1533
|
logger.always(chalk.dim(" Stored proxy OAuth/API credentials are ignored; clients need their own valid Anthropic auth."));
|
|
1530
1534
|
}
|
|
@@ -1554,13 +1558,16 @@ async function startProxyRuntime(params) {
|
|
|
1554
1558
|
else {
|
|
1555
1559
|
logger.always(chalk.dim(" ⊘ Dev mode: skipping client auto-configuration"));
|
|
1556
1560
|
}
|
|
1557
|
-
const maintenance = startProxyBackgroundMaintenance(params.cleanupLogs, params.
|
|
1561
|
+
const maintenance = startProxyBackgroundMaintenance(params.cleanupLogs, () => params.runtimeConfigStore
|
|
1562
|
+
? params.runtimeConfigStore.getSnapshot().accountAllowlist
|
|
1563
|
+
: params.accountAllowlist);
|
|
1558
1564
|
registerProxyShutdownHandlers({
|
|
1559
1565
|
server,
|
|
1560
1566
|
host: params.host,
|
|
1561
1567
|
port: params.port,
|
|
1562
1568
|
isDev,
|
|
1563
1569
|
updaterSupervisor,
|
|
1570
|
+
stopRuntimeConfig,
|
|
1564
1571
|
...maintenance,
|
|
1565
1572
|
});
|
|
1566
1573
|
}
|
|
@@ -1587,13 +1594,34 @@ async function startProxyCommandHandler(argv) {
|
|
|
1587
1594
|
if (!isDev) {
|
|
1588
1595
|
await ensureProxyStartAllowed(spinner);
|
|
1589
1596
|
}
|
|
1597
|
+
const baseEnv = { ...process.env };
|
|
1598
|
+
const envResolution = resolveProxyEnvFile({
|
|
1599
|
+
explicitEnvFile: argv.envFile,
|
|
1600
|
+
env: baseEnv,
|
|
1601
|
+
});
|
|
1590
1602
|
const loadedEnvFile = await loadProxyStartEnv(argv, spinner);
|
|
1591
1603
|
// Reuse upstream TCP connections (longer keep-alive + bounded pool) instead
|
|
1592
1604
|
// of opening a new flow per request — cuts outbound flow churn through host
|
|
1593
1605
|
// content-filters. Runs once, after env load so it can be tuned via env.
|
|
1594
1606
|
configureProxyKeepAliveDispatcher();
|
|
1595
1607
|
const { neurolink, cleanupLogs } = await createProxyNeurolinkRuntime(devPaths?.logsDir);
|
|
1596
|
-
const
|
|
1608
|
+
const configPath = argv.config
|
|
1609
|
+
? resolve(argv.config)
|
|
1610
|
+
: join(homedir(), ".neurolink", "proxy-config.yaml");
|
|
1611
|
+
const runtimeConfigStore = await ProxyRuntimeConfigStore.create({
|
|
1612
|
+
configPath,
|
|
1613
|
+
configRequired: Boolean(argv.config),
|
|
1614
|
+
envFilePath: envResolution.path ?? join(homedir(), ".neurolink", ".env"),
|
|
1615
|
+
envFileRequired: envResolution.required,
|
|
1616
|
+
baseEnv,
|
|
1617
|
+
strategyOverride: argv.strategy,
|
|
1618
|
+
passthrough: argv.passthrough ?? false,
|
|
1619
|
+
});
|
|
1620
|
+
const initialConfig = runtimeConfigStore.getSnapshot();
|
|
1621
|
+
const { proxyConfig, strategy, modelRouter, passthrough, primaryAccountKey, accountAllowlist, } = initialConfig;
|
|
1622
|
+
if (spinner && proxyConfig) {
|
|
1623
|
+
spinner.text = `Loaded proxy config from ${configPath}`;
|
|
1624
|
+
}
|
|
1597
1625
|
if (spinner) {
|
|
1598
1626
|
spinner.text = "Configuring server...";
|
|
1599
1627
|
}
|
|
@@ -1609,6 +1637,7 @@ async function startProxyCommandHandler(argv) {
|
|
|
1609
1637
|
proxyConfig,
|
|
1610
1638
|
primaryAccountKey,
|
|
1611
1639
|
accountAllowlist,
|
|
1640
|
+
runtimeConfigStore,
|
|
1612
1641
|
});
|
|
1613
1642
|
await initializeProxyOpenTelemetry();
|
|
1614
1643
|
if (spinner) {
|
|
@@ -1627,6 +1656,7 @@ async function startProxyCommandHandler(argv) {
|
|
|
1627
1656
|
loadedEnvFile,
|
|
1628
1657
|
passthrough,
|
|
1629
1658
|
cleanupLogs,
|
|
1659
|
+
runtimeConfigStore,
|
|
1630
1660
|
});
|
|
1631
1661
|
}
|
|
1632
1662
|
catch (error) {
|
|
@@ -1810,6 +1840,9 @@ export const proxyStatusCommand = {
|
|
|
1810
1840
|
envFile: null,
|
|
1811
1841
|
fallbackChain: null,
|
|
1812
1842
|
accountAllowlist: null,
|
|
1843
|
+
configGeneration: null,
|
|
1844
|
+
configLoadedAt: null,
|
|
1845
|
+
lastConfigReloadError: null,
|
|
1813
1846
|
autoUpdateEnabled: isProxyAutoUpdateEnabled(),
|
|
1814
1847
|
updaterPid: null,
|
|
1815
1848
|
updaterRunning: false,
|
|
@@ -1830,6 +1863,9 @@ export const proxyStatusCommand = {
|
|
|
1830
1863
|
status.envFile = state.envFile ?? null;
|
|
1831
1864
|
status.fallbackChain = state.fallbackChain ?? null;
|
|
1832
1865
|
status.accountAllowlist = state.accountAllowlist ?? null;
|
|
1866
|
+
status.configGeneration = state.configGeneration ?? null;
|
|
1867
|
+
status.configLoadedAt = state.configLoadedAt ?? null;
|
|
1868
|
+
status.lastConfigReloadError = state.lastConfigReloadError ?? null;
|
|
1833
1869
|
status.updaterPid = state.updaterPid ?? null;
|
|
1834
1870
|
status.updaterRunning = state.updaterPid
|
|
1835
1871
|
? isProcessRunning(state.updaterPid)
|
|
@@ -1837,12 +1873,24 @@ export const proxyStatusCommand = {
|
|
|
1837
1873
|
}
|
|
1838
1874
|
// Fetch live stats before rendering (JSON or text)
|
|
1839
1875
|
let liveStats = null;
|
|
1876
|
+
let liveConfig = null;
|
|
1840
1877
|
if (status.running && status.url) {
|
|
1841
1878
|
try {
|
|
1842
1879
|
const statusResp = await fetch(`${status.url}/status`);
|
|
1843
1880
|
if (statusResp.ok) {
|
|
1844
1881
|
const statusData = (await statusResp.json());
|
|
1845
1882
|
liveStats = statusData.stats;
|
|
1883
|
+
liveConfig = statusData.config;
|
|
1884
|
+
if (typeof liveConfig?.generation === "number") {
|
|
1885
|
+
status.configGeneration = liveConfig.generation;
|
|
1886
|
+
}
|
|
1887
|
+
if (typeof liveConfig?.loadedAt === "string") {
|
|
1888
|
+
status.configLoadedAt = liveConfig.loadedAt;
|
|
1889
|
+
}
|
|
1890
|
+
status.lastConfigReloadError =
|
|
1891
|
+
typeof liveConfig?.lastReloadError === "string"
|
|
1892
|
+
? liveConfig.lastReloadError
|
|
1893
|
+
: null;
|
|
1846
1894
|
}
|
|
1847
1895
|
}
|
|
1848
1896
|
catch {
|
|
@@ -1850,7 +1898,7 @@ export const proxyStatusCommand = {
|
|
|
1850
1898
|
}
|
|
1851
1899
|
}
|
|
1852
1900
|
if (argv.format === "json") {
|
|
1853
|
-
logger.always(JSON.stringify({ ...status, stats: liveStats }, null, 2));
|
|
1901
|
+
logger.always(JSON.stringify({ ...status, stats: liveStats, config: liveConfig }, null, 2));
|
|
1854
1902
|
return;
|
|
1855
1903
|
}
|
|
1856
1904
|
// Text format
|
|
@@ -1864,6 +1912,15 @@ export const proxyStatusCommand = {
|
|
|
1864
1912
|
logger.always(` ${chalk.bold("URL:")} ${chalk.cyan(status.url)}`);
|
|
1865
1913
|
logger.always(` ${chalk.bold("Strategy:")} ${chalk.cyan(status.strategy)}`);
|
|
1866
1914
|
logger.always(` ${chalk.bold("Mode:")} ${chalk.cyan(status.mode ?? "full")}`);
|
|
1915
|
+
if (status.configGeneration !== null) {
|
|
1916
|
+
logger.always(` ${chalk.bold("Config:")} ${chalk.cyan(`generation ${status.configGeneration}`)}` +
|
|
1917
|
+
(status.configLoadedAt
|
|
1918
|
+
? chalk.gray(` (${status.configLoadedAt})`)
|
|
1919
|
+
: ""));
|
|
1920
|
+
}
|
|
1921
|
+
if (status.lastConfigReloadError) {
|
|
1922
|
+
logger.always(` ${chalk.bold("Config error:")} ${chalk.red(status.lastConfigReloadError)}`);
|
|
1923
|
+
}
|
|
1867
1924
|
logger.always(` ${chalk.bold("Started:")} ${chalk.cyan(status.startTime)}`);
|
|
1868
1925
|
logger.always(` ${chalk.bold("Uptime:")} ${chalk.cyan(formatUptime(status.uptime ?? 0))}`);
|
|
1869
1926
|
logger.always(` ${chalk.bold("Auto-update:")} ${status.autoUpdateEnabled ? chalk.green(status.updaterRunning ? `enabled (PID ${status.updaterPid})` : "enabled (worker unavailable)") : chalk.yellow("disabled")}`);
|
|
@@ -10,9 +10,18 @@ import type { SageMakerConfig, InvokeEndpointParams, InvokeEndpointResponse, Con
|
|
|
10
10
|
*/
|
|
11
11
|
export declare class SageMakerRuntimeClient {
|
|
12
12
|
private client;
|
|
13
|
+
private sdkModule;
|
|
13
14
|
private config;
|
|
14
15
|
private isDisposed;
|
|
15
16
|
constructor(config: SageMakerConfig);
|
|
17
|
+
/**
|
|
18
|
+
* Lazily load (and cache) the `@aws-sdk/client-sagemaker-runtime` module.
|
|
19
|
+
*/
|
|
20
|
+
private getSdk;
|
|
21
|
+
/**
|
|
22
|
+
* Lazily construct (and cache) the underlying AWS SDK client.
|
|
23
|
+
*/
|
|
24
|
+
private getClient;
|
|
16
25
|
/**
|
|
17
26
|
* Invoke a SageMaker endpoint for synchronous inference
|
|
18
27
|
*
|