@juspay/neurolink 12.12.9 → 12.12.11
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 +394 -396
- package/dist/cli/commands/proxy.d.ts +4 -0
- package/dist/cli/commands/proxy.js +60 -18
- package/dist/cli/commands/proxyAnalyze.js +8 -1
- package/dist/core/baseProvider.d.ts +0 -22
- package/dist/core/baseProvider.js +18 -81
- package/dist/providers/anthropic/client.js +4 -1
- package/dist/providers/openaiChatCompletionsBase.js +4 -1
- package/dist/proxy/bodyCaptureProcessing.d.ts +22 -0
- package/dist/proxy/bodyCaptureProcessing.js +219 -0
- package/dist/proxy/bodyCaptureWorker.d.ts +16 -0
- package/dist/proxy/bodyCaptureWorker.js +232 -0
- package/dist/proxy/bodyCaptureWorkerEntry.d.ts +1 -0
- package/dist/proxy/bodyCaptureWorkerEntry.js +34 -0
- package/dist/proxy/proxyAnalysis.js +159 -17
- package/dist/proxy/proxyLifecycle.d.ts +25 -0
- package/dist/proxy/proxyLifecycle.js +111 -5
- package/dist/proxy/proxyRequestKind.d.ts +2 -0
- package/dist/proxy/proxyRequestKind.js +6 -0
- package/dist/proxy/proxyRuntimeMetrics.d.ts +3 -0
- package/dist/proxy/proxyRuntimeMetrics.js +34 -0
- package/dist/proxy/requestLogger.d.ts +10 -6
- package/dist/proxy/requestLogger.js +99 -231
- package/dist/proxy/rollingProxyServer.js +15 -4
- package/dist/proxy/rollingWorkerProcess.d.ts +4 -0
- package/dist/proxy/rollingWorkerProcess.js +25 -8
- package/dist/proxy/rollingWorkerProtocol.d.ts +6 -0
- package/dist/proxy/rollingWorkerProtocol.js +12 -1
- package/dist/proxy/rollingWorkerSupervisor.d.ts +28 -0
- package/dist/proxy/rollingWorkerSupervisor.js +73 -25
- package/dist/proxy/socketWorkerRuntime.d.ts +5 -0
- package/dist/proxy/socketWorkerRuntime.js +19 -2
- package/dist/server/routes/codexProxyRoutes.js +39 -3
- package/dist/services/server/ai/observability/instrumentation.js +7 -1
- package/dist/types/cli.d.ts +1 -1
- package/dist/types/proxy.d.ts +69 -4
- package/package.json +1 -1
- package/dist/core/modules/GenerationHandler.d.ts +0 -145
- package/dist/core/modules/GenerationHandler.js +0 -754
|
@@ -60,6 +60,10 @@ export declare function isRollingHandoffCapable(state: ProxySupervisorState | nu
|
|
|
60
60
|
export declare function processLooksLikeProxySupervisor(pid: number, expectedStartTimeIso?: string): Promise<boolean>;
|
|
61
61
|
export declare function probeProxyHealth(host: string, port: number, timeoutMs: number): Promise<ProxyHealthProbe>;
|
|
62
62
|
export declare function mapClaudeErrorTypeToStatus(errorType?: string): number;
|
|
63
|
+
/**
|
|
64
|
+
* Assemble proxy routes with shared admission, runtime error accounting,
|
|
65
|
+
* and response tracking.
|
|
66
|
+
*/
|
|
63
67
|
export declare function createProxyStartApp(params: {
|
|
64
68
|
neurolink: ProxyNeurolinkRuntime["neurolink"];
|
|
65
69
|
modelRouter: ModelRouterInterface | undefined;
|
|
@@ -27,11 +27,12 @@ import { startProxyLogCleanupScheduler } from "../../proxy/logCleanupScheduler.j
|
|
|
27
27
|
import { anthropicAccountKeysEqual, createAccountAllowlist, isAccountAllowed, LEGACY_ANTHROPIC_ACCOUNT_KEY, normalizeAnthropicAccountKey, shouldLoadFallbackCredential, } from "../../proxy/accountSelection.js";
|
|
28
28
|
import { resolveProxyStatusAccountIdentity } from "../../proxy/codexAccountUsage.js";
|
|
29
29
|
import { beginProxyRequest, getProxyActivitySnapshot, observeProxyFinalLog, takeProxyResponseObservers, trackProxyResponse, } from "../../proxy/proxyActivity.js";
|
|
30
|
-
import { flushProxyLifecycleEvents, getProxyLifecycleLoggerSnapshot, hashProxyLifecycleSessionId, logProxyLifecycleEvent, } from "../../proxy/proxyLifecycle.js";
|
|
30
|
+
import { configureProxyLifecycleLogger, flushProxyLifecycleEvents, persistProxyLifecycleAcceptance, getProxyLifecycleLoggerSnapshot, hashProxyLifecycleSessionId, logProxyLifecycleEvent, } from "../../proxy/proxyLifecycle.js";
|
|
31
31
|
import { describeInstallFailure, getGlobalInstallArgs, isTransientInstallFailure, resolveGlobalInstaller, validateInstalledVersion, } from "../../proxy/globalInstaller.js";
|
|
32
32
|
import { startUpdaterWorkerSupervisor } from "../../proxy/updaterSupervisor.js";
|
|
33
33
|
import { openProxyWorkerLog } from "../../proxy/workerLog.js";
|
|
34
34
|
import { startRollingProxyServer } from "../../proxy/rollingProxyServer.js";
|
|
35
|
+
import { isProxyAuxiliaryRequest } from "../../proxy/proxyRequestKind.js";
|
|
35
36
|
import { spawnProxySocketWorker } from "../../proxy/rollingWorkerProcess.js";
|
|
36
37
|
import { PROXY_ROLLING_SUPERVISOR_ENV, PROXY_SOCKET_WORKER_ENV, } from "../../proxy/rollingWorkerProtocol.js";
|
|
37
38
|
import { attachSocketWorkerProcess } from "../../proxy/socketWorkerRuntime.js";
|
|
@@ -1056,7 +1057,12 @@ function redactStatusPrimaryAccount(primary) {
|
|
|
1056
1057
|
source: primary.source,
|
|
1057
1058
|
};
|
|
1058
1059
|
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Confirm durable admission and reconcile route finals with the actual
|
|
1062
|
+
* response transport lifecycle.
|
|
1063
|
+
*/
|
|
1059
1064
|
function registerProxyRequestTracking(app, requestMetadata, readiness) {
|
|
1065
|
+
/** Persist admission before dispatch and observe the response through termination. */
|
|
1060
1066
|
const trackingHandler = async (c, next) => {
|
|
1061
1067
|
const startedMonotonicMs = performance.now();
|
|
1062
1068
|
const contentLengthHeader = c.req.raw.headers.get("content-length");
|
|
@@ -1097,17 +1103,16 @@ function registerProxyRequestTracking(app, requestMetadata, readiness) {
|
|
|
1097
1103
|
// The route adapter populates model/stream/toolCount after parsing. Omit
|
|
1098
1104
|
// them at acceptance instead of publishing misleading placeholder values;
|
|
1099
1105
|
// subsequent events carry the parsed metadata under the same request ID.
|
|
1100
|
-
logProxyLifecycleEvent({
|
|
1101
|
-
event: "request_accepted",
|
|
1102
|
-
requestId: metadata.requestId,
|
|
1103
|
-
method: metadata.method,
|
|
1104
|
-
path: metadata.path,
|
|
1105
|
-
sessionHash,
|
|
1106
|
-
requestBytes,
|
|
1107
|
-
elapsedMs: 0,
|
|
1108
|
-
monotonicMs: startedMonotonicMs,
|
|
1109
|
-
});
|
|
1110
1106
|
try {
|
|
1107
|
+
await persistProxyLifecycleAcceptance({
|
|
1108
|
+
requestId: metadata.requestId,
|
|
1109
|
+
method: metadata.method,
|
|
1110
|
+
path: metadata.path,
|
|
1111
|
+
sessionHash,
|
|
1112
|
+
requestBytes,
|
|
1113
|
+
elapsedMs: 0,
|
|
1114
|
+
monotonicMs: startedMonotonicMs,
|
|
1115
|
+
});
|
|
1111
1116
|
await next();
|
|
1112
1117
|
const responseStatus = c.res.status;
|
|
1113
1118
|
logProxyLifecycleEvent({
|
|
@@ -1180,6 +1185,7 @@ function registerProxyRequestTracking(app, requestMetadata, readiness) {
|
|
|
1180
1185
|
accountingTimedOut = true;
|
|
1181
1186
|
}
|
|
1182
1187
|
const final = metadata.terminalResult;
|
|
1188
|
+
const auxiliary = isProxyAuxiliaryRequest(metadata.method, metadata.path);
|
|
1183
1189
|
const terminalOutcome = final?.terminalOutcome ??
|
|
1184
1190
|
(outcome === "stream_error" ||
|
|
1185
1191
|
metadata.terminalErrorType === "stream_error"
|
|
@@ -1188,7 +1194,9 @@ function registerProxyRequestTracking(app, requestMetadata, readiness) {
|
|
|
1188
1194
|
? "client_cancelled"
|
|
1189
1195
|
: responseStatus >= 400
|
|
1190
1196
|
? "handler_error"
|
|
1191
|
-
:
|
|
1197
|
+
: auxiliary
|
|
1198
|
+
? outcome
|
|
1199
|
+
: "unknown");
|
|
1192
1200
|
logProxyLifecycleEvent({
|
|
1193
1201
|
event: "request_terminal",
|
|
1194
1202
|
timestampMs: terminalTimestampMs,
|
|
@@ -1210,7 +1218,9 @@ function registerProxyRequestTracking(app, requestMetadata, readiness) {
|
|
|
1210
1218
|
transportOutcome: outcome,
|
|
1211
1219
|
outcomeSource: final
|
|
1212
1220
|
? "final_request"
|
|
1213
|
-
: responseStatus >= 400
|
|
1221
|
+
: responseStatus >= 400 ||
|
|
1222
|
+
(auxiliary &&
|
|
1223
|
+
(outcome === "completed" || outcome === "bodyless"))
|
|
1214
1224
|
? "http_status"
|
|
1215
1225
|
: terminalOutcome === "unknown"
|
|
1216
1226
|
? "unknown"
|
|
@@ -1219,7 +1229,7 @@ function registerProxyRequestTracking(app, requestMetadata, readiness) {
|
|
|
1219
1229
|
? "timeout"
|
|
1220
1230
|
: accountingFailed
|
|
1221
1231
|
? "observer_error"
|
|
1222
|
-
: final
|
|
1232
|
+
: final || auxiliary
|
|
1223
1233
|
? "complete"
|
|
1224
1234
|
: "missing_final",
|
|
1225
1235
|
errorType: final?.errorType ?? metadata.terminalErrorType,
|
|
@@ -1270,6 +1280,10 @@ function registerProxyRequestTracking(app, requestMetadata, readiness) {
|
|
|
1270
1280
|
app.use("/v1beta/*", trackingHandler);
|
|
1271
1281
|
app.use("/backend-api/*", trackingHandler);
|
|
1272
1282
|
}
|
|
1283
|
+
/**
|
|
1284
|
+
* Assemble proxy routes with shared admission, runtime error accounting,
|
|
1285
|
+
* and response tracking.
|
|
1286
|
+
*/
|
|
1273
1287
|
export async function createProxyStartApp(params) {
|
|
1274
1288
|
const { createClaudeProxyRoutes } = await import("../../server/routes/claudeProxyRoutes.js");
|
|
1275
1289
|
const { createOpenAIProxyRoutes } = await import("../../server/routes/openaiProxyRoutes.js");
|
|
@@ -1356,9 +1370,13 @@ export async function createProxyStartApp(params) {
|
|
|
1356
1370
|
stream: false,
|
|
1357
1371
|
toolCount: 0,
|
|
1358
1372
|
};
|
|
1359
|
-
metadata.terminalErrorType = "unhandled_proxy_error";
|
|
1360
1373
|
metadata.terminalErrorCode = getProxyRuntimeErrorCode(err);
|
|
1361
|
-
|
|
1374
|
+
const telemetryUnavailable = metadata.terminalErrorCode === "PROXY_TELEMETRY_UNAVAILABLE";
|
|
1375
|
+
const status = telemetryUnavailable ? 503 : 502;
|
|
1376
|
+
metadata.terminalErrorType = telemetryUnavailable
|
|
1377
|
+
? "telemetry_unavailable"
|
|
1378
|
+
: "unhandled_proxy_error";
|
|
1379
|
+
await recordRuntimeError(metadata, status, metadata.terminalErrorType, errMsg, {
|
|
1362
1380
|
clientMessage: "Proxy internal error",
|
|
1363
1381
|
clientErrorType: "api_error",
|
|
1364
1382
|
errorCode: metadata.terminalErrorCode,
|
|
@@ -1370,7 +1388,7 @@ export async function createProxyStartApp(params) {
|
|
|
1370
1388
|
type: "api_error",
|
|
1371
1389
|
message: "Proxy internal error",
|
|
1372
1390
|
},
|
|
1373
|
-
},
|
|
1391
|
+
}, status);
|
|
1374
1392
|
});
|
|
1375
1393
|
app.post("/internal/update-control", async (c) => {
|
|
1376
1394
|
const suppliedToken = c.req.header("x-neurolink-update-token");
|
|
@@ -2270,6 +2288,7 @@ function startProxyBackgroundMaintenance(logsDir, getAccountAllowlist) {
|
|
|
2270
2288
|
const logCleanupScheduler = startProxyLogCleanupScheduler({ logsDir });
|
|
2271
2289
|
return { refreshInterval, logCleanupScheduler };
|
|
2272
2290
|
}
|
|
2291
|
+
/** Register signal handlers and return the shared, idempotent drain-and-flush operation. */
|
|
2273
2292
|
function registerProxyShutdownHandlers(params) {
|
|
2274
2293
|
let shutdownStarted = false;
|
|
2275
2294
|
/**
|
|
@@ -2329,6 +2348,7 @@ function registerProxyShutdownHandlers(params) {
|
|
|
2329
2348
|
}
|
|
2330
2349
|
});
|
|
2331
2350
|
};
|
|
2351
|
+
/** Stop background work, drain connections, and flush pending telemetry before exit. */
|
|
2332
2352
|
const shutdown = async (signal, options) => {
|
|
2333
2353
|
if (shutdownStarted) {
|
|
2334
2354
|
return;
|
|
@@ -2359,7 +2379,9 @@ function registerProxyShutdownHandlers(params) {
|
|
|
2359
2379
|
const [usageStatsFlushResult, lifecycleFlushResult, requestLogsFlushResult,] = await Promise.allSettled([
|
|
2360
2380
|
withTimeout(usageStatsFlush, PROXY_LIFECYCLE_SHUTDOWN_TIMEOUT_MS, "Timed out flushing proxy usage statistics during shutdown"),
|
|
2361
2381
|
withTimeout(flushProxyLifecycleEvents(), PROXY_LIFECYCLE_SHUTDOWN_TIMEOUT_MS, "Timed out flushing proxy lifecycle metadata during shutdown"),
|
|
2362
|
-
|
|
2382
|
+
// This flush owns a separate bounded budget covering the body worker
|
|
2383
|
+
// deadline plus index/OTLP publication; do not truncate it to five seconds.
|
|
2384
|
+
requestLogsFlush,
|
|
2363
2385
|
]);
|
|
2364
2386
|
if (usageStatsFlushResult.status === "rejected") {
|
|
2365
2387
|
const error = usageStatsFlushResult.reason;
|
|
@@ -2413,6 +2435,7 @@ function registerProxyShutdownHandlers(params) {
|
|
|
2413
2435
|
}
|
|
2414
2436
|
return shutdown;
|
|
2415
2437
|
}
|
|
2438
|
+
/** Start the HTTP runtime and connect its worker, configuration, and shutdown lifecycles. */
|
|
2416
2439
|
async function startProxyRuntime(params) {
|
|
2417
2440
|
const socketWorker = isProxySocketWorkerProcess();
|
|
2418
2441
|
const { createAdaptorServer, serve } = await import("@hono/node-server");
|
|
@@ -2716,6 +2739,7 @@ async function startProxyRuntime(params) {
|
|
|
2716
2739
|
attachSocketWorkerProcess(server, {
|
|
2717
2740
|
generation: getProxyWorkerGeneration(),
|
|
2718
2741
|
version: PROXY_VERSION,
|
|
2742
|
+
processInstanceId: getProxyLifecycleLoggerSnapshot().processInstanceId,
|
|
2719
2743
|
onActivated: () => {
|
|
2720
2744
|
persistInitialProxyState();
|
|
2721
2745
|
void reconcileActivatedUpdate();
|
|
@@ -2726,6 +2750,10 @@ async function startProxyRuntime(params) {
|
|
|
2726
2750
|
});
|
|
2727
2751
|
}
|
|
2728
2752
|
}
|
|
2753
|
+
/**
|
|
2754
|
+
* Run the stable listener and journal worker incidents independently of
|
|
2755
|
+
* serving-process exits.
|
|
2756
|
+
*/
|
|
2729
2757
|
async function runLaunchdProxySupervisor(argv, spinner) {
|
|
2730
2758
|
await ensureProxyStartAllowed(spinner);
|
|
2731
2759
|
const entryScript = process.argv[1];
|
|
@@ -2736,8 +2764,20 @@ async function runLaunchdProxySupervisor(argv, spinner) {
|
|
|
2736
2764
|
const port = argv.port ?? 55669;
|
|
2737
2765
|
const workerArgs = process.argv.slice(2);
|
|
2738
2766
|
const supervisorStartedAt = new Date().toISOString();
|
|
2767
|
+
configureProxyLifecycleLogger({
|
|
2768
|
+
enabled: true,
|
|
2769
|
+
logDir: join(homedir(), ".neurolink", "logs"),
|
|
2770
|
+
filePrefix: "proxy-supervisor",
|
|
2771
|
+
});
|
|
2739
2772
|
let currentUpdaterPid;
|
|
2740
2773
|
const rollingServer = await startRollingProxyServer({
|
|
2774
|
+
onEvent: (event) => logProxyLifecycleEvent({
|
|
2775
|
+
event: "supervisor_event",
|
|
2776
|
+
requestId: "-",
|
|
2777
|
+
method: "-",
|
|
2778
|
+
path: "-",
|
|
2779
|
+
supervisorEvent: event,
|
|
2780
|
+
}),
|
|
2741
2781
|
host,
|
|
2742
2782
|
port,
|
|
2743
2783
|
initialVersion: PROXY_VERSION,
|
|
@@ -2817,6 +2857,7 @@ async function runLaunchdProxySupervisor(argv, spinner) {
|
|
|
2817
2857
|
}
|
|
2818
2858
|
logger.always(`[proxy-supervisor] listening on ${host}:${rollingServer.address.port} workerPid=${rollingServer.snapshot().active?.pid ?? "unknown"} version=${PROXY_VERSION}`);
|
|
2819
2859
|
let stopping = false;
|
|
2860
|
+
/** Drain the rolling workers and flush the supervisor journal before clearing ownership. */
|
|
2820
2861
|
const shutdown = async (signal) => {
|
|
2821
2862
|
if (stopping) {
|
|
2822
2863
|
return;
|
|
@@ -2826,6 +2867,7 @@ async function runLaunchdProxySupervisor(argv, spinner) {
|
|
|
2826
2867
|
process.off("SIGUSR2", activatePendingUpdate);
|
|
2827
2868
|
updaterSupervisor.stop();
|
|
2828
2869
|
await rollingServer.close();
|
|
2870
|
+
await flushProxyLifecycleEvents().catch((error) => logger.warn(String(error)));
|
|
2829
2871
|
const supervisorState = loadProxySupervisorState();
|
|
2830
2872
|
if (supervisorState?.pid === process.pid) {
|
|
2831
2873
|
clearProxySupervisorState();
|
|
@@ -5,6 +5,10 @@ function formatLatency(label, summary) {
|
|
|
5
5
|
const value = (amount) => amount === null ? "-" : amount.toFixed(1);
|
|
6
6
|
return `${label.padEnd(22)} ${String(summary.count).padStart(7)} ${value(summary.p50).padStart(9)} ${value(summary.p95).padStart(9)} ${value(summary.p99).padStart(9)} ${value(summary.max).padStart(9)}`;
|
|
7
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* Render the offline evidence report without converting missing outcomes
|
|
10
|
+
* into inferred success.
|
|
11
|
+
*/
|
|
8
12
|
function printAnalysis(report) {
|
|
9
13
|
logger.always("");
|
|
10
14
|
logger.always(chalk.bold.cyan("NeuroLink Proxy Analysis"));
|
|
@@ -33,8 +37,11 @@ function printAnalysis(report) {
|
|
|
33
37
|
}
|
|
34
38
|
}
|
|
35
39
|
logger.always(report.coverage.lifecycle
|
|
36
|
-
? ` Lifecycle: ${report.lifecycle.accepted} accepted, ${report.lifecycle.terminal} terminal, ${report.lifecycle.unsettled} unsettled`
|
|
40
|
+
? ` Lifecycle: ${report.lifecycle.accepted} accepted (${report.lifecycle.auxiliaryRequests} auxiliary), ${report.lifecycle.terminal} terminal, ${report.lifecycle.unsettled} unsettled (${report.lifecycle.unconfirmedAtWorkerExit.length} unconfirmed at worker exit)`
|
|
37
41
|
: chalk.yellow(" Lifecycle: unavailable (no lifecycle metadata)"));
|
|
42
|
+
if (report.runtime.samples > 0) {
|
|
43
|
+
logger.always(` Runtime: ${report.runtime.samples} samples, max event-loop delay ${report.runtime.maxEventLoopDelayMs ?? "unknown"}ms, max CPU ${report.runtime.maxCpuPercentOneCore?.toFixed(1) ?? "unknown"}% of one core, max host load ${report.runtime.maxHostLoad1m ?? "unknown"}`);
|
|
44
|
+
}
|
|
38
45
|
if (report.coverage.attempts) {
|
|
39
46
|
const finalRateLimits = report.coverage.finalRequests
|
|
40
47
|
? `${report.requests.finalRateLimits} final`
|
|
@@ -22,7 +22,6 @@ export declare abstract class BaseProvider implements AIProvider {
|
|
|
22
22
|
} | null): void;
|
|
23
23
|
private messageBuilder;
|
|
24
24
|
private streamHandler;
|
|
25
|
-
private generationHandler;
|
|
26
25
|
protected telemetryHandler: TelemetryHandler;
|
|
27
26
|
private utilities;
|
|
28
27
|
private readonly toolsManager;
|
|
@@ -185,30 +184,10 @@ export declare abstract class BaseProvider implements AIProvider {
|
|
|
185
184
|
* @returns Promise resolving to ModelMessage array ready for AI SDK
|
|
186
185
|
*/
|
|
187
186
|
protected buildMessagesForStream(options: StreamOptions | TextGenerationOptions): Promise<ModelMessage[]>;
|
|
188
|
-
/**
|
|
189
|
-
* Execute the generation with AI SDK - delegated to GenerationHandler
|
|
190
|
-
*/
|
|
191
|
-
private executeGeneration;
|
|
192
|
-
/**
|
|
193
|
-
* Log generation completion information - delegated to GenerationHandler
|
|
194
|
-
*/
|
|
195
|
-
private logGenerationComplete;
|
|
196
187
|
/**
|
|
197
188
|
* Record performance metrics - delegated to TelemetryHandler
|
|
198
189
|
*/
|
|
199
190
|
protected recordPerformanceMetrics(usage: RawUsageObject | undefined, responseTime: number): Promise<void>;
|
|
200
|
-
/**
|
|
201
|
-
* Extract tool information from generation result - delegated to GenerationHandler
|
|
202
|
-
*/
|
|
203
|
-
private extractToolInformation;
|
|
204
|
-
/**
|
|
205
|
-
* Format the enhanced result - delegated to GenerationHandler
|
|
206
|
-
*/
|
|
207
|
-
private formatEnhancedResult;
|
|
208
|
-
/**
|
|
209
|
-
* Analyze AI response structure and log detailed debugging information - delegated to GenerationHandler
|
|
210
|
-
*/
|
|
211
|
-
private analyzeAIResponse;
|
|
212
191
|
/**
|
|
213
192
|
* Text generation method - implements AIProvider interface
|
|
214
193
|
* Tools are always available unless explicitly disabled
|
|
@@ -281,7 +260,6 @@ export declare abstract class BaseProvider implements AIProvider {
|
|
|
281
260
|
private runGenerateInActiveContext;
|
|
282
261
|
protected handleDirectTTSSynthesis(options: TextGenerationOptions, startTime: number): Promise<EnhancedGenerateResult>;
|
|
283
262
|
private handleVideoFrameGeneration;
|
|
284
|
-
private executeStandardGenerateFlow;
|
|
285
263
|
/**
|
|
286
264
|
* Close out a turn produced by a provider's own native generate loop.
|
|
287
265
|
*
|
|
@@ -32,14 +32,13 @@ import { TimeoutError as AsyncTimeoutError, withTimeoutFn, } from "../utils/asyn
|
|
|
32
32
|
import { composeAbortSignals, createTimeoutController, TimeoutError, } from "../utils/timeout.js";
|
|
33
33
|
import { shouldDisableBuiltinTools } from "../utils/toolUtils.js";
|
|
34
34
|
import { getKeyCount, getKeysAsString } from "../utils/transformationUtils.js";
|
|
35
|
-
import { ToolExecutionRecorder
|
|
35
|
+
import { ToolExecutionRecorder } from "./toolExecutionRecorder.js";
|
|
36
36
|
import { TTS_ERROR_CODES, TTSProcessor } from "../utils/ttsProcessor.js";
|
|
37
37
|
import { executeVideoAnalysis, hasVideoFrames, } from "../utils/videoAnalysisProcessor.js";
|
|
38
38
|
import { dedupeTools } from "./toolDedup.js";
|
|
39
39
|
import { resolveToolPolicy, toolNameMatcher } from "../tools/toolPolicy.js";
|
|
40
40
|
import { applyToolGate } from "../tools/toolGate.js";
|
|
41
41
|
import { partitionToolsForDiscovery, isDiscoveryMetaTool, LARGE_CATALOG_WARN_THRESHOLD, } from "../tools/toolDiscovery.js";
|
|
42
|
-
import { GenerationHandler } from "./modules/GenerationHandler.js";
|
|
43
42
|
// Import modules for composition
|
|
44
43
|
import { MessageBuilder } from "./modules/MessageBuilder.js";
|
|
45
44
|
import { StreamHandler } from "./modules/StreamHandler.js";
|
|
@@ -137,7 +136,6 @@ export class BaseProvider {
|
|
|
137
136
|
// alone.
|
|
138
137
|
messageBuilder;
|
|
139
138
|
streamHandler;
|
|
140
|
-
generationHandler;
|
|
141
139
|
telemetryHandler;
|
|
142
140
|
utilities;
|
|
143
141
|
toolsManager;
|
|
@@ -150,7 +148,6 @@ export class BaseProvider {
|
|
|
150
148
|
this.messageBuilder = new MessageBuilder(this.providerName, this.modelName);
|
|
151
149
|
this.streamHandler = new StreamHandler(this.providerName, this.modelName);
|
|
152
150
|
this.telemetryHandler = new TelemetryHandler(this.providerName, this.modelName, this.neurolink);
|
|
153
|
-
this.generationHandler = new GenerationHandler(this.providerName, this.modelName, () => this.supportsTools(), (options, type) => this.telemetryHandler.getTelemetryConfig(options, type), (toolCalls, toolResults, options, timestamp) => this.handleToolExecutionStorage(toolCalls, toolResults, options, timestamp), { getEmitterFn: () => this.neurolink?.getEventEmitter() });
|
|
154
151
|
this.utilities = new Utilities(this.providerName, this.modelName, this.defaultTimeout, this.middlewareOptions);
|
|
155
152
|
this.toolsManager = new ToolsManager(this.providerName, this.directTools, this.neurolink, {
|
|
156
153
|
isZodSchema: (schema) => this.isZodSchema(schema),
|
|
@@ -175,7 +172,6 @@ export class BaseProvider {
|
|
|
175
172
|
this.messageBuilder = new MessageBuilder(this.providerName, this.modelName);
|
|
176
173
|
this.streamHandler = new StreamHandler(this.providerName, this.modelName);
|
|
177
174
|
this.telemetryHandler = new TelemetryHandler(this.providerName, this.modelName, this.neurolink);
|
|
178
|
-
this.generationHandler = new GenerationHandler(this.providerName, this.modelName, () => this.supportsTools(), (options, type) => this.telemetryHandler.getTelemetryConfig(options, type), (toolCalls, toolResults, options, timestamp) => this.handleToolExecutionStorage(toolCalls, toolResults, options, timestamp), { getEmitterFn: () => this.neurolink?.getEventEmitter() });
|
|
179
175
|
this.utilities = new Utilities(this.providerName, this.modelName, this.defaultTimeout, this.middlewareOptions);
|
|
180
176
|
}
|
|
181
177
|
/**
|
|
@@ -1202,42 +1198,12 @@ export class BaseProvider {
|
|
|
1202
1198
|
async buildMessagesForStream(options) {
|
|
1203
1199
|
return this.messageBuilder.buildMessagesForStream(options);
|
|
1204
1200
|
}
|
|
1205
|
-
/**
|
|
1206
|
-
* Execute the generation with AI SDK - delegated to GenerationHandler
|
|
1207
|
-
*/
|
|
1208
|
-
async executeGeneration(model, messages, tools, options) {
|
|
1209
|
-
return this.generationHandler.executeGeneration(model, messages, tools, options);
|
|
1210
|
-
}
|
|
1211
|
-
/**
|
|
1212
|
-
* Log generation completion information - delegated to GenerationHandler
|
|
1213
|
-
*/
|
|
1214
|
-
logGenerationComplete(generateResult) {
|
|
1215
|
-
this.generationHandler.logGenerationComplete(generateResult);
|
|
1216
|
-
}
|
|
1217
1201
|
/**
|
|
1218
1202
|
* Record performance metrics - delegated to TelemetryHandler
|
|
1219
1203
|
*/
|
|
1220
1204
|
async recordPerformanceMetrics(usage, responseTime) {
|
|
1221
1205
|
await this.telemetryHandler.recordPerformanceMetrics(usage, responseTime);
|
|
1222
1206
|
}
|
|
1223
|
-
/**
|
|
1224
|
-
* Extract tool information from generation result - delegated to GenerationHandler
|
|
1225
|
-
*/
|
|
1226
|
-
extractToolInformation(generateResult) {
|
|
1227
|
-
return this.generationHandler.extractToolInformation(generateResult);
|
|
1228
|
-
}
|
|
1229
|
-
/**
|
|
1230
|
-
* Format the enhanced result - delegated to GenerationHandler
|
|
1231
|
-
*/
|
|
1232
|
-
formatEnhancedResult(generateResult, tools, toolsUsed, toolExecutions, options) {
|
|
1233
|
-
return this.generationHandler.formatEnhancedResult(generateResult, tools, toolsUsed, toolExecutions, options);
|
|
1234
|
-
}
|
|
1235
|
-
/**
|
|
1236
|
-
* Analyze AI response structure and log detailed debugging information - delegated to GenerationHandler
|
|
1237
|
-
*/
|
|
1238
|
-
analyzeAIResponse(result) {
|
|
1239
|
-
this.generationHandler.analyzeAIResponse(result);
|
|
1240
|
-
}
|
|
1241
1207
|
/**
|
|
1242
1208
|
* Text generation method - implements AIProvider interface
|
|
1243
1209
|
* Tools are always available unless explicitly disabled
|
|
@@ -1398,13 +1364,28 @@ export class BaseProvider {
|
|
|
1398
1364
|
if (requestKind === "tts-direct") {
|
|
1399
1365
|
return this.handleDirectTTSSynthesis(options, startTime);
|
|
1400
1366
|
}
|
|
1401
|
-
|
|
1367
|
+
// Only `model` is used now — the video-frame path needs it. `tools`
|
|
1368
|
+
// fed the standard generate flow, which no longer exists.
|
|
1369
|
+
const { model } = await this.prepareGenerationContext(options);
|
|
1402
1370
|
const messages = await this.buildMessages(options);
|
|
1403
1371
|
const videoFrameResult = await this.handleVideoFrameGeneration(options, messages, model, startTime);
|
|
1404
1372
|
if (videoFrameResult) {
|
|
1405
1373
|
return videoFrameResult;
|
|
1406
1374
|
}
|
|
1407
|
-
|
|
1375
|
+
// Every provider that generates text overrides `generate()` and runs a
|
|
1376
|
+
// native loop. There is no shared fallback any more: the standard flow
|
|
1377
|
+
// called the ai package's `generateText`, and once that was gone the
|
|
1378
|
+
// flow could only throw. Reaching here means a provider was asked for
|
|
1379
|
+
// text without implementing it — an image or embedding provider handed
|
|
1380
|
+
// a text model, or a new subclass with no `generate()` yet.
|
|
1381
|
+
throw new NeuroLinkError({
|
|
1382
|
+
code: ERROR_CODES.INVALID_CONFIGURATION,
|
|
1383
|
+
message: `${this.providerName} cannot generate text: it does not override generate(). Every text provider implements a native generate() — see docs/plans/2026-09-03-completing-the-ai-sdk-removal.md`,
|
|
1384
|
+
category: ErrorCategory.CONFIGURATION,
|
|
1385
|
+
severity: ErrorSeverity.CRITICAL,
|
|
1386
|
+
retriable: false,
|
|
1387
|
+
context: { provider: this.providerName, model: this.modelName },
|
|
1388
|
+
});
|
|
1408
1389
|
}
|
|
1409
1390
|
catch (error) {
|
|
1410
1391
|
otelSpan.setStatus({
|
|
@@ -1537,50 +1518,6 @@ export class BaseProvider {
|
|
|
1537
1518
|
usage,
|
|
1538
1519
|
}, options, startTime);
|
|
1539
1520
|
}
|
|
1540
|
-
async executeStandardGenerateFlow(options, startTime, model, messages, tools) {
|
|
1541
|
-
// Apply a defensive default timeout when the caller didn't pass one.
|
|
1542
|
-
// Without this guard, AI SDK's generateText() will wait forever on
|
|
1543
|
-
// an upstream that accepts the connection but never produces a response
|
|
1544
|
-
// (observed against the litellm gateway when a request triggers the
|
|
1545
|
-
// team-access denial path — connection stays open, no response is sent,
|
|
1546
|
-
// and the matrix test hangs the entire suite). Callers can still pass
|
|
1547
|
-
// a larger value (e.g. video generation passes 10 min).
|
|
1548
|
-
//
|
|
1549
|
-
// A provider descriptor may declare a LARGER generate budget than the
|
|
1550
|
-
// 3-min floor (litellm: 300s — slow proxied models routinely need more
|
|
1551
|
-
// than 180s end-to-end even while streaming). The declared value only
|
|
1552
|
-
// ever raises the default, never lowers it: several descriptors carry
|
|
1553
|
-
// aspirational sub-180s numbers (openai 30s, bedrock 45s) that were
|
|
1554
|
-
// never enforced on this path, and enforcing them now would break
|
|
1555
|
-
// long-running generations that have always been allowed.
|
|
1556
|
-
const descriptorGenerateMs = PROVIDER_DESCRIPTORS_BY_NAME.get(this.providerName)?.timeouts?.generateMs;
|
|
1557
|
-
// An explicit, valid turnTimeoutMs is the caller's whole-turn contract
|
|
1558
|
-
// and owns this hard abort; `timeout` then keeps its per-model-call
|
|
1559
|
-
// meaning (it reaches the model layer via providerOptions.neurolink).
|
|
1560
|
-
// Before this, `timeout` alone bounded the ENTIRE multi-step loop, so a
|
|
1561
|
-
// caller asking for a 40-minute turn of 5-minute calls was killed at 5
|
|
1562
|
-
// minutes flat — mid-loop, dressed as "Request was aborted.".
|
|
1563
|
-
const generateResult = await this.withTurnTimeout(options, descriptorGenerateMs, (timedOptions) => this.executeGeneration(model, messages, tools, timedOptions));
|
|
1564
|
-
this.analyzeAIResponse(generateResult);
|
|
1565
|
-
this.logGenerationComplete(generateResult);
|
|
1566
|
-
const responseTime = Date.now() - startTime;
|
|
1567
|
-
const { toolsUsed, toolExecutions } = this.extractToolInformation(generateResult);
|
|
1568
|
-
// Prefer the per-call recorder's real records (params/result/timing per
|
|
1569
|
-
// execution); fall back to a conversion of the step-extraction entries
|
|
1570
|
-
// for tools the recorder could not wrap (provider-executed tools).
|
|
1571
|
-
const toolExecutionRecords = resolveToolExecutionRecords(options, toolExecutions);
|
|
1572
|
-
let enhancedResult = this.formatEnhancedResult(generateResult, tools, toolsUsed, toolExecutionRecords, options);
|
|
1573
|
-
// Recorded AFTER formatEnhancedResult so telemetry sees the same usage
|
|
1574
|
-
// the caller gets: the cross-step aggregate (totalUsage, not last-step
|
|
1575
|
-
// usage) WITH the providerMetadata cache merge applied — otherwise
|
|
1576
|
-
// providers whose cache data lives only in providerMetadata would have
|
|
1577
|
-
// their cache tokens billed at the full input rate in OTEL metrics,
|
|
1578
|
-
// diverging from analytics.cost.
|
|
1579
|
-
await this.recordPerformanceMetrics(enhancedResult.usage, responseTime);
|
|
1580
|
-
enhancedResult = await this.synthesizeAIResponseIfNeeded(enhancedResult, options);
|
|
1581
|
-
const finalResult = await this.enhanceResult(enhancedResult, options, startTime);
|
|
1582
|
-
return finalResult;
|
|
1583
|
-
}
|
|
1584
1521
|
/**
|
|
1585
1522
|
* Close out a turn produced by a provider's own native generate loop.
|
|
1586
1523
|
*
|
|
@@ -1280,7 +1280,10 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
1280
1280
|
};
|
|
1281
1281
|
},
|
|
1282
1282
|
doStream: () => {
|
|
1283
|
-
throw new Error(`${providerName}: doStream is not implemented on the delegating model
|
|
1283
|
+
throw new Error(`${providerName}: doStream is not implemented on the delegating model. ` +
|
|
1284
|
+
`NeuroLink streams through executeStream, reached via NeuroLink.stream() — ` +
|
|
1285
|
+
`use that (the browser bundle exports the NeuroLink class) rather than ` +
|
|
1286
|
+
`calling doStream on a model handle.`);
|
|
1284
1287
|
},
|
|
1285
1288
|
};
|
|
1286
1289
|
return delegatingModel;
|
|
@@ -784,7 +784,10 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
784
784
|
};
|
|
785
785
|
},
|
|
786
786
|
doStream: () => {
|
|
787
|
-
throw new Error(`${providerName}: doStream is not implemented on the delegating model
|
|
787
|
+
throw new Error(`${providerName}: doStream is not implemented on the delegating model. ` +
|
|
788
|
+
`NeuroLink streams through executeStream, reached via NeuroLink.stream() — ` +
|
|
789
|
+
`use that (the browser bundle exports the NeuroLink class) rather than ` +
|
|
790
|
+
`calling doStream on a model handle.`);
|
|
788
791
|
},
|
|
789
792
|
};
|
|
790
793
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ProxyBodyCaptureEntry, ProcessedProxyBodyCapture } from "../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Split redacted text into byte-bounded OTLP chunks without splitting
|
|
4
|
+
* encoded characters.
|
|
5
|
+
*/
|
|
6
|
+
export declare function splitUtf8StringByBytes(input: string, maxBytes: number): string[];
|
|
7
|
+
/** Shared pure redaction for replay; serving paths invoke it in the worker. */
|
|
8
|
+
export declare function prepareProxyBodyForLogging(body: unknown): {
|
|
9
|
+
value?: string;
|
|
10
|
+
bytes?: number;
|
|
11
|
+
truncated: boolean;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Expose the same header-redaction policy to replay and metadata
|
|
15
|
+
* consumers.
|
|
16
|
+
*/
|
|
17
|
+
export declare function redactProxyHeadersForLogging(headers: Record<string, string> | undefined): Record<string, string> | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Process a capture in the worker and retain redacted output when artifact
|
|
20
|
+
* persistence fails.
|
|
21
|
+
*/
|
|
22
|
+
export declare function processProxyBodyCapture(entry: ProxyBodyCaptureEntry, logDir: string): Promise<ProcessedProxyBodyCapture>;
|