@juspay/neurolink 12.19.2 → 12.20.1

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 (50) hide show
  1. package/CHANGELOG.md +2 -2
  2. package/dist/adapters/imageFormatSupport.d.ts +5 -2
  3. package/dist/adapters/imageFormatSupport.js +47 -24
  4. package/dist/browser/neurolink.min.js +394 -394
  5. package/dist/cli/commands/proxy.js +171 -25
  6. package/dist/cli/commands/telemetry.d.ts +4 -1
  7. package/dist/cli/commands/telemetry.js +113 -0
  8. package/dist/cli/loop/optionsSchema.d.ts +1 -1
  9. package/dist/cli/utils/nativeTelemetry.d.ts +19 -0
  10. package/dist/cli/utils/nativeTelemetry.js +322 -0
  11. package/dist/core/modules/MessageBuilder.js +2 -0
  12. package/dist/neurolink.js +1 -0
  13. package/dist/processors/config/fileExtensions.d.ts +8 -1
  14. package/dist/processors/config/fileExtensions.js +8 -1
  15. package/dist/proxy/bodyCapturePolicy.d.ts +6 -0
  16. package/dist/proxy/bodyCapturePolicy.js +132 -0
  17. package/dist/proxy/otelLogSink.d.ts +11 -1
  18. package/dist/proxy/otelLogSink.js +174 -27
  19. package/dist/proxy/otlpLogTransport.d.ts +4 -2
  20. package/dist/proxy/otlpLogTransport.js +22 -7
  21. package/dist/proxy/requestLogger.js +11 -3
  22. package/dist/proxy/restartControl.js +18 -3
  23. package/dist/proxy/rollingProxyServer.js +48 -1
  24. package/dist/proxy/rollingWorkerProcess.js +87 -10
  25. package/dist/proxy/rollingWorkerProtocol.js +4 -2
  26. package/dist/proxy/rollingWorkerSupervisor.js +47 -10
  27. package/dist/proxy/socketWorkerRuntime.js +90 -4
  28. package/dist/proxy/updateChecker.d.ts +3 -3
  29. package/dist/proxy/updateChecker.js +16 -9
  30. package/dist/proxy/updateCoordinator.d.ts +10 -1
  31. package/dist/proxy/updateCoordinator.js +68 -1
  32. package/dist/proxy/updateState.d.ts +2 -0
  33. package/dist/proxy/updateState.js +15 -0
  34. package/dist/rag/pipeline/RAGPipeline.js +29 -0
  35. package/dist/rag/ragIntegration.js +21 -1
  36. package/dist/types/cli.d.ts +18 -0
  37. package/dist/types/file.d.ts +10 -0
  38. package/dist/types/generate.d.ts +27 -1
  39. package/dist/types/proxy.d.ts +97 -3
  40. package/dist/types/stream.d.ts +13 -1
  41. package/dist/utils/imageProcessor.js +151 -6
  42. package/dist/utils/messageBuilder.d.ts +2 -2
  43. package/dist/utils/messageBuilder.js +23 -13
  44. package/dist/utils/multimodalOptionsBuilder.d.ts +3 -0
  45. package/dist/utils/multimodalOptionsBuilder.js +1 -0
  46. package/docs-site/static/search-index.json +1 -1
  47. package/package.json +2 -1
  48. package/scripts/observability/NATIVE-DURABLE.md +68 -0
  49. package/scripts/observability/otel-collector.proxy-native-durable.yaml +2 -2
  50. package/scripts/observability/proxy-telemetry-check.mjs +135 -50
@@ -1,3 +1,41 @@
1
+ /**
2
+ * Parse serving identity conservatively from `/status`. A rolling status is
3
+ * trusted only when the worker-local fields agree with the supervisor snapshot.
4
+ */
5
+ export function parseProxyServingWorkerIdentity(payload, rollingSupervisor) {
6
+ if (!payload || typeof payload !== "object") {
7
+ return null;
8
+ }
9
+ const value = payload;
10
+ if (typeof value.version !== "string" ||
11
+ !/^\d+\.\d+\.\d+$/.test(value.version) ||
12
+ !Number.isSafeInteger(value.pid) ||
13
+ Number(value.pid) <= 0) {
14
+ return null;
15
+ }
16
+ if (!rollingSupervisor) {
17
+ return { version: value.version, pid: Number(value.pid), generation: null };
18
+ }
19
+ const active = value.autoUpdate?.rolling?.active;
20
+ if (!active ||
21
+ active.version !== value.version ||
22
+ active.pid !== value.pid ||
23
+ !Number.isSafeInteger(active.generation) ||
24
+ Number(active.generation) <= 0) {
25
+ return null;
26
+ }
27
+ return {
28
+ version: value.version,
29
+ pid: Number(value.pid),
30
+ generation: Number(active.generation),
31
+ };
32
+ }
33
+ /** A long-running update may mutate live selection only for its original worker. */
34
+ export function isSameProxyServingWorker(expected, current) {
35
+ return (current?.version === expected.version &&
36
+ current.pid === expected.pid &&
37
+ current.generation === expected.generation);
38
+ }
1
39
  /**
2
40
  * A replacement updater starts from the newly installed package. The registry
3
41
  * then reports no newer version even when its launchd parent still runs the
@@ -72,6 +110,8 @@ function isQuiet(activity, quietThresholdMs, nowMs) {
72
110
  }
73
111
  /**
74
112
  * Prefer a naturally quiet window, then briefly drain new inference traffic.
113
+ * Automatic supervisor refresh uses allowBusyDrain=false: it fences admission
114
+ * only after a quiet observation and never waits for busy workers behind it.
75
115
  * Existing requests are never interrupted; a bounded drain failure is returned
76
116
  * to the caller so admission can be reopened and retried later.
77
117
  */
@@ -89,16 +129,43 @@ export async function waitForProxyUpdateWindow(options) {
89
129
  }
90
130
  const activity = await options.getActivity();
91
131
  if (activity && isQuiet(activity, options.quietThresholdMs, now())) {
92
- return { ready: true, draining: false };
132
+ if (options.allowBusyDrain !== false) {
133
+ return { ready: true, draining: false };
134
+ }
135
+ break;
93
136
  }
94
137
  options.onPhase?.("waiting_for_quiet", activity);
95
138
  await wait(options.pollIntervalMs);
96
139
  }
140
+ if (options.allowBusyDrain === false) {
141
+ if (options.isStopping()) {
142
+ return { ready: false, draining: false, reason: "stopping" };
143
+ }
144
+ if (!options.isParentAlive()) {
145
+ return { ready: false, draining: false, reason: "parent_stopped" };
146
+ }
147
+ const activity = await options.getActivity();
148
+ if (!activity || !isQuiet(activity, options.quietThresholdMs, now())) {
149
+ options.onPhase?.("waiting_for_quiet", activity);
150
+ return { ready: false, draining: false, reason: "busy" };
151
+ }
152
+ }
97
153
  if (!(await options.setDraining(true))) {
98
154
  // The control response may have been lost after the parent applied it.
99
155
  // Treat state as unknown/draining so the caller always attempts resume.
100
156
  return { ready: false, draining: true, reason: "drain_failed" };
101
157
  }
158
+ if (options.allowBusyDrain === false) {
159
+ // The worker's idle fence closes the observation/admission race. Any new
160
+ // cross-generation activity means resume immediately, never hold a busy
161
+ // service behind maintenance responses for the normal drain timeout.
162
+ const activity = await options.getActivity();
163
+ if (activity && isProxyRuntimeSettled(activity)) {
164
+ return { ready: true, draining: true };
165
+ }
166
+ const resumed = await options.setDraining(false);
167
+ return { ready: false, draining: !resumed, reason: "busy" };
168
+ }
102
169
  const drainDeadline = now() + options.drainWaitMs;
103
170
  while (now() < drainDeadline) {
104
171
  if (options.isStopping()) {
@@ -67,3 +67,5 @@ export declare function reconcileRunningUpdate(runningVersion: string, stateFile
67
67
  * @param stateFilePath - Override path for testing
68
68
  */
69
69
  export declare function recordCheck(latestVersion: string, stateFilePath?: string): void;
70
+ /** Preserve the last successful registry version when discovery fails. */
71
+ export declare function recordCheckFailure(message: string, stateFilePath?: string): void;
@@ -124,6 +124,12 @@ export function loadUpdateState(stateFilePath) {
124
124
  return {
125
125
  ...getDefaultUpdateState(),
126
126
  ...candidate,
127
+ lastCheckAttemptAt: typeof candidate.lastCheckAttemptAt === "string"
128
+ ? candidate.lastCheckAttemptAt
129
+ : undefined,
130
+ lastCheckError: typeof candidate.lastCheckError === "string"
131
+ ? candidate.lastCheckError.slice(0, 1_000)
132
+ : null,
127
133
  suppressedVersions: candidate.suppressedVersions ?? {},
128
134
  // Backfill order matters for state files written before `installedVersion`
129
135
  // existed. Back then `recordUpdateInstalled()` set ONLY
@@ -303,6 +309,15 @@ export function reconcileRunningUpdate(runningVersion, stateFilePath) {
303
309
  export function recordCheck(latestVersion, stateFilePath) {
304
310
  const state = loadUpdateState(stateFilePath) ?? getDefaultUpdateState();
305
311
  state.lastCheckAt = new Date().toISOString();
312
+ state.lastCheckAttemptAt = state.lastCheckAt;
313
+ state.lastCheckError = null;
306
314
  state.lastCheckVersion = latestVersion;
307
315
  saveUpdateState(state, stateFilePath);
308
316
  }
317
+ /** Preserve the last successful registry version when discovery fails. */
318
+ export function recordCheckFailure(message, stateFilePath) {
319
+ const state = loadUpdateState(stateFilePath) ?? getDefaultUpdateState();
320
+ state.lastCheckAttemptAt = new Date().toISOString();
321
+ state.lastCheckError = message.slice(0, 1_000);
322
+ saveUpdateState(state, stateFilePath);
323
+ }
@@ -26,6 +26,7 @@
26
26
  * ```
27
27
  */
28
28
  import { randomUUID } from "crypto";
29
+ import { extname } from "path";
29
30
  import { MDocument } from "../document/MDocument.js";
30
31
  import { loadDocument } from "../document/loaders.js";
31
32
  import { InMemoryVectorStore } from "../retrieval/vectorQueryTool.js";
@@ -472,6 +473,19 @@ export class RAGPipeline {
472
473
  let chunksCreated = 0;
473
474
  for (const source of sources) {
474
475
  try {
476
+ if (typeof source === "string") {
477
+ // IMAGE_EXTENSIONS includes .svg, but that constant's raster
478
+ // guarantee is scoped to the processor path (SvgProcessor). Here
479
+ // it would flow raw markup bytes into generateImageCaption (which
480
+ // base64s them into a vision call) and into the multi-modal embed
481
+ // call — both raster-only. Mirrors the identical skip in
482
+ // ragIntegration.ts's ingestion-tool entry point so the two agree.
483
+ const withoutQueryOrFragment = source.split(/[?#]/)[0];
484
+ if (extname(withoutQueryOrFragment).toLowerCase() === ".svg") {
485
+ logger.warn(`[RAGPipeline] SVG is not supported as a multi-modal image source, skipping: ${redactUrlForError(source)}`);
486
+ continue;
487
+ }
488
+ }
475
489
  // Load the image
476
490
  let imageDoc;
477
491
  if (Buffer.isBuffer(source)) {
@@ -484,6 +498,21 @@ export class RAGPipeline {
484
498
  else {
485
499
  imageDoc = await this.imageLoader.load(source);
486
500
  }
501
+ // The extension check above is necessary but not sufficient: it only
502
+ // sees the name. A source with no `.svg` suffix — an extension-less
503
+ // URL, or a local file named without one — reaches ImageLoader, which
504
+ // falls back to detecting the type from the bytes and returns
505
+ // `image/svg+xml` for markup starting `<svg`/`<?xm`. So re-check the
506
+ // resolved type AND the bytes: loadFromPath prefers a recognized
507
+ // extension over content detection, so SVG markup named `.png` keeps
508
+ // an image/png label. Skip rather than throw so SVG behaves the same
509
+ // whichever way it was identified. `supportedFormats` does not cover
510
+ // this: it is optional, and unset on the default config.
511
+ if (imageDoc.mimeType === "image/svg+xml" ||
512
+ ImageProcessor.detectImageType(imageDoc.image) === "image/svg+xml") {
513
+ logger.warn(`[RAGPipeline] SVG is not supported as a multi-modal image source, skipping: ${redactUrlForError(imageDoc.metadata.source)}`);
514
+ continue;
515
+ }
487
516
  const { supportedFormats } = this.multiModalConfig;
488
517
  if (supportedFormats && !supportedFormats.includes(imageDoc.mimeType)) {
489
518
  throw ErrorFactory.invalidParameters("ingestImages", new Error(`unsupported image MIME type: ${imageDoc.mimeType}`), { mimeType: imageDoc.mimeType, supportedFormats });
@@ -4,6 +4,7 @@ import { z } from "zod";
4
4
  import { SpanSerializer, SpanType, SpanStatus, getMetricsAggregator, } from "../observability/index.js";
5
5
  import { logger } from "../utils/logger.js";
6
6
  import { redactUrlForError } from "../utils/logSanitize.js";
7
+ import { ImageProcessor } from "../utils/imageProcessor.js";
7
8
  import { createChunker } from "./ChunkerFactory.js";
8
9
  import { createVectorQueryTool, InMemoryVectorStore, } from "./retrieval/vectorQueryTool.js";
9
10
  import { ImageLoader } from "./document/imageLoader.js";
@@ -300,6 +301,25 @@ async function _prepareRAGToolInner(ragConfig, fallbackProvider) {
300
301
  for (const { path: imgPath } of imageFiles) {
301
302
  try {
302
303
  const imageDoc = await imageLoader.load(imgPath);
304
+ // The two `ext === ".svg"` checks above are a cheap early-out, not the
305
+ // boundary: both match the NAME exactly, and the name is not what
306
+ // decides the type. ImageLoader resolves it two ways that disagree
307
+ // with a bare `.svg` comparison — `.svgz` maps to image/svg+xml
308
+ // through EXTENSION_MIME_MAP, and loadFromURL ignores the extension
309
+ // entirely and sniffs the bytes, so any image-extension URL actually
310
+ // serving SVG resolves to image/svg+xml here. A local raster suffix
311
+ // has the opposite problem: loadFromPath trusts the extension before
312
+ // the bytes, so inspect both. Otherwise `hasImage: true` below sends
313
+ // SVG markup to the raster embed call. Skip rather than throw,
314
+ // matching the early-outs and the identical guard
315
+ // in RAGPipeline.ingestImages — the two entry points are independent
316
+ // (prepareRAGTool builds its chunks here, not via ingestImages), so
317
+ // each needs its own.
318
+ if (imageDoc.mimeType === "image/svg+xml" ||
319
+ ImageProcessor.detectImageType(imageDoc.image) === "image/svg+xml") {
320
+ logger.warn(`[RAG] SVG is not supported as a RAG image source, skipping: ${redactUrlForError(imgPath)}`);
321
+ continue;
322
+ }
303
323
  // Use filename-based text representation for simple embedding
304
324
  const imageText = imageDoc.text;
305
325
  imageChunks.push({
@@ -476,7 +496,7 @@ async function _prepareRAGToolInner(ragConfig, fallbackProvider) {
476
496
  tool: aiTool,
477
497
  toolName,
478
498
  chunksIndexed: allChunks.length + imageChunks.length,
479
- filesLoaded: fileContents.length + imageFiles.length,
499
+ filesLoaded: fileContents.length + imageChunks.length,
480
500
  };
481
501
  }
482
502
  /** @internal Exported for testing only */
@@ -1044,6 +1044,24 @@ export type TelemetryStatsArgs = TelemetryCommandArgs & {
1044
1044
  byModel?: boolean;
1045
1045
  byProvider?: boolean;
1046
1046
  };
1047
+ /** Staging-only native collector migration; never activates a service. */
1048
+ export type TelemetryNativePrepareArgs = {
1049
+ collectorConfig: string;
1050
+ output: string;
1051
+ queueDirectory: string;
1052
+ compactionDirectory: string;
1053
+ bodyPort: number;
1054
+ bodyStream?: string;
1055
+ metadataQueueMib: number;
1056
+ bodyQueueMib: number;
1057
+ diskQuotaMib: number;
1058
+ metadataRetentionDays: number;
1059
+ bodyRetentionDays: number;
1060
+ };
1061
+ export type TelemetryNativeValidateArgs = {
1062
+ directory: string;
1063
+ collectorBin: string;
1064
+ };
1047
1065
  /** Available exporter names */
1048
1066
  export type ExporterName = "langfuse" | "langsmith" | "otel" | "datadog" | "sentry" | "braintrust" | "arize" | "posthog" | "laminar";
1049
1067
  /** Observability command arguments */
@@ -9,6 +9,16 @@ export type FileType = "csv" | "image" | "pdf" | "audio" | "video" | "archive" |
9
9
  * Office document types
10
10
  */
11
11
  export type OfficeDocumentType = "docx" | "pptx" | "xlsx";
12
+ /**
13
+ * Transcode target for a vision-incompatible image (`adapters/imageFormatSupport.ts`).
14
+ *
15
+ * Defaults to `"png"` everywhere a caller does not pick one: sources routed
16
+ * through that module are frequently lossless (TIFF, BMP, ICO) or already
17
+ * carry alpha, and a lossy re-encode of an image the model is about to read
18
+ * closely is the wrong default. `"jpeg"` is available for callers who know
19
+ * their source has no alpha and prefer the smaller payload.
20
+ */
21
+ export type VisionImageOutputFormat = "png" | "jpeg";
12
22
  /**
13
23
  * Outcome of a vision-compatibility pass over one image.
14
24
  *
@@ -17,7 +17,7 @@ import type { AvatarOptions, AvatarResult } from "./avatar.js";
17
17
  import type { MusicOptions, MusicResult } from "./music.js";
18
18
  import type { StandardRecord, ValidationSchema, ZodUnknownSchema } from "./aliases.js";
19
19
  import type { NeurolinkCredentials } from "./providers.js";
20
- import type { CSVProcessorOptions, FileWithMetadata, MultimodalAudioEntry } from "./file.js";
20
+ import type { CSVProcessorOptions, FileWithMetadata, MultimodalAudioEntry, VisionImageOutputFormat } from "./file.js";
21
21
  import type { WorkflowConfig } from "./workflow.js";
22
22
  import type { Schema, Tool, ToolChoice } from "./tools.js";
23
23
  import type { StepResult, LanguageModel } from "./providers.js";
@@ -156,6 +156,18 @@ export type GenerateOptions = {
156
156
  */
157
157
  maxPages?: number;
158
158
  };
159
+ /**
160
+ * Options for images that need transcoding before a vision provider can
161
+ * read them (HEIC, TIFF, BMP, ICO, JPEG 2000, AVIF — see
162
+ * `adapters/imageFormatSupport.ts`).
163
+ */
164
+ imageOptions?: {
165
+ /**
166
+ * Transcode target for an incompatible image. Defaults to `"png"` — the
167
+ * module's own default, unchanged unless a caller opts in here.
168
+ */
169
+ outputFormat?: VisionImageOutputFormat;
170
+ };
159
171
  videoOptions?: {
160
172
  /** Frames to extract. Unset lets VideoProcessor pick from the clip's duration; clamped to 100. */
161
173
  frames?: number;
@@ -1394,6 +1406,20 @@ export type TextGenerationOptions = {
1394
1406
  /** Max pages converted by the image fallback (#297); defaults to PDF_LIMITS.DEFAULT_MAX_PAGES. */
1395
1407
  maxPages?: number;
1396
1408
  };
1409
+ /**
1410
+ * Options for images that need transcoding before a vision provider can
1411
+ * read them (HEIC, TIFF, BMP, ICO, JPEG 2000, AVIF — see
1412
+ * `adapters/imageFormatSupport.ts`). Mirrors `GenerateOptions.imageOptions`;
1413
+ * this is the shape that actually reaches `MessageBuilder` for providers
1414
+ * built on `BaseProvider`.
1415
+ */
1416
+ imageOptions?: {
1417
+ /**
1418
+ * Transcode target for an incompatible image. Defaults to `"png"` — the
1419
+ * module's own default, unchanged unless a caller opts in here.
1420
+ */
1421
+ outputFormat?: VisionImageOutputFormat;
1422
+ };
1397
1423
  enableSummarization?: boolean;
1398
1424
  /**
1399
1425
  * Skip injecting tool schemas into the system prompt.
@@ -16,6 +16,11 @@ import type { ProxyContextEvidence, ProxyPreparedContext } from "./proxyContext.
16
16
  * - src/lib/server/routes/claudeProxyRoutes.ts (runtime state, deps)
17
17
  */
18
18
  import type { Counter, Histogram, Span } from "@opentelemetry/api";
19
+ import type { LogRecordExporter } from "@opentelemetry/sdk-logs";
20
+ /** HTTP exporter with an explicit bounded-shutdown cancellation boundary. */
21
+ export type ProxyOtlpLogTransport = LogRecordExporter & {
22
+ abortPending: () => void;
23
+ };
19
24
  import type { Hono } from "hono";
20
25
  import type { Ora } from "ora";
21
26
  import type { MCPToolRegistry } from "../mcp/toolRegistry.js";
@@ -726,6 +731,49 @@ export type ProxyBodyDeliveryResult = {
726
731
  maxChunkExportMs?: number;
727
732
  reason?: string;
728
733
  };
734
+ /** One-hop reference to identical redacted bytes from the same request. */
735
+ export type ProxyBodyCaptureReference = {
736
+ captureId: string;
737
+ requestId: string;
738
+ bodySha256: string;
739
+ redactedBodyBytes: number;
740
+ /** Source export settlement time, used to bound backend reconstruction. */
741
+ exportedAt: string;
742
+ };
743
+ export type ProxyBodyCapturePublication = {
744
+ delivery?: ProxyBodyDeliveryResult | {
745
+ status: "reference";
746
+ reason: "identical_redacted_payload";
747
+ } | {
748
+ status: "policy_excluded";
749
+ reason: "body_byte_budget_exhausted" | "body_byte_budget_invalid";
750
+ };
751
+ reference?: ProxyBodyCaptureReference;
752
+ };
753
+ export type ProxyBodyCapturePolicyOptions = {
754
+ bytesPerMinute?: string;
755
+ deduplicate?: boolean;
756
+ now?: () => number;
757
+ };
758
+ export type ProxyBodyCapturePolicySnapshot = {
759
+ deduplicationEnabled: boolean;
760
+ maxReferences: number;
761
+ referenceTtlMs: number;
762
+ references: number;
763
+ byteBudgetPerMinute: number | null;
764
+ invalidByteBudget: boolean;
765
+ availableBytes: number | null;
766
+ submittedBytes: number;
767
+ deduplicatedCaptures: number;
768
+ deduplicatedBytes: number;
769
+ policyExcludedCaptures: number;
770
+ policyExcludedBytes: number;
771
+ };
772
+ export type ProxyBodyDeduplicationEntry = {
773
+ reference: ProxyBodyCaptureReference;
774
+ expiresAt: number;
775
+ acknowledged: Promise<boolean>;
776
+ };
729
777
  /** One bounded body publication, tracked across exporter callbacks. */
730
778
  export type ProxyBodyPublicationProgress = {
731
779
  acknowledged: number;
@@ -828,6 +876,7 @@ export type ProxyRequestLoggerSnapshot = {
828
876
  diskEnabled?: boolean;
829
877
  otel?: ReturnType<typeof import("../proxy/otelLogSink.js").getProxyOtelLogSnapshot>;
830
878
  bodyCapture?: ProxyBodyCaptureWorkerSnapshot;
879
+ bodyCapturePolicy?: ProxyBodyCapturePolicySnapshot;
831
880
  enabled: boolean;
832
881
  requests: ProxyRequestLogSinkSnapshot;
833
882
  attempts: ProxyRequestLogSinkSnapshot;
@@ -2563,6 +2612,20 @@ export type UpdateCheckResult = {
2563
2612
  currentVersion: string;
2564
2613
  latestVersion: string;
2565
2614
  updateAvailable: boolean;
2615
+ } & ({
2616
+ checkSucceeded: true;
2617
+ checkError?: never;
2618
+ } | {
2619
+ checkSucceeded: false;
2620
+ checkError: string;
2621
+ updateAvailable: false;
2622
+ });
2623
+ /** Immutable identity of the worker an update attempt is allowed to replace. */
2624
+ export type ProxyServingWorkerIdentity = {
2625
+ version: string;
2626
+ pid: number;
2627
+ /** Rolling generation; null only for a legacy single-process service. */
2628
+ generation: number | null;
2566
2629
  };
2567
2630
  /** Result of one local proxy health probe by the updater or fail-open guard. */
2568
2631
  export type ProxyHealthProbe = {
@@ -2587,6 +2650,9 @@ export type SuppressedVersion = {
2587
2650
  export type UpdateState = {
2588
2651
  lastCheckAt: string;
2589
2652
  lastCheckVersion: string;
2653
+ /** Last attempt is distinct from the last successful registry observation. */
2654
+ lastCheckAttemptAt?: string;
2655
+ lastCheckError?: string | null;
2590
2656
  suppressedVersions: Record<string, SuppressedVersion>;
2591
2657
  /**
2592
2658
  * Last package version whose stable trampoline was successfully validated.
@@ -2621,7 +2687,7 @@ export type UpdateState = {
2621
2687
  export type ProxyUpdateWindowResult = {
2622
2688
  ready: boolean;
2623
2689
  draining: boolean;
2624
- reason?: "stopping" | "parent_stopped" | "drain_failed" | "drain_timeout";
2690
+ reason?: "stopping" | "parent_stopped" | "drain_failed" | "drain_timeout" | "busy";
2625
2691
  };
2626
2692
  /** Dependencies and timing controls for the updater's safe-window coordinator. */
2627
2693
  export type ProxyUpdateWindowOptions = {
@@ -2629,6 +2695,8 @@ export type ProxyUpdateWindowOptions = {
2629
2695
  quietWaitMs: number;
2630
2696
  drainWaitMs: number;
2631
2697
  pollIntervalMs: number;
2698
+ /** Automatic supervisor refresh must defer instead of draining busy traffic. */
2699
+ allowBusyDrain?: boolean;
2632
2700
  getActivity: () => Promise<ProxyRuntimeActivity | null>;
2633
2701
  setDraining: (draining: boolean) => Promise<boolean>;
2634
2702
  isStopping: () => boolean;
@@ -2705,6 +2773,10 @@ export type ProxyWorkerControlMessage = {
2705
2773
  } | {
2706
2774
  type: "proxy-worker:shutdown";
2707
2775
  generation: number;
2776
+ } | {
2777
+ type: "proxy-worker:socket-offer";
2778
+ generation: number;
2779
+ socketId: string;
2708
2780
  } | {
2709
2781
  type: "proxy-worker:socket-commit";
2710
2782
  generation: number;
@@ -2720,6 +2792,10 @@ export type ProxyWorkerStatusMessage = {
2720
2792
  pid: number;
2721
2793
  version: string;
2722
2794
  processInstanceId?: string;
2795
+ /** New supervisors defer sending descriptors until commit. */
2796
+ socketOfferProtocol?: "control-before-handle";
2797
+ /** New workers confirm descriptor adoption before a generation drains. */
2798
+ socketCommitProtocol?: "worker-ack";
2723
2799
  } | {
2724
2800
  type: "proxy-worker:drained";
2725
2801
  generation: number;
@@ -2738,6 +2814,11 @@ export type ProxyWorkerStatusMessage = {
2738
2814
  generation: number;
2739
2815
  pid: number;
2740
2816
  socketId: string;
2817
+ } | {
2818
+ type: "proxy-worker:socket-committed";
2819
+ generation: number;
2820
+ pid: number;
2821
+ socketId: string;
2741
2822
  } | {
2742
2823
  type: "proxy-worker:replacement-requested";
2743
2824
  generation: number;
@@ -2755,8 +2836,12 @@ export type TransferableProxySocket = Pick<import("node:net").Socket, "destroy"
2755
2836
  export type DetachableTransferableProxySocket = TransferableProxySocket & Pick<import("node:net").Socket, "off">;
2756
2837
  export type RollingWorkerHandle = {
2757
2838
  pid: number;
2839
+ /** Combined offer and commit budget, before any retry. Defaults to 60s. */
2840
+ socketTransferTimeoutMs?: number;
2758
2841
  sendControl: (message: ProxyWorkerControlMessage) => void;
2759
- sendSocket: (generation: number, socket: TransferableProxySocket, callback: (error?: Error | null) => void) => void;
2842
+ sendSocket: (generation: number, socket: TransferableProxySocket, callback: (error?: Error | null) => void,
2843
+ /** Absolute admission deadline shared by queueing and any safe retry. */
2844
+ deadlineAt?: number) => void;
2760
2845
  terminate: (signal?: NodeJS.Signals) => void;
2761
2846
  onMessage: (listener: (message: ProxyWorkerStatusMessage) => void) => () => void;
2762
2847
  onExit: (listener: (code: number | null, signal: string | null) => void) => () => void;
@@ -2853,6 +2938,9 @@ export type RollingProxyServerOptions = {
2853
2938
  shutdownTimeoutMs?: number;
2854
2939
  recoveryDelayMs?: number;
2855
2940
  maxRecoveryDelayMs?: number;
2941
+ /** Backoff after failed pressure-induced replacement, while old worker serves. */
2942
+ stallReplacementDelayMs?: number;
2943
+ maxStallReplacementDelayMs?: number;
2856
2944
  onStateChange?: (snapshot: RollingWorkerSupervisorSnapshot) => void;
2857
2945
  onEvent?: (event: RollingWorkerSupervisorEvent) => void;
2858
2946
  log?: (message: string) => void;
@@ -2879,8 +2967,12 @@ export type RollingCandidateWorker = RollingManagedWorker & {
2879
2967
  activationRequested: boolean;
2880
2968
  settle: (error?: Error) => void;
2881
2969
  };
2882
- export type RollingQueuedSocket = {
2970
+ export type RollingSocketAdmission = {
2883
2971
  socket: TransferableProxySocket;
2972
+ deadlineAt: number;
2973
+ retried: boolean;
2974
+ };
2975
+ export type RollingQueuedSocket = RollingSocketAdmission & {
2884
2976
  timeout: NodeJS.Timeout;
2885
2977
  };
2886
2978
  export type SocketWorkerRuntime = {
@@ -2896,6 +2988,8 @@ export type SocketWorkerRuntime = {
2896
2988
  };
2897
2989
  export type SocketWorkerRuntimeOptions = {
2898
2990
  onDrained?: () => void;
2991
+ /** Bound for a committed socket's first HTTP request while draining. */
2992
+ firstRequestGraceMs?: number;
2899
2993
  };
2900
2994
  /** Shape of the dynamically-imported js-yaml module. `dump` is optional —
2901
2995
  * read-only consumers (proxy config loader) only need `load`; writers
@@ -15,7 +15,7 @@ import type { AIModelProviderConfig, NeurolinkCredentials } from "./providers.js
15
15
  import type { TTSChunk, TTSOptions, TTSResult } from "./tts.js";
16
16
  import type { STTOptions, STTResult } from "./stt.js";
17
17
  import type { StandardRecord, ValidationSchema } from "./aliases.js";
18
- import type { CSVProcessorOptions, FileWithMetadata } from "./file.js";
18
+ import type { CSVProcessorOptions, FileWithMetadata, VisionImageOutputFormat } from "./file.js";
19
19
  import type { WorkflowConfig } from "./workflow.js";
20
20
  import type { LanguageModel, StepResult } from "./providers.js";
21
21
  import type { Tool, ToolChoice } from "./tools.js";
@@ -373,6 +373,18 @@ export type StreamOptions = {
373
373
  */
374
374
  maxPages?: number;
375
375
  };
376
+ /**
377
+ * Options for images that need transcoding before a vision provider can
378
+ * read them (HEIC, TIFF, BMP, ICO, JPEG 2000, AVIF — see
379
+ * `adapters/imageFormatSupport.ts`). Mirrors `GenerateOptions.imageOptions`.
380
+ */
381
+ imageOptions?: {
382
+ /**
383
+ * Transcode target for an incompatible image. Defaults to `"png"` — the
384
+ * module's own default, unchanged unless a caller opts in here.
385
+ */
386
+ outputFormat?: VisionImageOutputFormat;
387
+ };
376
388
  videoOptions?: {
377
389
  /** Frames to extract. Unset lets VideoProcessor pick from the clip's duration; clamped to 100. */
378
390
  frames?: number;