@juspay/neurolink 9.79.1 → 9.79.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/browser/neurolink.min.js +337 -332
- package/dist/core/modules/GenerationHandler.js +43 -1
- package/dist/core/modules/structuredOutputPolicy.d.ts +42 -6
- package/dist/core/modules/structuredOutputPolicy.js +58 -7
- package/dist/lib/core/modules/GenerationHandler.js +43 -1
- package/dist/lib/core/modules/structuredOutputPolicy.d.ts +42 -6
- package/dist/lib/core/modules/structuredOutputPolicy.js +58 -7
- package/dist/lib/processors/document/ExcelProcessor.js +9 -1
- package/dist/lib/providers/anthropic.js +37 -41
- package/dist/lib/providers/anthropicImageBlocks.d.ts +47 -0
- package/dist/lib/providers/anthropicImageBlocks.js +223 -0
- package/dist/lib/providers/googleNativeGemini3.d.ts +26 -0
- package/dist/lib/providers/googleNativeGemini3.js +48 -0
- package/dist/lib/providers/googleVertex.d.ts +16 -0
- package/dist/lib/providers/googleVertex.js +200 -24
- package/dist/lib/proxy/oauthFetch.js +26 -14
- package/dist/lib/proxy/proxyTracer.d.ts +7 -1
- package/dist/lib/proxy/proxyTracer.js +29 -0
- package/dist/lib/proxy/systemRelocation.d.ts +21 -0
- package/dist/lib/proxy/systemRelocation.js +51 -0
- package/dist/lib/server/routes/claudeProxyRoutes.js +147 -19
- package/dist/lib/types/proxy.d.ts +10 -0
- package/dist/processors/document/ExcelProcessor.js +9 -1
- package/dist/providers/anthropic.js +37 -41
- package/dist/providers/anthropicImageBlocks.d.ts +47 -0
- package/dist/providers/anthropicImageBlocks.js +222 -0
- package/dist/providers/googleNativeGemini3.d.ts +26 -0
- package/dist/providers/googleNativeGemini3.js +48 -0
- package/dist/providers/googleVertex.d.ts +16 -0
- package/dist/providers/googleVertex.js +200 -24
- package/dist/proxy/oauthFetch.js +26 -14
- package/dist/proxy/proxyTracer.d.ts +7 -1
- package/dist/proxy/proxyTracer.js +29 -0
- package/dist/proxy/systemRelocation.d.ts +21 -0
- package/dist/proxy/systemRelocation.js +50 -0
- package/dist/server/routes/claudeProxyRoutes.js +147 -19
- package/dist/types/proxy.d.ts +10 -0
- package/package.json +8 -2
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure converters from multimodal content parts into native Anthropic
|
|
3
|
+
* Messages-API content blocks (image / document).
|
|
4
|
+
*
|
|
5
|
+
* Two input shapes reach the native Anthropic surface:
|
|
6
|
+
*
|
|
7
|
+
* 1. NeuroLink/V3 multimodal parts — `{ type: "image", image }` produced by
|
|
8
|
+
* the multimodal message builder (base64, data URL, https URL, or bytes).
|
|
9
|
+
*
|
|
10
|
+
* 2. AI-SDK LanguageModel prompt parts — `{ type: "file", mediaType, data }`.
|
|
11
|
+
* This is how `ai@6` encodes BOTH images and PDFs in the prompt that the
|
|
12
|
+
* provider's `doGenerate(options.prompt)` receives. Before this module the
|
|
13
|
+
* converter only handled `type:"image"`, so on the tool-using generate
|
|
14
|
+
* path (which always normalises images to `type:"file"`) the image part
|
|
15
|
+
* was silently dropped — the model never saw the image ("no image
|
|
16
|
+
* detected"). Gemini/Vertex are immune because they read `input.images`
|
|
17
|
+
* directly in their own builders instead of going through this conversion.
|
|
18
|
+
*
|
|
19
|
+
* Media type is taken from the AI-SDK-provided `mediaType` when present, then
|
|
20
|
+
* sniffed from magic bytes, and only then defaulted — a hardcoded `image/png`
|
|
21
|
+
* default silently corrupts JPEG/GIF/WebP uploads (the Anthropic API rejects a
|
|
22
|
+
* mislabeled base64 image with HTTP 400 and the image vanishes).
|
|
23
|
+
*/
|
|
24
|
+
// The base64 image media types the Anthropic Messages API accepts are exactly
|
|
25
|
+
// `Anthropic.Messages.Base64ImageSource["media_type"]` — referenced inline below
|
|
26
|
+
// rather than redeclared as a local alias (project rule: type aliases live in
|
|
27
|
+
// src/lib/types/, and this provider-internal shape does not warrant a barrel entry).
|
|
28
|
+
const SUPPORTED_IMAGE_MEDIA_TYPES = new Set([
|
|
29
|
+
"image/jpeg",
|
|
30
|
+
"image/png",
|
|
31
|
+
"image/gif",
|
|
32
|
+
"image/webp",
|
|
33
|
+
]);
|
|
34
|
+
/** Map a caller-provided MIME hint onto a supported image media type (or undefined). */
|
|
35
|
+
const normalizeImageMediaType = (hint) => {
|
|
36
|
+
if (!hint) {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
const h = hint.toLowerCase().split(";")[0].trim();
|
|
40
|
+
if (h === "image/jpg") {
|
|
41
|
+
return "image/jpeg";
|
|
42
|
+
}
|
|
43
|
+
return SUPPORTED_IMAGE_MEDIA_TYPES.has(h)
|
|
44
|
+
? h
|
|
45
|
+
: undefined;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Detect a supported image media type from a buffer's magic bytes. Returns
|
|
49
|
+
* undefined when the bytes are not one of the four Anthropic-supported formats.
|
|
50
|
+
*/
|
|
51
|
+
export function sniffImageMediaType(bytes) {
|
|
52
|
+
// PNG: 89 50 4E 47 0D 0A 1A 0A
|
|
53
|
+
if (bytes.length >= 8 &&
|
|
54
|
+
bytes[0] === 0x89 &&
|
|
55
|
+
bytes[1] === 0x50 &&
|
|
56
|
+
bytes[2] === 0x4e &&
|
|
57
|
+
bytes[3] === 0x47) {
|
|
58
|
+
return "image/png";
|
|
59
|
+
}
|
|
60
|
+
// JPEG: FF D8 FF
|
|
61
|
+
if (bytes.length >= 3 &&
|
|
62
|
+
bytes[0] === 0xff &&
|
|
63
|
+
bytes[1] === 0xd8 &&
|
|
64
|
+
bytes[2] === 0xff) {
|
|
65
|
+
return "image/jpeg";
|
|
66
|
+
}
|
|
67
|
+
// GIF: "GIF"
|
|
68
|
+
if (bytes.length >= 6 &&
|
|
69
|
+
bytes[0] === 0x47 &&
|
|
70
|
+
bytes[1] === 0x49 &&
|
|
71
|
+
bytes[2] === 0x46) {
|
|
72
|
+
return "image/gif";
|
|
73
|
+
}
|
|
74
|
+
// WebP: "RIFF"...."WEBP"
|
|
75
|
+
if (bytes.length >= 12 &&
|
|
76
|
+
bytes[0] === 0x52 &&
|
|
77
|
+
bytes[1] === 0x49 &&
|
|
78
|
+
bytes[2] === 0x46 &&
|
|
79
|
+
bytes[3] === 0x46 &&
|
|
80
|
+
bytes[8] === 0x57 &&
|
|
81
|
+
bytes[9] === 0x45 &&
|
|
82
|
+
bytes[10] === 0x42 &&
|
|
83
|
+
bytes[11] === 0x50) {
|
|
84
|
+
return "image/webp";
|
|
85
|
+
}
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
// The longest signature `sniffImageMediaType` checks is WebP — "RIFF"…"WEBP",
|
|
89
|
+
// which spans the first 12 decoded bytes. Base64 packs 3 bytes per 4 chars, so
|
|
90
|
+
// 16 chars already cover those 12 bytes; we slice 32 (a clean 4-char boundary
|
|
91
|
+
// that decodes to 24 bytes) for a comfortable margin over every signature.
|
|
92
|
+
const SNIFF_BASE64_CHARS = 32;
|
|
93
|
+
/** Sniff the leading bytes of a base64 payload (best-effort, never throws). */
|
|
94
|
+
const sniffBase64 = (b64) => {
|
|
95
|
+
try {
|
|
96
|
+
return sniffImageMediaType(Buffer.from(b64.slice(0, SNIFF_BASE64_CHARS), "base64"));
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* Convert an image part (data URL, bare base64, https URL, byte array, or
|
|
104
|
+
* ArrayBuffer) into an Anthropic image block. Honors `mediaTypeHint` (the
|
|
105
|
+
* AI-SDK `mediaType`), falls back to magic-byte sniffing, then to image/png.
|
|
106
|
+
* Returns undefined for unusable inputs.
|
|
107
|
+
*/
|
|
108
|
+
export function toAnthropicImageBlock(data, mediaTypeHint) {
|
|
109
|
+
if (data instanceof ArrayBuffer) {
|
|
110
|
+
return toAnthropicImageBlock(new Uint8Array(data), mediaTypeHint);
|
|
111
|
+
}
|
|
112
|
+
if (data instanceof Uint8Array) {
|
|
113
|
+
const media_type = normalizeImageMediaType(mediaTypeHint) ??
|
|
114
|
+
sniffImageMediaType(data) ??
|
|
115
|
+
"image/png";
|
|
116
|
+
return {
|
|
117
|
+
type: "image",
|
|
118
|
+
source: {
|
|
119
|
+
type: "base64",
|
|
120
|
+
media_type,
|
|
121
|
+
data: Buffer.from(data).toString("base64"),
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
if (typeof data !== "string" && !(data instanceof URL)) {
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
const str = data instanceof URL ? data.toString() : data;
|
|
129
|
+
const dataUrlMatch = str.match(/^data:(image\/[a-z0-9+.-]+);base64,(.+)$/i);
|
|
130
|
+
if (dataUrlMatch) {
|
|
131
|
+
const media_type = normalizeImageMediaType(dataUrlMatch[1]) ?? sniffBase64(dataUrlMatch[2]);
|
|
132
|
+
if (!media_type) {
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
type: "image",
|
|
137
|
+
source: { type: "base64", media_type, data: dataUrlMatch[2] },
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
if (/^https?:\/\//i.test(str)) {
|
|
141
|
+
return { type: "image", source: { type: "url", url: str } };
|
|
142
|
+
}
|
|
143
|
+
// Bare base64 payload — prefer the hint, then sniff, then assume PNG
|
|
144
|
+
// (matches the OpenAI-compat client's historical default).
|
|
145
|
+
const media_type = normalizeImageMediaType(mediaTypeHint) ?? sniffBase64(str) ?? "image/png";
|
|
146
|
+
return {
|
|
147
|
+
type: "image",
|
|
148
|
+
source: { type: "base64", media_type, data: str },
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/** Convert a PDF file part into an Anthropic document block. */
|
|
152
|
+
const toAnthropicPdfBlock = (data) => {
|
|
153
|
+
if (data instanceof ArrayBuffer) {
|
|
154
|
+
return toAnthropicPdfBlock(new Uint8Array(data));
|
|
155
|
+
}
|
|
156
|
+
if (data instanceof Uint8Array) {
|
|
157
|
+
return {
|
|
158
|
+
type: "document",
|
|
159
|
+
source: {
|
|
160
|
+
type: "base64",
|
|
161
|
+
media_type: "application/pdf",
|
|
162
|
+
data: Buffer.from(data).toString("base64"),
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
if (data instanceof URL) {
|
|
167
|
+
return { type: "document", source: { type: "url", url: data.toString() } };
|
|
168
|
+
}
|
|
169
|
+
if (typeof data === "string") {
|
|
170
|
+
const m = data.match(/^data:application\/pdf;base64,(.+)$/i);
|
|
171
|
+
if (m) {
|
|
172
|
+
return {
|
|
173
|
+
type: "document",
|
|
174
|
+
source: { type: "base64", media_type: "application/pdf", data: m[1] },
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
if (/^https?:\/\//i.test(data)) {
|
|
178
|
+
return { type: "document", source: { type: "url", url: data } };
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
type: "document",
|
|
182
|
+
source: { type: "base64", media_type: "application/pdf", data },
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
return undefined;
|
|
186
|
+
};
|
|
187
|
+
/**
|
|
188
|
+
* Convert an AI-SDK `{ type: "file", mediaType, data }` content part into the
|
|
189
|
+
* matching Anthropic content block: image/* → image block (media type honored,
|
|
190
|
+
* not hardcoded), application/pdf → document block. Returns undefined for
|
|
191
|
+
* unsupported media types (so the caller simply omits them rather than 400ing).
|
|
192
|
+
* When `mediaType` is absent, image bytes are still salvaged via sniffing.
|
|
193
|
+
*/
|
|
194
|
+
export function fileToAnthropicBlock(part) {
|
|
195
|
+
const data = part?.data;
|
|
196
|
+
if (data === undefined || data === null) {
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
const mediaType = typeof part.mediaType === "string"
|
|
200
|
+
? part.mediaType.toLowerCase().split(";")[0].trim()
|
|
201
|
+
: "";
|
|
202
|
+
if (SUPPORTED_IMAGE_MEDIA_TYPES.has(mediaType) || mediaType === "image/jpg") {
|
|
203
|
+
// Hint is one of the four Anthropic-supported image types (or the jpg alias).
|
|
204
|
+
return toAnthropicImageBlock(data, mediaType);
|
|
205
|
+
}
|
|
206
|
+
if (mediaType === "application/pdf") {
|
|
207
|
+
return toAnthropicPdfBlock(data);
|
|
208
|
+
}
|
|
209
|
+
// No media type, unknown media type, OR an unsupported image/* hint
|
|
210
|
+
// (e.g. image/svg+xml, image/bmp) — attempt magic-byte salvage so that the
|
|
211
|
+
// part is included only when the actual bytes sniff to a supported format,
|
|
212
|
+
// otherwise omit it rather than mislabeling it as image/png.
|
|
213
|
+
const bytes = data instanceof ArrayBuffer
|
|
214
|
+
? new Uint8Array(data)
|
|
215
|
+
: data instanceof Uint8Array
|
|
216
|
+
? data
|
|
217
|
+
: undefined;
|
|
218
|
+
if (bytes && sniffImageMediaType(bytes)) {
|
|
219
|
+
return toAnthropicImageBlock(bytes);
|
|
220
|
+
}
|
|
221
|
+
return undefined;
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=anthropicImageBlocks.js.map
|
|
@@ -96,6 +96,32 @@ export declare function buildNativeConfig(options: {
|
|
|
96
96
|
* Compute a safe, clamped maxSteps value.
|
|
97
97
|
*/
|
|
98
98
|
export declare function computeMaxSteps(rawMaxSteps?: number): number;
|
|
99
|
+
/**
|
|
100
|
+
* Map a `@google/genai` `Candidate.finishReason` enum value onto NeuroLink's
|
|
101
|
+
* unified finish reason, mirroring anthropic.ts `mapAnthropicStopReason`.
|
|
102
|
+
*
|
|
103
|
+
* Enum values per `@google/genai` `FinishReason`: STOP, MAX_TOKENS, SAFETY,
|
|
104
|
+
* RECITATION, LANGUAGE, OTHER, BLOCKLIST, PROHIBITED_CONTENT, SPII,
|
|
105
|
+
* MALFORMED_FUNCTION_CALL, IMAGE_SAFETY, UNEXPECTED_TOOL_CALL,
|
|
106
|
+
* FINISH_REASON_UNSPECIFIED. Unknown / unset / non-terminal values default to
|
|
107
|
+
* "stop" (a clean completion is the safe assumption).
|
|
108
|
+
*
|
|
109
|
+
* Returns a plain string (not a `{ unified, raw }` object): the Vertex result
|
|
110
|
+
* builders and the consuming layer (neurolink.ts `finishReason || "unknown"`
|
|
111
|
+
* and `finishReason === "length"`) compare against plain strings.
|
|
112
|
+
*/
|
|
113
|
+
export declare function mapGeminiFinishReason(raw: string | null | undefined): "stop" | "length" | "tool-calls" | "content-filter";
|
|
114
|
+
/**
|
|
115
|
+
* Append a step's text to a running cross-step accumulator, ignoring empty
|
|
116
|
+
* steps and inserting a single newline between non-empty contributions.
|
|
117
|
+
*
|
|
118
|
+
* The native Gemini loops overwrite per-step text into `lastStepText`, so when
|
|
119
|
+
* the loop is force-terminated by the step cap the intermediate tool-step prose
|
|
120
|
+
* is lost and a canned placeholder becomes the answer. Accumulating here mirrors
|
|
121
|
+
* the Vertex-Claude loop's `aggregatedTurnText += block.text` so the gathered
|
|
122
|
+
* text can be surfaced at the maxSteps-exhaustion exit instead of the placeholder.
|
|
123
|
+
*/
|
|
124
|
+
export declare function appendStepText(accumulated: string, stepText: string): string;
|
|
99
125
|
/**
|
|
100
126
|
* Process stream chunks to extract raw response parts, function calls, and usage metadata.
|
|
101
127
|
*
|
|
@@ -450,6 +450,54 @@ export function computeMaxSteps(rawMaxSteps) {
|
|
|
450
450
|
? Math.min(Math.floor(value), GEMINI3_NATIVE_MAX_STEPS)
|
|
451
451
|
: Math.min(DEFAULT_MAX_STEPS, GEMINI3_NATIVE_MAX_STEPS);
|
|
452
452
|
}
|
|
453
|
+
/**
|
|
454
|
+
* Map a `@google/genai` `Candidate.finishReason` enum value onto NeuroLink's
|
|
455
|
+
* unified finish reason, mirroring anthropic.ts `mapAnthropicStopReason`.
|
|
456
|
+
*
|
|
457
|
+
* Enum values per `@google/genai` `FinishReason`: STOP, MAX_TOKENS, SAFETY,
|
|
458
|
+
* RECITATION, LANGUAGE, OTHER, BLOCKLIST, PROHIBITED_CONTENT, SPII,
|
|
459
|
+
* MALFORMED_FUNCTION_CALL, IMAGE_SAFETY, UNEXPECTED_TOOL_CALL,
|
|
460
|
+
* FINISH_REASON_UNSPECIFIED. Unknown / unset / non-terminal values default to
|
|
461
|
+
* "stop" (a clean completion is the safe assumption).
|
|
462
|
+
*
|
|
463
|
+
* Returns a plain string (not a `{ unified, raw }` object): the Vertex result
|
|
464
|
+
* builders and the consuming layer (neurolink.ts `finishReason || "unknown"`
|
|
465
|
+
* and `finishReason === "length"`) compare against plain strings.
|
|
466
|
+
*/
|
|
467
|
+
export function mapGeminiFinishReason(raw) {
|
|
468
|
+
switch (raw) {
|
|
469
|
+
case "MAX_TOKENS":
|
|
470
|
+
return "length";
|
|
471
|
+
case "MALFORMED_FUNCTION_CALL":
|
|
472
|
+
case "UNEXPECTED_TOOL_CALL":
|
|
473
|
+
return "tool-calls";
|
|
474
|
+
case "SAFETY":
|
|
475
|
+
case "RECITATION":
|
|
476
|
+
case "BLOCKLIST":
|
|
477
|
+
case "PROHIBITED_CONTENT":
|
|
478
|
+
case "SPII":
|
|
479
|
+
case "IMAGE_SAFETY":
|
|
480
|
+
return "content-filter";
|
|
481
|
+
default:
|
|
482
|
+
return "stop";
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Append a step's text to a running cross-step accumulator, ignoring empty
|
|
487
|
+
* steps and inserting a single newline between non-empty contributions.
|
|
488
|
+
*
|
|
489
|
+
* The native Gemini loops overwrite per-step text into `lastStepText`, so when
|
|
490
|
+
* the loop is force-terminated by the step cap the intermediate tool-step prose
|
|
491
|
+
* is lost and a canned placeholder becomes the answer. Accumulating here mirrors
|
|
492
|
+
* the Vertex-Claude loop's `aggregatedTurnText += block.text` so the gathered
|
|
493
|
+
* text can be surfaced at the maxSteps-exhaustion exit instead of the placeholder.
|
|
494
|
+
*/
|
|
495
|
+
export function appendStepText(accumulated, stepText) {
|
|
496
|
+
if (!stepText) {
|
|
497
|
+
return accumulated;
|
|
498
|
+
}
|
|
499
|
+
return accumulated ? `${accumulated}\n${stepText}` : stepText;
|
|
500
|
+
}
|
|
453
501
|
/**
|
|
454
502
|
* Process stream chunks to extract raw response parts, function calls, and usage metadata.
|
|
455
503
|
*
|
|
@@ -160,6 +160,22 @@ export declare class GoogleVertexProvider extends BaseProvider {
|
|
|
160
160
|
* This bypasses @ai-sdk/google-vertex to properly handle thought_signature
|
|
161
161
|
*/
|
|
162
162
|
private executeNativeGemini3Generate;
|
|
163
|
+
/**
|
|
164
|
+
* One-shot, tools-disabled model call used when a native Gemini agentic loop
|
|
165
|
+
* is force-terminated by the step cap with no text produced. Lets the model
|
|
166
|
+
* synthesize a final answer from the function results already in `contents`
|
|
167
|
+
* instead of returning a canned placeholder (Bug 1, part b).
|
|
168
|
+
*
|
|
169
|
+
* Tools are disabled by OMITTING `config.tools` — the codebase's established
|
|
170
|
+
* mechanism. `@google/genai`'s `FunctionCallingConfigMode.NONE` is documented
|
|
171
|
+
* as equivalent to passing no function declarations, and `functionCallingConfig`
|
|
172
|
+
* is not used anywhere in this codebase. When the structured-output
|
|
173
|
+
* (`final_result`) pattern was active, a trailing instruction countermands the
|
|
174
|
+
* earlier "you MUST call final_result" directive so the model answers in plain
|
|
175
|
+
* text. Never throws — returns empty text so the caller falls back to the
|
|
176
|
+
* placeholder, guaranteeing no new failure path.
|
|
177
|
+
*/
|
|
178
|
+
private synthesizeFinalAnswerWithoutTools;
|
|
163
179
|
/**
|
|
164
180
|
* Create native AnthropicVertex client for Claude models
|
|
165
181
|
*/
|