@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.
- package/CHANGELOG.md +2 -2
- package/dist/adapters/imageFormatSupport.d.ts +5 -2
- package/dist/adapters/imageFormatSupport.js +47 -24
- package/dist/browser/neurolink.min.js +394 -394
- package/dist/cli/commands/proxy.js +171 -25
- package/dist/cli/commands/telemetry.d.ts +4 -1
- package/dist/cli/commands/telemetry.js +113 -0
- package/dist/cli/loop/optionsSchema.d.ts +1 -1
- package/dist/cli/utils/nativeTelemetry.d.ts +19 -0
- package/dist/cli/utils/nativeTelemetry.js +322 -0
- package/dist/core/modules/MessageBuilder.js +2 -0
- package/dist/neurolink.js +1 -0
- package/dist/processors/config/fileExtensions.d.ts +8 -1
- package/dist/processors/config/fileExtensions.js +8 -1
- package/dist/proxy/bodyCapturePolicy.d.ts +6 -0
- package/dist/proxy/bodyCapturePolicy.js +132 -0
- package/dist/proxy/otelLogSink.d.ts +11 -1
- package/dist/proxy/otelLogSink.js +174 -27
- package/dist/proxy/otlpLogTransport.d.ts +4 -2
- package/dist/proxy/otlpLogTransport.js +22 -7
- package/dist/proxy/requestLogger.js +11 -3
- package/dist/proxy/restartControl.js +18 -3
- package/dist/proxy/rollingProxyServer.js +48 -1
- package/dist/proxy/rollingWorkerProcess.js +87 -10
- package/dist/proxy/rollingWorkerProtocol.js +4 -2
- package/dist/proxy/rollingWorkerSupervisor.js +47 -10
- package/dist/proxy/socketWorkerRuntime.js +90 -4
- package/dist/proxy/updateChecker.d.ts +3 -3
- package/dist/proxy/updateChecker.js +16 -9
- package/dist/proxy/updateCoordinator.d.ts +10 -1
- package/dist/proxy/updateCoordinator.js +68 -1
- package/dist/proxy/updateState.d.ts +2 -0
- package/dist/proxy/updateState.js +15 -0
- package/dist/rag/pipeline/RAGPipeline.js +29 -0
- package/dist/rag/ragIntegration.js +21 -1
- package/dist/types/cli.d.ts +18 -0
- package/dist/types/file.d.ts +10 -0
- package/dist/types/generate.d.ts +27 -1
- package/dist/types/proxy.d.ts +97 -3
- package/dist/types/stream.d.ts +13 -1
- package/dist/utils/imageProcessor.js +151 -6
- package/dist/utils/messageBuilder.d.ts +2 -2
- package/dist/utils/messageBuilder.js +23 -13
- package/dist/utils/multimodalOptionsBuilder.d.ts +3 -0
- package/dist/utils/multimodalOptionsBuilder.js +1 -0
- package/docs-site/static/search-index.json +1 -1
- package/package.json +2 -1
- package/scripts/observability/NATIVE-DURABLE.md +68 -0
- package/scripts/observability/otel-collector.proxy-native-durable.yaml +2 -2
- 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
|
-
|
|
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 +
|
|
499
|
+
filesLoaded: fileContents.length + imageChunks.length,
|
|
480
500
|
};
|
|
481
501
|
}
|
|
482
502
|
/** @internal Exported for testing only */
|
package/dist/types/cli.d.ts
CHANGED
|
@@ -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 */
|
package/dist/types/file.d.ts
CHANGED
|
@@ -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
|
*
|
package/dist/types/generate.d.ts
CHANGED
|
@@ -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.
|
package/dist/types/proxy.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
package/dist/types/stream.d.ts
CHANGED
|
@@ -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;
|