@juspay/neurolink 10.4.1 → 10.4.3
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 +386 -389
- package/dist/cli/commands/proxy.d.ts +1 -1
- package/dist/cli/commands/proxy.js +124 -56
- package/dist/cli/commands/proxyAnalyze.js +10 -1
- package/dist/lib/providers/anthropic.js +18 -1
- package/dist/lib/providers/googleAiStudio.js +18 -1
- package/dist/lib/providers/googleNativeGemini3.d.ts +19 -1
- package/dist/lib/providers/googleNativeGemini3.js +61 -4
- package/dist/lib/providers/googleVertex.d.ts +48 -0
- package/dist/lib/providers/googleVertex.js +204 -108
- package/dist/lib/providers/openaiChatCompletionsBase.js +34 -2
- package/dist/lib/providers/openaiChatCompletionsClient.d.ts +11 -5
- package/dist/lib/providers/openaiChatCompletionsClient.js +14 -8
- package/dist/lib/proxy/proxyAnalysis.js +136 -12
- package/dist/lib/proxy/requestLogger.d.ts +2 -0
- package/dist/lib/proxy/requestLogger.js +72 -13
- package/dist/lib/proxy/rollingProxyServer.js +79 -8
- package/dist/lib/proxy/rollingWorkerProcess.js +20 -15
- package/dist/lib/proxy/rollingWorkerProtocol.js +3 -0
- package/dist/lib/proxy/rollingWorkerSupervisor.d.ts +1 -0
- package/dist/lib/proxy/rollingWorkerSupervisor.js +31 -11
- package/dist/lib/proxy/runtimeConfig.d.ts +1 -0
- package/dist/lib/proxy/runtimeConfig.js +20 -5
- package/dist/lib/proxy/socketWorkerRuntime.js +41 -13
- package/dist/lib/server/routes/claudeProxyRoutes.js +63 -75
- package/dist/lib/tools/toolDiscovery.d.ts +25 -3
- package/dist/lib/tools/toolDiscovery.js +76 -4
- package/dist/lib/types/proxy.d.ts +31 -1
- package/dist/lib/types/toolResolution.d.ts +10 -0
- package/dist/providers/anthropic.js +18 -1
- package/dist/providers/googleAiStudio.js +18 -1
- package/dist/providers/googleNativeGemini3.d.ts +19 -1
- package/dist/providers/googleNativeGemini3.js +61 -4
- package/dist/providers/googleVertex.d.ts +48 -0
- package/dist/providers/googleVertex.js +204 -108
- package/dist/providers/openaiChatCompletionsBase.js +34 -2
- package/dist/providers/openaiChatCompletionsClient.d.ts +11 -5
- package/dist/providers/openaiChatCompletionsClient.js +14 -8
- package/dist/proxy/proxyAnalysis.js +136 -12
- package/dist/proxy/requestLogger.d.ts +2 -0
- package/dist/proxy/requestLogger.js +72 -13
- package/dist/proxy/rollingProxyServer.js +79 -8
- package/dist/proxy/rollingWorkerProcess.js +20 -15
- package/dist/proxy/rollingWorkerProtocol.js +3 -0
- package/dist/proxy/rollingWorkerSupervisor.d.ts +1 -0
- package/dist/proxy/rollingWorkerSupervisor.js +31 -11
- package/dist/proxy/runtimeConfig.d.ts +1 -0
- package/dist/proxy/runtimeConfig.js +20 -5
- package/dist/proxy/socketWorkerRuntime.js +41 -13
- package/dist/server/routes/claudeProxyRoutes.js +63 -75
- package/dist/tools/toolDiscovery.d.ts +25 -3
- package/dist/tools/toolDiscovery.js +76 -4
- package/dist/types/proxy.d.ts +31 -1
- package/dist/types/toolResolution.d.ts +10 -0
- package/package.json +1 -1
|
@@ -22,7 +22,7 @@ import { ProxyRuntimeConfigStore } from "../../lib/proxy/runtimeConfig.js";
|
|
|
22
22
|
* string) recorded when the supervisor we expect at `pid` was launched.
|
|
23
23
|
* When provided, this is cross-checked against `pid`'s actual OS-reported
|
|
24
24
|
* start time (see {@link getProcessStartTime}) — the args match alone
|
|
25
|
-
* (
|
|
25
|
+
* (the `neurolink proxy start` command is present) is not airtight, since an
|
|
26
26
|
* unrelated process (e.g. a shell running this very test suite, or a
|
|
27
27
|
* coincidentally-named script) could match it too. A pid recycled by such
|
|
28
28
|
* a process would have a start time far from the recorded supervisor
|
|
@@ -187,7 +187,7 @@ async function getProcessStartTime(pid) {
|
|
|
187
187
|
* string) recorded when the supervisor we expect at `pid` was launched.
|
|
188
188
|
* When provided, this is cross-checked against `pid`'s actual OS-reported
|
|
189
189
|
* start time (see {@link getProcessStartTime}) — the args match alone
|
|
190
|
-
* (
|
|
190
|
+
* (the `neurolink proxy start` command is present) is not airtight, since an
|
|
191
191
|
* unrelated process (e.g. a shell running this very test suite, or a
|
|
192
192
|
* coincidentally-named script) could match it too. A pid recycled by such
|
|
193
193
|
* a process would have a start time far from the recorded supervisor
|
|
@@ -215,9 +215,9 @@ export async function processLooksLikeProxySupervisor(pid, expectedStartTimeIso)
|
|
|
215
215
|
// "neurolink", an editor with a neurolink file open) would match, and a
|
|
216
216
|
// stale/recycled pid running such a process could then be SIGTERM'd/
|
|
217
217
|
// SIGKILL'd by `ensureSupervisorStoppedBeforeClear` during uninstall.
|
|
218
|
-
// Require the actual proxy-supervisor invocation
|
|
219
|
-
//
|
|
220
|
-
if (
|
|
218
|
+
// Require the actual proxy-supervisor invocation. Commands such as
|
|
219
|
+
// `neurolink proxy status` must never be mistaken for the listener owner.
|
|
220
|
+
if (!/\bneurolink(?:-proxy)?\b.*\bproxy\s+start\b/i.test(args)) {
|
|
221
221
|
return false;
|
|
222
222
|
}
|
|
223
223
|
if (!expectedStartTimeIso) {
|
|
@@ -1556,6 +1556,80 @@ export async function createProxyStartApp(params) {
|
|
|
1556
1556
|
});
|
|
1557
1557
|
const primaryAccount = await resolveStatusPrimaryAccount(activeProxyConfig);
|
|
1558
1558
|
const activeUpdaterPid = supervisorState?.updaterPid ?? runtimeState?.updaterPid;
|
|
1559
|
+
const accountRows = Object.values(stats.accounts).map((account) => {
|
|
1560
|
+
const normalizedKey = normalizeAnthropicAccountKey(account.label);
|
|
1561
|
+
const isLegacyAccount = account.type === "oauth" && account.label === legacyAccountLabel;
|
|
1562
|
+
const accountKey = storedAccountKeys.has(normalizedKey)
|
|
1563
|
+
? normalizedKey
|
|
1564
|
+
: isLegacyAccount
|
|
1565
|
+
? LEGACY_ANTHROPIC_ACCOUNT_KEY
|
|
1566
|
+
: account.label === "env"
|
|
1567
|
+
? ENV_ANTHROPIC_ACCOUNT_KEY
|
|
1568
|
+
: normalizedKey;
|
|
1569
|
+
const isStored = storedAccountKeys.has(accountKey) || isLegacyAccount;
|
|
1570
|
+
const cooling = (cooldowns[accountKey]?.coolingUntil ?? 0) > now;
|
|
1571
|
+
const accountStatus = disabledAccountKeys.has(accountKey)
|
|
1572
|
+
? "disabled"
|
|
1573
|
+
: !isAccountAllowed(accountKey, activeAccountAllowlist)
|
|
1574
|
+
? "excluded"
|
|
1575
|
+
: accountInventoryLoaded && account.type === "oauth" && !isStored
|
|
1576
|
+
? "removed"
|
|
1577
|
+
: cooling
|
|
1578
|
+
? "cooling"
|
|
1579
|
+
: "active";
|
|
1580
|
+
return {
|
|
1581
|
+
label: account.label,
|
|
1582
|
+
type: account.type,
|
|
1583
|
+
attempts: account.attemptCount,
|
|
1584
|
+
requests: account.successCount + account.errorCount,
|
|
1585
|
+
success: account.successCount,
|
|
1586
|
+
errors: account.errorCount,
|
|
1587
|
+
attemptErrors: account.attemptErrorCount,
|
|
1588
|
+
rateLimits: account.rateLimitCount,
|
|
1589
|
+
transientRateLimits: account.transientRateLimitCount,
|
|
1590
|
+
quotaRateLimits: account.quotaRateLimitCount,
|
|
1591
|
+
cooling,
|
|
1592
|
+
status: accountStatus,
|
|
1593
|
+
};
|
|
1594
|
+
});
|
|
1595
|
+
const attributed = accountRows.reduce((total, account) => ({
|
|
1596
|
+
attempts: total.attempts + (account.attempts ?? 0),
|
|
1597
|
+
requests: total.requests + (account.requests ?? 0),
|
|
1598
|
+
success: total.success + (account.success ?? 0),
|
|
1599
|
+
errors: total.errors + (account.errors ?? 0),
|
|
1600
|
+
attemptErrors: total.attemptErrors + (account.attemptErrors ?? 0),
|
|
1601
|
+
rateLimits: total.rateLimits + (account.rateLimits ?? 0),
|
|
1602
|
+
transientRateLimits: total.transientRateLimits + (account.transientRateLimits ?? 0),
|
|
1603
|
+
quotaRateLimits: total.quotaRateLimits + (account.quotaRateLimits ?? 0),
|
|
1604
|
+
}), {
|
|
1605
|
+
attempts: 0,
|
|
1606
|
+
requests: 0,
|
|
1607
|
+
success: 0,
|
|
1608
|
+
errors: 0,
|
|
1609
|
+
attemptErrors: 0,
|
|
1610
|
+
rateLimits: 0,
|
|
1611
|
+
transientRateLimits: 0,
|
|
1612
|
+
quotaRateLimits: 0,
|
|
1613
|
+
});
|
|
1614
|
+
const unattributed = {
|
|
1615
|
+
attempts: Math.max(0, stats.totalAttempts - attributed.attempts),
|
|
1616
|
+
requests: Math.max(0, stats.totalRequests - attributed.requests),
|
|
1617
|
+
success: Math.max(0, stats.totalSuccess - attributed.success),
|
|
1618
|
+
errors: Math.max(0, stats.totalErrors - attributed.errors),
|
|
1619
|
+
attemptErrors: Math.max(0, stats.totalAttemptErrors - attributed.attemptErrors),
|
|
1620
|
+
rateLimits: Math.max(0, stats.totalRateLimits - attributed.rateLimits),
|
|
1621
|
+
transientRateLimits: Math.max(0, stats.totalTransientRateLimits - attributed.transientRateLimits),
|
|
1622
|
+
quotaRateLimits: Math.max(0, stats.totalQuotaRateLimits - attributed.quotaRateLimits),
|
|
1623
|
+
};
|
|
1624
|
+
if (Object.values(unattributed).some((count) => count > 0)) {
|
|
1625
|
+
accountRows.push({
|
|
1626
|
+
label: "unattributed",
|
|
1627
|
+
type: "internal",
|
|
1628
|
+
...unattributed,
|
|
1629
|
+
cooling: false,
|
|
1630
|
+
status: "unattributed",
|
|
1631
|
+
});
|
|
1632
|
+
}
|
|
1559
1633
|
return c.json({
|
|
1560
1634
|
status: "running",
|
|
1561
1635
|
ready: health.ready,
|
|
@@ -1578,42 +1652,7 @@ export async function createProxyStartApp(params) {
|
|
|
1578
1652
|
totalRateLimits: stats.totalRateLimits,
|
|
1579
1653
|
totalTransientRateLimits: stats.totalTransientRateLimits,
|
|
1580
1654
|
totalQuotaRateLimits: stats.totalQuotaRateLimits,
|
|
1581
|
-
accounts:
|
|
1582
|
-
const normalizedKey = normalizeAnthropicAccountKey(account.label);
|
|
1583
|
-
const isLegacyAccount = account.type === "oauth" && account.label === legacyAccountLabel;
|
|
1584
|
-
const accountKey = storedAccountKeys.has(normalizedKey)
|
|
1585
|
-
? normalizedKey
|
|
1586
|
-
: isLegacyAccount
|
|
1587
|
-
? LEGACY_ANTHROPIC_ACCOUNT_KEY
|
|
1588
|
-
: account.label === "env"
|
|
1589
|
-
? ENV_ANTHROPIC_ACCOUNT_KEY
|
|
1590
|
-
: normalizedKey;
|
|
1591
|
-
const isStored = storedAccountKeys.has(accountKey) || isLegacyAccount;
|
|
1592
|
-
const cooling = (cooldowns[accountKey]?.coolingUntil ?? 0) > now;
|
|
1593
|
-
const accountStatus = disabledAccountKeys.has(accountKey)
|
|
1594
|
-
? "disabled"
|
|
1595
|
-
: !isAccountAllowed(accountKey, activeAccountAllowlist)
|
|
1596
|
-
? "excluded"
|
|
1597
|
-
: accountInventoryLoaded && account.type === "oauth" && !isStored
|
|
1598
|
-
? "removed"
|
|
1599
|
-
: cooling
|
|
1600
|
-
? "cooling"
|
|
1601
|
-
: "active";
|
|
1602
|
-
return {
|
|
1603
|
-
label: account.label,
|
|
1604
|
-
type: account.type,
|
|
1605
|
-
attempts: account.attemptCount,
|
|
1606
|
-
requests: account.successCount + account.errorCount,
|
|
1607
|
-
success: account.successCount,
|
|
1608
|
-
errors: account.errorCount,
|
|
1609
|
-
attemptErrors: account.attemptErrorCount,
|
|
1610
|
-
rateLimits: account.rateLimitCount,
|
|
1611
|
-
transientRateLimits: account.transientRateLimitCount,
|
|
1612
|
-
quotaRateLimits: account.quotaRateLimitCount,
|
|
1613
|
-
cooling,
|
|
1614
|
-
status: accountStatus,
|
|
1615
|
-
};
|
|
1616
|
-
}),
|
|
1655
|
+
accounts: accountRows,
|
|
1617
1656
|
primaryAccount,
|
|
1618
1657
|
persistence: getUsageStatsPersistenceStatus(),
|
|
1619
1658
|
},
|
|
@@ -1930,9 +1969,11 @@ function registerProxyShutdownHandlers(params) {
|
|
|
1930
1969
|
}
|
|
1931
1970
|
}
|
|
1932
1971
|
const usageStatsFlush = import("../../lib/proxy/usageStats.js").then(({ flushUsageStats }) => flushUsageStats());
|
|
1933
|
-
const
|
|
1972
|
+
const requestLogsFlush = import("../../lib/proxy/requestLogger.js").then(({ flushRequestLogs }) => flushRequestLogs());
|
|
1973
|
+
const [usageStatsFlushResult, lifecycleFlushResult, requestLogsFlushResult,] = await Promise.allSettled([
|
|
1934
1974
|
withTimeout(usageStatsFlush, PROXY_LIFECYCLE_SHUTDOWN_TIMEOUT_MS, "Timed out flushing proxy usage statistics during shutdown"),
|
|
1935
1975
|
withTimeout(flushProxyLifecycleEvents(), PROXY_LIFECYCLE_SHUTDOWN_TIMEOUT_MS, "Timed out flushing proxy lifecycle metadata during shutdown"),
|
|
1976
|
+
withTimeout(requestLogsFlush, PROXY_LIFECYCLE_SHUTDOWN_TIMEOUT_MS, "Timed out flushing proxy request logs during shutdown"),
|
|
1936
1977
|
]);
|
|
1937
1978
|
if (usageStatsFlushResult.status === "rejected") {
|
|
1938
1979
|
const error = usageStatsFlushResult.reason;
|
|
@@ -1942,6 +1983,10 @@ function registerProxyShutdownHandlers(params) {
|
|
|
1942
1983
|
const error = lifecycleFlushResult.reason;
|
|
1943
1984
|
logger.debug(`[proxy] lifecycle metadata flush failed during shutdown: ${error instanceof Error ? error.message : String(error)}`);
|
|
1944
1985
|
}
|
|
1986
|
+
if (requestLogsFlushResult.status === "rejected") {
|
|
1987
|
+
const error = requestLogsFlushResult.reason;
|
|
1988
|
+
logger.warn(`[proxy] request log flush failed during shutdown: ${error instanceof Error ? error.message : String(error)}`);
|
|
1989
|
+
}
|
|
1945
1990
|
try {
|
|
1946
1991
|
const { flushOpenTelemetry, shutdownOpenTelemetry } = await import("../../lib/services/server/ai/observability/instrumentation.js");
|
|
1947
1992
|
await flushOpenTelemetry();
|
|
@@ -2136,8 +2181,27 @@ async function startProxyRuntime(params) {
|
|
|
2136
2181
|
let stopRuntimeConfig;
|
|
2137
2182
|
if (params.runtimeConfigStore) {
|
|
2138
2183
|
const runtimeConfigStore = params.runtimeConfigStore;
|
|
2139
|
-
const unsubscribeReload = runtimeConfigStore.subscribeReload(() => {
|
|
2140
|
-
|
|
2184
|
+
const unsubscribeReload = runtimeConfigStore.subscribeReload((result) => {
|
|
2185
|
+
const snapshot = runtimeConfigStore.getSnapshot();
|
|
2186
|
+
persistRuntimeConfig(snapshot);
|
|
2187
|
+
if (socketWorker &&
|
|
2188
|
+
result.applied &&
|
|
2189
|
+
result.changed &&
|
|
2190
|
+
result.environmentChanged &&
|
|
2191
|
+
process.connected &&
|
|
2192
|
+
process.send) {
|
|
2193
|
+
try {
|
|
2194
|
+
process.send({
|
|
2195
|
+
type: "proxy-worker:replacement-requested",
|
|
2196
|
+
generation: getProxyWorkerGeneration(),
|
|
2197
|
+
pid: process.pid,
|
|
2198
|
+
reason: "environment",
|
|
2199
|
+
});
|
|
2200
|
+
}
|
|
2201
|
+
catch (error) {
|
|
2202
|
+
logger.warn(`[proxy] failed to request environment replacement: ${error instanceof Error ? error.message : String(error)}`);
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2141
2205
|
});
|
|
2142
2206
|
const reloadOnSighup = () => {
|
|
2143
2207
|
void runtimeConfigStore.reload("sighup");
|
|
@@ -2646,20 +2710,23 @@ export const proxyStatusCommand = {
|
|
|
2646
2710
|
supervisorState.rolling.active?.pid === state?.pid)
|
|
2647
2711
|
: true);
|
|
2648
2712
|
if ((state || supervisorState) && (servingWorker || supervisorRunning)) {
|
|
2649
|
-
const
|
|
2650
|
-
const
|
|
2713
|
+
const servingState = servingWorker ? state : null;
|
|
2714
|
+
const activeHost = servingState?.host ?? supervisorState?.host ?? null;
|
|
2715
|
+
const activePort = servingState?.port ?? supervisorState?.port ?? null;
|
|
2651
2716
|
status.running = true;
|
|
2652
2717
|
status.pid = servingWorker && state ? state.pid : null;
|
|
2653
2718
|
status.port = activePort;
|
|
2654
2719
|
status.host = activeHost;
|
|
2655
|
-
status.mode =
|
|
2656
|
-
?
|
|
2720
|
+
status.mode = servingState
|
|
2721
|
+
? servingState.passthrough
|
|
2657
2722
|
? "passthrough"
|
|
2658
2723
|
: "full"
|
|
2659
2724
|
: null;
|
|
2660
|
-
status.strategy =
|
|
2725
|
+
status.strategy = servingState?.strategy ?? null;
|
|
2661
2726
|
status.startTime =
|
|
2662
|
-
|
|
2727
|
+
(supervisorRunning ? supervisorState?.startTime : null) ??
|
|
2728
|
+
servingState?.startTime ??
|
|
2729
|
+
null;
|
|
2663
2730
|
status.uptime = status.startTime
|
|
2664
2731
|
? Date.now() - new Date(status.startTime).getTime()
|
|
2665
2732
|
: null;
|
|
@@ -2667,17 +2734,18 @@ export const proxyStatusCommand = {
|
|
|
2667
2734
|
activeHost && activePort
|
|
2668
2735
|
? `http://${activeHost === "0.0.0.0" ? "localhost" : activeHost}:${activePort}`
|
|
2669
2736
|
: null;
|
|
2670
|
-
status.envFile =
|
|
2671
|
-
status.fallbackChain =
|
|
2672
|
-
status.accountAllowlist =
|
|
2673
|
-
status.configGeneration =
|
|
2674
|
-
status.configLoadedAt =
|
|
2675
|
-
status.lastConfigReloadError =
|
|
2737
|
+
status.envFile = servingState?.envFile ?? null;
|
|
2738
|
+
status.fallbackChain = servingState?.fallbackChain ?? null;
|
|
2739
|
+
status.accountAllowlist = servingState?.accountAllowlist ?? null;
|
|
2740
|
+
status.configGeneration = servingState?.configGeneration ?? null;
|
|
2741
|
+
status.configLoadedAt = servingState?.configLoadedAt ?? null;
|
|
2742
|
+
status.lastConfigReloadError =
|
|
2743
|
+
servingState?.lastConfigReloadError ?? null;
|
|
2676
2744
|
status.supervisorPid = supervisorPid ?? null;
|
|
2677
2745
|
status.supervisorRunning = supervisorRunning;
|
|
2678
2746
|
status.rolling = supervisorState?.rolling ?? null;
|
|
2679
2747
|
status.updaterPid =
|
|
2680
|
-
supervisorState?.updaterPid ??
|
|
2748
|
+
supervisorState?.updaterPid ?? servingState?.updaterPid ?? null;
|
|
2681
2749
|
status.updaterRunning = status.updaterPid
|
|
2682
2750
|
? isProcessRunning(status.updaterPid)
|
|
2683
2751
|
: false;
|
|
@@ -11,7 +11,7 @@ function printAnalysis(report) {
|
|
|
11
11
|
logger.always(chalk.gray("=".repeat(50)));
|
|
12
12
|
logger.always(` Since: ${chalk.cyan(report.since)}`);
|
|
13
13
|
logger.always(` Logs: ${chalk.cyan(report.logsDir)}`);
|
|
14
|
-
logger.always(` Files: ${report.files.requests} request, ${report.files.attempts} attempt, ${report.files.lifecycle} lifecycle`);
|
|
14
|
+
logger.always(` Files: ${report.files.requests} request, ${report.files.attempts} attempt, ${report.files.lifecycle} lifecycle, ${report.files.debug} debug`);
|
|
15
15
|
logger.always("");
|
|
16
16
|
logger.always(chalk.bold(" Reliability"));
|
|
17
17
|
logger.always(report.coverage.finalRequests
|
|
@@ -68,6 +68,15 @@ function printAnalysis(report) {
|
|
|
68
68
|
logger.always("");
|
|
69
69
|
logger.always(chalk.bold(" Data Quality"));
|
|
70
70
|
logger.always(` ${report.dataQuality.linesRead} lines scanned, ${report.dataQuality.malformedLines} malformed, ${report.dataQuality.unsupportedLifecycleLines} unsupported lifecycle, ${report.dataQuality.lifecycleSequenceGaps} sequence gaps, ${report.dataQuality.lifecycleSequenceDuplicates} duplicates`);
|
|
71
|
+
for (const [stream, range] of Object.entries(report.dataQuality.streams)) {
|
|
72
|
+
if (range.observedFrom) {
|
|
73
|
+
logger.always(` ${stream}: ${range.observedFrom} to ${range.observedTo}${range.startsAtOrBeforeRequestedWindow ? "" : chalk.yellow(" (starts after requested window)")}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const artifacts = report.dataQuality.bodyArtifacts;
|
|
77
|
+
if (artifacts.capturesIndexed > 0) {
|
|
78
|
+
logger.always(` Body artifacts: ${artifacts.artifactsPresent}/${artifacts.artifactsReferenced} present, ${artifacts.artifactsMissing} missing, ${artifacts.writeFailures} write failures, ${artifacts.invalidPaths} invalid paths, ${artifacts.truncatedCaptures} truncated captures`);
|
|
79
|
+
}
|
|
71
80
|
if (report.accounts.length > 0) {
|
|
72
81
|
logger.always("");
|
|
73
82
|
logger.always(chalk.bold(" Accounts"));
|
|
@@ -15,6 +15,7 @@ import { logger } from "../utils/logger.js";
|
|
|
15
15
|
import { redactUrlCredentials } from "../utils/logSanitize.js";
|
|
16
16
|
import { ANTHROPIC_MAX_CACHE_BREAKPOINTS, applyAnthropicHistoryCacheBreakpoints, countAnthropicCacheMarkers, } from "../utils/anthropicCacheBreakpoints.js";
|
|
17
17
|
import { calculateCost } from "../utils/pricing.js";
|
|
18
|
+
import { resolveDeferredTool } from "../tools/toolDiscovery.js";
|
|
18
19
|
import { createAnthropicConfig, getProviderModel, validateApiKey, } from "../utils/providerConfig.js";
|
|
19
20
|
import { composeAbortSignals, createTimeoutController, mergeAbortSignals, TimeoutError, } from "../utils/timeout.js";
|
|
20
21
|
import { resolveToolChoice } from "../utils/toolChoice.js";
|
|
@@ -1400,6 +1401,18 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
1400
1401
|
let totalCacheWrite = 0;
|
|
1401
1402
|
let lastStop = null;
|
|
1402
1403
|
for (let step = 0; step < maxSteps; step++) {
|
|
1404
|
+
// Mid-turn discovery sync: search_tools (tools.discovery) hydrates
|
|
1405
|
+
// new tools into toolsRecord between steps; Claude only calls tools
|
|
1406
|
+
// declared in the request, so advertise them now (`params` below
|
|
1407
|
+
// rebuilds from anthropicTools every step).
|
|
1408
|
+
if (anthropicTools) {
|
|
1409
|
+
const declared = new Set(anthropicTools.map((t) => t.name));
|
|
1410
|
+
const hydrated = Object.fromEntries(Object.entries(toolsRecord).filter(([name]) => !declared.has(name)));
|
|
1411
|
+
if (Object.keys(hydrated).length > 0) {
|
|
1412
|
+
anthropicTools.push(...(toolsToAnthropic(hydrated) ?? []));
|
|
1413
|
+
logger.info(`[Anthropic] ${Object.keys(hydrated).length} tool(s) hydrated mid-turn via discovery: ${Object.keys(hydrated).join(", ")}`);
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1403
1416
|
// Prompt-cache parity with the native Vertex+Claude path — rolling
|
|
1404
1417
|
// history breakpoints, re-applied per step so the stable prefix
|
|
1405
1418
|
// stays byte-identical while the breakpoint follows the growing
|
|
@@ -1566,7 +1579,11 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
1566
1579
|
args,
|
|
1567
1580
|
});
|
|
1568
1581
|
toolsUsed.push(acc.name);
|
|
1569
|
-
|
|
1582
|
+
// Live record lookup, then deferred-catalog auto-hydration: with
|
|
1583
|
+
// tools.discovery on, the model may call a cataloged tool it never
|
|
1584
|
+
// loaded via search_tools — a real tool, not a hallucination.
|
|
1585
|
+
const tool = (toolsRecord[acc.name] ??
|
|
1586
|
+
resolveDeferredTool(toolsRecord, acc.name));
|
|
1570
1587
|
try {
|
|
1571
1588
|
if (!tool?.execute) {
|
|
1572
1589
|
throw new Error(`Tool not found: ${acc.name}`);
|
|
@@ -10,7 +10,7 @@ import { composeAbortSignals, createTimeoutController, TimeoutError, } from "../
|
|
|
10
10
|
import { withTimeout } from "../utils/async/index.js";
|
|
11
11
|
import { estimateTokens } from "../utils/tokenEstimation.js";
|
|
12
12
|
import { transformToolExecutions } from "../utils/transformationUtils.js";
|
|
13
|
-
import { buildGeminiResponseSchema, buildNativeConfig, buildNativeToolDeclarations, collectStreamChunks, collectStreamChunksIncremental, computeMaxSteps, createTextChannel, buildUserPartsWithMultimodal, executeNativeToolCalls, extractTextFromParts, extractThoughtSignature, handleMaxStepsTermination, prependConversationMessages, pushModelResponseToHistory, DedupExecuteMap, } from "./googleNativeGemini3.js";
|
|
13
|
+
import { buildGeminiResponseSchema, buildNativeConfig, buildNativeToolDeclarations, collectStreamChunks, collectStreamChunksIncremental, computeMaxSteps, createTextChannel, buildUserPartsWithMultimodal, executeNativeToolCalls, extractTextFromParts, extractThoughtSignature, handleMaxStepsTermination, prependConversationMessages, pushModelResponseToHistory, refreshNativeToolDeclarations, DedupExecuteMap, } from "./googleNativeGemini3.js";
|
|
14
14
|
import { createProxyFetch } from "../proxy/proxyFetch.js";
|
|
15
15
|
// Google AI Live API types now imported from ../types/providerSpecific.js
|
|
16
16
|
// Import proper types for multimodal message handling
|
|
@@ -523,10 +523,12 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
523
523
|
let toolsConfig;
|
|
524
524
|
let executeMap = new DedupExecuteMap();
|
|
525
525
|
let originalNameMap = new Map();
|
|
526
|
+
let declarationsResult;
|
|
526
527
|
if (options.tools &&
|
|
527
528
|
Object.keys(options.tools).length > 0 &&
|
|
528
529
|
!options.disableTools) {
|
|
529
530
|
const result = buildNativeToolDeclarations(options.tools);
|
|
531
|
+
declarationsResult = result;
|
|
530
532
|
toolsConfig = result.toolsConfig;
|
|
531
533
|
executeMap = result.executeMap;
|
|
532
534
|
originalNameMap = result.originalNameMap;
|
|
@@ -597,6 +599,11 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
597
599
|
: new Error("Request aborted");
|
|
598
600
|
}
|
|
599
601
|
step++;
|
|
602
|
+
// Mid-turn discovery sync: advertise tools hydrated into the
|
|
603
|
+
// live record by search_tools during the previous step.
|
|
604
|
+
if (declarationsResult) {
|
|
605
|
+
refreshNativeToolDeclarations(options.tools, declarationsResult);
|
|
606
|
+
}
|
|
600
607
|
logger.debug(`[GoogleAIStudio] Native SDK step ${step}/${maxSteps}`);
|
|
601
608
|
try {
|
|
602
609
|
const rawStream = await client.models.generateContentStream({
|
|
@@ -640,6 +647,8 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
640
647
|
abortSignal: composedSignal,
|
|
641
648
|
originalNameMap,
|
|
642
649
|
toolExecutions,
|
|
650
|
+
liveTools: options.tools,
|
|
651
|
+
declarations: declarationsResult,
|
|
643
652
|
});
|
|
644
653
|
// Persist this step's tool calls/results into conversation
|
|
645
654
|
// memory. Without this, tool_call / tool_result rows never
|
|
@@ -810,11 +819,13 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
810
819
|
let toolsConfig;
|
|
811
820
|
let executeMap = new DedupExecuteMap();
|
|
812
821
|
let originalNameMap = new Map();
|
|
822
|
+
let declarationsResult;
|
|
813
823
|
const shouldUseTools = !options.disableTools;
|
|
814
824
|
if (shouldUseTools) {
|
|
815
825
|
const tools = options.tools || {};
|
|
816
826
|
if (Object.keys(tools).length > 0) {
|
|
817
827
|
const result = buildNativeToolDeclarations(tools);
|
|
828
|
+
declarationsResult = result;
|
|
818
829
|
toolsConfig = result.toolsConfig;
|
|
819
830
|
executeMap = result.executeMap;
|
|
820
831
|
originalNameMap = result.originalNameMap;
|
|
@@ -856,6 +867,10 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
856
867
|
: new Error("Request aborted");
|
|
857
868
|
}
|
|
858
869
|
step++;
|
|
870
|
+
// Mid-turn discovery sync — see the stream twin.
|
|
871
|
+
if (declarationsResult) {
|
|
872
|
+
refreshNativeToolDeclarations(options.tools, declarationsResult);
|
|
873
|
+
}
|
|
859
874
|
logger.debug(`[GoogleAIStudio] Native SDK generate step ${step}/${maxSteps}`);
|
|
860
875
|
try {
|
|
861
876
|
const stream = await client.models.generateContentStream({
|
|
@@ -894,6 +909,8 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
894
909
|
toolExecutions,
|
|
895
910
|
abortSignal: composedSignal,
|
|
896
911
|
originalNameMap,
|
|
912
|
+
liveTools: options.tools,
|
|
913
|
+
declarations: declarationsResult,
|
|
897
914
|
});
|
|
898
915
|
// Persist this step's tool calls/results into conversation memory.
|
|
899
916
|
const stepToolCalls = allToolCalls.slice(toolCallsBefore);
|
|
@@ -82,7 +82,16 @@ export declare function normalizeToolsForJsonSchemaProvider(tools: Record<string
|
|
|
82
82
|
*
|
|
83
83
|
* This handles both Zod schemas and plain JSON Schema objects for tool parameters.
|
|
84
84
|
*/
|
|
85
|
-
export declare function buildNativeToolDeclarations(tools: Record<string, Tool>): NativeToolDeclarationsResult;
|
|
85
|
+
export declare function buildNativeToolDeclarations(tools: Record<string, Tool>, reservedNames?: ReadonlySet<string>): NativeToolDeclarationsResult;
|
|
86
|
+
/**
|
|
87
|
+
* Mid-turn tool sync for the native Gemini loops that build their snapshot
|
|
88
|
+
* via buildNativeToolDeclarations. `search_tools` (tools.discovery) hydrates
|
|
89
|
+
* discovered tools into the live record between steps; without this refresh
|
|
90
|
+
* they stay invisible to the rest of the turn and every call dies as
|
|
91
|
+
* TOOL_NOT_FOUND. Mutates the snapshot in place — the request config holds
|
|
92
|
+
* `toolsConfig` by reference — and returns true when anything was added.
|
|
93
|
+
*/
|
|
94
|
+
export declare function refreshNativeToolDeclarations(liveTools: Record<string, Tool> | undefined, current: NativeToolDeclarationsResult): boolean;
|
|
86
95
|
/**
|
|
87
96
|
* Build the native @google/genai config object shared by stream and generate.
|
|
88
97
|
*
|
|
@@ -217,6 +226,15 @@ export declare function executeNativeToolCalls(logLabel: string, stepFunctionCal
|
|
|
217
226
|
}>;
|
|
218
227
|
abortSignal?: AbortSignal;
|
|
219
228
|
originalNameMap?: Map<string, string>;
|
|
229
|
+
/**
|
|
230
|
+
* Live tool record + declaration snapshot for mid-turn discovery: on a
|
|
231
|
+
* dispatch miss the executor re-resolves against `liveTools` (hydrated
|
|
232
|
+
* by search_tools, or auto-hydrated from the deferred catalog) and syncs
|
|
233
|
+
* `declarations` in place. `declarations.executeMap` must be the same
|
|
234
|
+
* map passed as the `executeMap` argument (true at every call site).
|
|
235
|
+
*/
|
|
236
|
+
liveTools?: Record<string, Tool>;
|
|
237
|
+
declarations?: NativeToolDeclarationsResult;
|
|
220
238
|
}): Promise<NativeFunctionResponse[]>;
|
|
221
239
|
/**
|
|
222
240
|
* Handle maxSteps termination by producing a final answer when the model was
|
|
@@ -15,6 +15,7 @@ import { DEFAULT_CONTEXT_GUARD_RATIO, DEFAULT_MAX_STEPS, DEFAULT_TOOL_MAX_RETRIE
|
|
|
15
15
|
import { logger } from "../utils/logger.js";
|
|
16
16
|
import { convertZodToJsonSchema, ensureNestedSchemaTypes, inlineJsonSchema, isZodSchema, normalizeJsonSchemaObject, } from "../utils/schemaConversion.js";
|
|
17
17
|
import { createNativeThinkingConfig } from "../utils/thinkingConfig.js";
|
|
18
|
+
import { resolveLiveTool } from "../tools/toolDiscovery.js";
|
|
18
19
|
import { jsonSchema as aiJsonSchema, tool as createAISDKTool, } from "../utils/tool.js";
|
|
19
20
|
// ── Functions ──
|
|
20
21
|
/** Stable, key-order-independent serialization of tool args for the dedup key. */
|
|
@@ -367,7 +368,7 @@ export function normalizeToolsForJsonSchemaProvider(tools) {
|
|
|
367
368
|
*
|
|
368
369
|
* This handles both Zod schemas and plain JSON Schema objects for tool parameters.
|
|
369
370
|
*/
|
|
370
|
-
export function buildNativeToolDeclarations(tools) {
|
|
371
|
+
export function buildNativeToolDeclarations(tools, reservedNames) {
|
|
371
372
|
const functionDeclarations = [];
|
|
372
373
|
const executeMap = new DedupExecuteMap();
|
|
373
374
|
const skippedTools = [];
|
|
@@ -380,8 +381,10 @@ export function buildNativeToolDeclarations(tools) {
|
|
|
380
381
|
// execute are still pushed to functionDeclarations). The originalNameMap
|
|
381
382
|
// lets the calling stream loop translate Google-returned function-call
|
|
382
383
|
// names back to the consumer-facing identifier so the sanitization is
|
|
383
|
-
// transport-only.
|
|
384
|
-
|
|
384
|
+
// transport-only. `reservedNames` seeds the collision set so a mid-turn
|
|
385
|
+
// refresh (see refreshNativeToolDeclarations) never re-issues a safe name
|
|
386
|
+
// already assigned by the pre-loop snapshot.
|
|
387
|
+
const usedNames = new Set(reservedNames ?? []);
|
|
385
388
|
const originalNameMap = new Map();
|
|
386
389
|
for (const [name, tool] of Object.entries(tools)) {
|
|
387
390
|
try {
|
|
@@ -442,6 +445,36 @@ export function buildNativeToolDeclarations(tools) {
|
|
|
442
445
|
originalNameMap,
|
|
443
446
|
};
|
|
444
447
|
}
|
|
448
|
+
/**
|
|
449
|
+
* Mid-turn tool sync for the native Gemini loops that build their snapshot
|
|
450
|
+
* via buildNativeToolDeclarations. `search_tools` (tools.discovery) hydrates
|
|
451
|
+
* discovered tools into the live record between steps; without this refresh
|
|
452
|
+
* they stay invisible to the rest of the turn and every call dies as
|
|
453
|
+
* TOOL_NOT_FOUND. Mutates the snapshot in place — the request config holds
|
|
454
|
+
* `toolsConfig` by reference — and returns true when anything was added.
|
|
455
|
+
*/
|
|
456
|
+
export function refreshNativeToolDeclarations(liveTools, current) {
|
|
457
|
+
if (!liveTools) {
|
|
458
|
+
return false;
|
|
459
|
+
}
|
|
460
|
+
const declaredOriginals = new Set(current.originalNameMap.values());
|
|
461
|
+
const missing = Object.entries(liveTools).filter(([name]) => !declaredOriginals.has(name));
|
|
462
|
+
if (missing.length === 0) {
|
|
463
|
+
return false;
|
|
464
|
+
}
|
|
465
|
+
const built = buildNativeToolDeclarations(Object.fromEntries(missing), new Set(current.originalNameMap.keys()));
|
|
466
|
+
current.toolsConfig[0].functionDeclarations.push(...built.toolsConfig[0].functionDeclarations);
|
|
467
|
+
for (const [safeName, originalName] of built.originalNameMap) {
|
|
468
|
+
current.originalNameMap.set(safeName, originalName);
|
|
469
|
+
}
|
|
470
|
+
for (const [safeName, execute] of built.executeMap) {
|
|
471
|
+
current.executeMap.set(safeName, execute);
|
|
472
|
+
}
|
|
473
|
+
logger.info(`[buildNativeToolDeclarations] ${missing.length} tool(s) hydrated mid-turn via discovery: ${missing
|
|
474
|
+
.map(([name]) => name)
|
|
475
|
+
.join(", ")}`);
|
|
476
|
+
return true;
|
|
477
|
+
}
|
|
445
478
|
/**
|
|
446
479
|
* Build the native @google/genai config object shared by stream and generate.
|
|
447
480
|
*
|
|
@@ -795,7 +828,31 @@ export async function executeNativeToolCalls(logLabel, stepFunctionCalls, execut
|
|
|
795
828
|
});
|
|
796
829
|
continue;
|
|
797
830
|
}
|
|
798
|
-
|
|
831
|
+
let execute = executeMap.get(call.name);
|
|
832
|
+
if (!execute && options?.declarations) {
|
|
833
|
+
// Snapshot miss: the tool may have been hydrated into the live record
|
|
834
|
+
// by search_tools within this very step batch, or the model called a
|
|
835
|
+
// deferred catalog tool directly by its advertised name.
|
|
836
|
+
const liveTool = resolveLiveTool(options.liveTools, exposedName);
|
|
837
|
+
if (liveTool?.execute) {
|
|
838
|
+
refreshNativeToolDeclarations(options.liveTools, options.declarations);
|
|
839
|
+
// NOT_FOUND strikes accrued while the tool was deferred are snapshot
|
|
840
|
+
// artifacts, not real failures — reset so the breaker starts clean.
|
|
841
|
+
failedTools.delete(call.name);
|
|
842
|
+
// The refresh registered the executor under its Google-safe name —
|
|
843
|
+
// resolve it for this dispatch (later calls use the declared name).
|
|
844
|
+
for (const [safeName, originalName] of options.declarations
|
|
845
|
+
.originalNameMap) {
|
|
846
|
+
if (originalName === exposedName) {
|
|
847
|
+
execute = executeMap.get(safeName);
|
|
848
|
+
break;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
if (execute) {
|
|
852
|
+
logger.info(`${logLabel} Tool "${exposedName}" resolved mid-turn via discovery — executing.`);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
}
|
|
799
856
|
if (execute) {
|
|
800
857
|
try {
|
|
801
858
|
// AI SDK Tool execute requires (args, options) - provide minimal options
|
|
@@ -152,6 +152,54 @@ export declare class GoogleVertexProvider extends BaseProvider {
|
|
|
152
152
|
* Create @google/genai client configured for Vertex AI
|
|
153
153
|
*/
|
|
154
154
|
private createVertexGenAIClient;
|
|
155
|
+
/**
|
|
156
|
+
* Convert one AI-SDK tool into a Vertex Gemini function declaration.
|
|
157
|
+
* Single source for the pre-loop snapshot AND the mid-turn discovery
|
|
158
|
+
* refresh, so tools hydrated by search_tools get the exact same schema
|
|
159
|
+
* treatment (inline, typed, additionalProperties stripped).
|
|
160
|
+
*/
|
|
161
|
+
private buildGeminiFunctionDeclaration;
|
|
162
|
+
/**
|
|
163
|
+
* Mid-turn tool sync for the native Gemini loops. `search_tools` hydrates
|
|
164
|
+
* discovered tools into the live `options.tools` record between steps, but
|
|
165
|
+
* the loop's declarations + executeMap are a pre-loop snapshot — without
|
|
166
|
+
* this refresh, a tool discovered this turn stays invisible to the rest of
|
|
167
|
+
* the turn and every call to it dies as TOOL_NOT_FOUND. Mutates the
|
|
168
|
+
* declaration array in place (the request config holds it by reference)
|
|
169
|
+
* and clears breaker strikes accrued while the tool was still deferred.
|
|
170
|
+
*/
|
|
171
|
+
private refreshGeminiToolDeclarations;
|
|
172
|
+
/**
|
|
173
|
+
* Dispatch-miss recovery for the native Gemini loops: the model called a
|
|
174
|
+
* name missing from the executeMap snapshot. Re-read the live record (a
|
|
175
|
+
* tool hydrated by search_tools in this very step batch) or auto-hydrate a
|
|
176
|
+
* deferred catalog tool the model called directly by its advertised name.
|
|
177
|
+
* Returns the dedup-wrapped executor, or undefined when the name is
|
|
178
|
+
* genuinely unknown — callers keep TOOL_NOT_FOUND for that case.
|
|
179
|
+
*/
|
|
180
|
+
private resolveGeminiToolOnMiss;
|
|
181
|
+
/**
|
|
182
|
+
* Convert one AI-SDK tool into an Anthropic (Claude-on-Vertex) tool
|
|
183
|
+
* declaration. Single source for the pre-loop snapshot AND the mid-turn
|
|
184
|
+
* discovery refresh — Anthropic validates input_schema as JSON Schema
|
|
185
|
+
* draft 2020-12 and rejects OpenAPI-3 dialect (`nullable: true`), so this
|
|
186
|
+
* uses the default JSON Schema target, matching the direct anthropic
|
|
187
|
+
* provider. The Gemini paths keep "openApi3".
|
|
188
|
+
*/
|
|
189
|
+
private buildAnthropicToolDeclaration;
|
|
190
|
+
/**
|
|
191
|
+
* Mid-turn tool sync for the Claude-on-Vertex loops — the Anthropic
|
|
192
|
+
* counterpart of refreshGeminiToolDeclarations. Claude only calls tools
|
|
193
|
+
* present in the request's `tools` array, so without this per-step refresh
|
|
194
|
+
* a tool discovered via search_tools is unreachable for the rest of the
|
|
195
|
+
* turn. Mutates the array in place (requestParams holds it by reference).
|
|
196
|
+
*/
|
|
197
|
+
private refreshAnthropicToolDeclarations;
|
|
198
|
+
/**
|
|
199
|
+
* Dispatch-miss recovery for the Claude-on-Vertex loops — the Anthropic
|
|
200
|
+
* counterpart of resolveGeminiToolOnMiss.
|
|
201
|
+
*/
|
|
202
|
+
private resolveAnthropicToolOnMiss;
|
|
155
203
|
/**
|
|
156
204
|
* Execute stream using native @google/genai SDK for Gemini 3 models on Vertex AI
|
|
157
205
|
* This bypasses @ai-sdk/google-vertex to properly handle thought_signature
|