@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.
Files changed (40) hide show
  1. package/CHANGELOG.md +2 -2
  2. package/dist/browser/neurolink.min.js +394 -396
  3. package/dist/cli/commands/proxy.d.ts +4 -0
  4. package/dist/cli/commands/proxy.js +60 -18
  5. package/dist/cli/commands/proxyAnalyze.js +8 -1
  6. package/dist/core/baseProvider.d.ts +0 -22
  7. package/dist/core/baseProvider.js +18 -81
  8. package/dist/providers/anthropic/client.js +4 -1
  9. package/dist/providers/openaiChatCompletionsBase.js +4 -1
  10. package/dist/proxy/bodyCaptureProcessing.d.ts +22 -0
  11. package/dist/proxy/bodyCaptureProcessing.js +219 -0
  12. package/dist/proxy/bodyCaptureWorker.d.ts +16 -0
  13. package/dist/proxy/bodyCaptureWorker.js +232 -0
  14. package/dist/proxy/bodyCaptureWorkerEntry.d.ts +1 -0
  15. package/dist/proxy/bodyCaptureWorkerEntry.js +34 -0
  16. package/dist/proxy/proxyAnalysis.js +159 -17
  17. package/dist/proxy/proxyLifecycle.d.ts +25 -0
  18. package/dist/proxy/proxyLifecycle.js +111 -5
  19. package/dist/proxy/proxyRequestKind.d.ts +2 -0
  20. package/dist/proxy/proxyRequestKind.js +6 -0
  21. package/dist/proxy/proxyRuntimeMetrics.d.ts +3 -0
  22. package/dist/proxy/proxyRuntimeMetrics.js +34 -0
  23. package/dist/proxy/requestLogger.d.ts +10 -6
  24. package/dist/proxy/requestLogger.js +99 -231
  25. package/dist/proxy/rollingProxyServer.js +15 -4
  26. package/dist/proxy/rollingWorkerProcess.d.ts +4 -0
  27. package/dist/proxy/rollingWorkerProcess.js +25 -8
  28. package/dist/proxy/rollingWorkerProtocol.d.ts +6 -0
  29. package/dist/proxy/rollingWorkerProtocol.js +12 -1
  30. package/dist/proxy/rollingWorkerSupervisor.d.ts +28 -0
  31. package/dist/proxy/rollingWorkerSupervisor.js +73 -25
  32. package/dist/proxy/socketWorkerRuntime.d.ts +5 -0
  33. package/dist/proxy/socketWorkerRuntime.js +19 -2
  34. package/dist/server/routes/codexProxyRoutes.js +39 -3
  35. package/dist/services/server/ai/observability/instrumentation.js +7 -1
  36. package/dist/types/cli.d.ts +1 -1
  37. package/dist/types/proxy.d.ts +69 -4
  38. package/package.json +1 -1
  39. package/dist/core/modules/GenerationHandler.d.ts +0 -145
  40. 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
- : "unknown");
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
- await recordRuntimeError(metadata, 502, "unhandled_proxy_error", errMsg, {
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
- }, 502);
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
- withTimeout(requestLogsFlush, PROXY_LIFECYCLE_SHUTDOWN_TIMEOUT_MS, "Timed out flushing proxy request logs during shutdown"),
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, resolveToolExecutionRecords, } from "./toolExecutionRecorder.js";
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
- const { tools, model } = await this.prepareGenerationContext(options);
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
- return await this.executeStandardGenerateFlow(options, startTime, model, messages, tools);
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 the streaming path uses executeStream directly.`);
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 the streaming path uses executeStream directly.`);
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>;