@juspay/neurolink 12.47.0 → 12.47.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 +3 -3
- package/dist/adapters/video/ffmpegAdapter.d.ts +3 -1
- package/dist/adapters/video/ffmpegAdapter.js +13 -2
- package/dist/browser/neurolink.min.js +284 -284
- package/dist/cli/commands/setup.js +6 -5
- package/dist/cli/proxy-clients/copilot.js +55 -1
- package/dist/constants/contextWindows.js +12 -0
- package/dist/core/toolExecutionGuards.d.ts +1 -9
- package/dist/core/toolExecutionGuards.js +1 -9
- package/dist/factories/providerFactory.js +16 -1
- package/dist/files/fileReferenceRegistry.js +3 -3
- package/dist/neurolink.js +13 -2
- package/dist/processors/media/VideoProcessor.js +16 -4
- package/dist/providers/googleNativeGemini3/utils.d.ts +9 -0
- package/dist/providers/googleNativeGemini3/utils.js +9 -0
- package/package.json +1 -1
|
@@ -19,6 +19,7 @@ import { handleBedrockSetup } from "./setup-bedrock.js";
|
|
|
19
19
|
import { handleGCPSetup } from "./setup-gcp.js";
|
|
20
20
|
import { handleHuggingFaceSetup } from "./setup-huggingface.js";
|
|
21
21
|
import { handleMistralSetup } from "./setup-mistral.js";
|
|
22
|
+
import { OpenRouterModels, } from "../../constants/enums.js";
|
|
22
23
|
import { PROVIDER_DESCRIPTORS_BY_NAME } from "../../factories/providerDescriptors.js";
|
|
23
24
|
import { createCohereConfig, createDeepSeekConfig, createIdeogramConfig, createJinaConfig, createNvidiaNimConfig, createOpenAICompatibleConfig, createRecraftConfig, createReplicateConfig, createStabilityConfig, createVoyageConfig, createTypeSafeConfig, createLayaConfig, createXorConfig, createPerplexityDeciderConfig, createCloudflareClefConfig, satisfiesFallbacks, } from "../../utils/providerConfig.js";
|
|
24
25
|
import { getCatalogJsonEntries, buildCatalogConfigOptions, } from "../../providers/catalog/loader.js";
|
|
@@ -571,13 +572,13 @@ async function handleOpenRouterSetup() {
|
|
|
571
572
|
logger.always(chalk.cyan(" export OPENROUTER_API_KEY=your_api_key_here"));
|
|
572
573
|
logger.always("");
|
|
573
574
|
logger.always(chalk.yellow("Step 3: Test the configuration"));
|
|
574
|
-
logger.always(chalk.cyan(
|
|
575
|
+
logger.always(chalk.cyan(` neurolink generate "Hello!" --provider openrouter --model ${OpenRouterModels.GEMINI_2_5_FLASH}`));
|
|
575
576
|
logger.always("");
|
|
576
577
|
logger.always(chalk.green("Available models include:"));
|
|
577
|
-
logger.always(
|
|
578
|
-
logger.always(
|
|
579
|
-
logger.always(
|
|
580
|
-
logger.always(
|
|
578
|
+
logger.always(` • ${OpenRouterModels.CLAUDE_SONNET_4_6} - Best for analysis`);
|
|
579
|
+
logger.always(` • ${OpenRouterModels.GPT_4O} - Industry standard`);
|
|
580
|
+
logger.always(` • ${OpenRouterModels.GEMINI_2_5_FLASH} - Fast and efficient`);
|
|
581
|
+
logger.always(` • ${OpenRouterModels.LLAMA_3_1_70B} - Open source`);
|
|
581
582
|
logger.always("");
|
|
582
583
|
logger.always(chalk.gray("See all models at: https://openrouter.ai/models"));
|
|
583
584
|
}
|
|
@@ -46,12 +46,66 @@ function getCopilotEnvPath() {
|
|
|
46
46
|
function getProfileCandidates() {
|
|
47
47
|
return [".zshrc", ".zprofile", ".bashrc", ".bash_profile", ".profile"].map((name) => join(homedir(), name));
|
|
48
48
|
}
|
|
49
|
+
/** A command that is `source`/`.` of a path ending in exactly `copilot-env.sh`, optionally after `then`/`else`/`do`. */
|
|
50
|
+
const ENV_SCRIPT_SOURCE_COMMAND = /^\s*(?:(?:then|else|do)\s+)?(?:source|\.)\s+(["']?)(?:[^\s"']*\/)?copilot-env\.sh\1(?:\s|$)/;
|
|
51
|
+
/**
|
|
52
|
+
* One profile line as the commands it runs, comment dropped. Quote-aware, so a
|
|
53
|
+
* `;` or `#` inside quotes neither splits nor starts a comment. Heredocs,
|
|
54
|
+
* multi-line strings and line continuations are not modelled, so a source line
|
|
55
|
+
* inside one is still counted, and a source built with `eval` is missed. Both
|
|
56
|
+
* are rare; this guards against the common mentions (a comment, an `echo`, a
|
|
57
|
+
* file-existence test, a `.bak` name) that suppressed the note before.
|
|
58
|
+
*/
|
|
59
|
+
function splitProfileCommands(line) {
|
|
60
|
+
const commands = [];
|
|
61
|
+
let current = "";
|
|
62
|
+
let quote = null;
|
|
63
|
+
for (let i = 0; i < line.length; i++) {
|
|
64
|
+
const char = line[i];
|
|
65
|
+
if (quote) {
|
|
66
|
+
current += char;
|
|
67
|
+
if (quote === '"' && char === "\\") {
|
|
68
|
+
current += line[i + 1] ?? "";
|
|
69
|
+
i++;
|
|
70
|
+
}
|
|
71
|
+
else if (char === quote) {
|
|
72
|
+
quote = null;
|
|
73
|
+
}
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (char === "\\") {
|
|
77
|
+
current += char + (line[i + 1] ?? "");
|
|
78
|
+
i++;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (char === '"' || char === "'") {
|
|
82
|
+
quote = char;
|
|
83
|
+
current += char;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (char === "#" &&
|
|
87
|
+
(current === "" || /\s/.test(current[current.length - 1]))) {
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
if (char === ";" || char === "&" || char === "|") {
|
|
91
|
+
commands.push(current);
|
|
92
|
+
current = "";
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
current += char;
|
|
96
|
+
}
|
|
97
|
+
commands.push(current);
|
|
98
|
+
return commands;
|
|
99
|
+
}
|
|
49
100
|
/** Whether any shell profile already sources the generated script. */
|
|
50
101
|
async function isEnvScriptSourced() {
|
|
51
102
|
const fs = await import("fs");
|
|
52
103
|
for (const profile of getProfileCandidates()) {
|
|
53
104
|
try {
|
|
54
|
-
if (fs
|
|
105
|
+
if (fs
|
|
106
|
+
.readFileSync(profile, "utf8")
|
|
107
|
+
.split(/\r?\n/)
|
|
108
|
+
.some((line) => splitProfileCommands(line).some((command) => ENV_SCRIPT_SOURCE_COMMAND.test(command)))) {
|
|
55
109
|
return true;
|
|
56
110
|
}
|
|
57
111
|
}
|
|
@@ -122,6 +122,11 @@ export const MODEL_CONTEXT_WINDOWS = {
|
|
|
122
122
|
"claude-fable-5-1": 1_000_000,
|
|
123
123
|
// Claude 5 (mid 2026) — 1M context window
|
|
124
124
|
"claude-sonnet-5": 1_000_000,
|
|
125
|
+
"claude-opus-5": 1_000_000,
|
|
126
|
+
"claude-fable-5": 1_000_000,
|
|
127
|
+
// Claude 4.7 / 4.8 — 1M context window (platform.claude.com/docs/en/models)
|
|
128
|
+
"claude-opus-4-8": 1_000_000,
|
|
129
|
+
"claude-opus-4-7": 1_000_000,
|
|
125
130
|
// Claude 4.6 (Feb 2026) — 1M context window
|
|
126
131
|
"claude-opus-4-6": 1_000_000,
|
|
127
132
|
"claude-sonnet-4-6": 1_000_000,
|
|
@@ -220,6 +225,13 @@ export const MODEL_CONTEXT_WINDOWS = {
|
|
|
220
225
|
"claude-sonnet-5-5": 1_000_000,
|
|
221
226
|
"claude-fable-5-1": 1_000_000,
|
|
222
227
|
"claude-sonnet-5": 1_000_000,
|
|
228
|
+
// Placed before the 200K `claude-opus-4` prefix key below: the lookup takes
|
|
229
|
+
// the first prefix that matches, so a dated `claude-opus-4-8@...` id would
|
|
230
|
+
// otherwise land on 200K.
|
|
231
|
+
"claude-opus-5": 1_000_000,
|
|
232
|
+
"claude-fable-5": 1_000_000,
|
|
233
|
+
"claude-opus-4-8": 1_000_000,
|
|
234
|
+
"claude-opus-4-7": 1_000_000,
|
|
223
235
|
"claude-opus-4-6": 1_000_000,
|
|
224
236
|
"claude-sonnet-4-6": 1_000_000,
|
|
225
237
|
"claude-sonnet-4-5": 200_000,
|
|
@@ -13,15 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import type { Tool, ToolExecutionGuards } from "../types/index.js";
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
17
|
-
* via buildNativeToolDeclarations. `search_tools` (tools.discovery) hydrates
|
|
18
|
-
* discovered tools into the live record between steps; without this refresh
|
|
19
|
-
* they stay invisible to the rest of the turn and every call dies as
|
|
20
|
-
* TOOL_NOT_FOUND. Mutates the snapshot in place — the request config holds
|
|
21
|
-
* `toolsConfig` by reference — and returns true when anything was added.
|
|
22
|
-
*/
|
|
23
|
-
/**
|
|
24
|
-
* Everything a native Gemini loop wraps around a tool call that the shared
|
|
16
|
+
* Everything a native provider loop wraps around a tool call that the shared
|
|
25
17
|
* engine does not do itself.
|
|
26
18
|
*
|
|
27
19
|
* Order matters. `raceWithAbort` sits INSIDE `withTimeout` so a turn-level
|
|
@@ -13,15 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { raceWithAbort, withTimeout } from "../utils/async/index.js";
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
17
|
-
* via buildNativeToolDeclarations. `search_tools` (tools.discovery) hydrates
|
|
18
|
-
* discovered tools into the live record between steps; without this refresh
|
|
19
|
-
* they stay invisible to the rest of the turn and every call dies as
|
|
20
|
-
* TOOL_NOT_FOUND. Mutates the snapshot in place — the request config holds
|
|
21
|
-
* `toolsConfig` by reference — and returns true when anything was added.
|
|
22
|
-
*/
|
|
23
|
-
/**
|
|
24
|
-
* Everything a native Gemini loop wraps around a tool call that the shared
|
|
16
|
+
* Everything a native provider loop wraps around a tool call that the shared
|
|
25
17
|
* engine does not do itself.
|
|
26
18
|
*
|
|
27
19
|
* Order matters. `raceWithAbort` sits INSIDE `withTimeout` so a turn-level
|
|
@@ -22,6 +22,17 @@ export function resolveCredentialKey(providerName) {
|
|
|
22
22
|
const normalized = providerName.toLowerCase();
|
|
23
23
|
return (ProviderFactory.getDescriptor(normalized)?.credentialsKey ?? normalized);
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* registerProvider() takes a class as well as a factory (ProviderConstructor),
|
|
27
|
+
* and calling a class without `new` throws. A prototype test cannot tell them
|
|
28
|
+
* apart, because every non-arrow function's prototype points back at itself,
|
|
29
|
+
* so the source text decides. A factory is therefore never constructed and
|
|
30
|
+
* never retried after it throws.
|
|
31
|
+
*/
|
|
32
|
+
function isClassConstructor(candidate) {
|
|
33
|
+
return (typeof candidate === "function" &&
|
|
34
|
+
/^class[\s{]/.test(Function.prototype.toString.call(candidate)));
|
|
35
|
+
}
|
|
25
36
|
/**
|
|
26
37
|
* True Factory Pattern implementation for AI Providers
|
|
27
38
|
* Uses registration-based approach to eliminate switch statements
|
|
@@ -113,7 +124,11 @@ export class ProviderFactory {
|
|
|
113
124
|
if (typeof registration.constructor !== "function") {
|
|
114
125
|
throw new Error(`Invalid constructor for provider ${providerName}: not a function`);
|
|
115
126
|
}
|
|
116
|
-
|
|
127
|
+
// A factory keeps being called as a method of the registration, as it
|
|
128
|
+
// always was; only a class is constructed.
|
|
129
|
+
const factoryResult = isClassConstructor(registration.constructor)
|
|
130
|
+
? new registration.constructor(model, resolvedProviderName, sdk, region, scopedCredentials)
|
|
131
|
+
: registration.constructor(model, resolvedProviderName, sdk, region, scopedCredentials);
|
|
117
132
|
const result = factoryResult instanceof Promise ? await factoryResult : factoryResult;
|
|
118
133
|
return result;
|
|
119
134
|
}
|
|
@@ -827,7 +827,7 @@ export class FileReferenceRegistry {
|
|
|
827
827
|
extractedText = await this.extractWordText(buffer, ref);
|
|
828
828
|
break;
|
|
829
829
|
case "pptx":
|
|
830
|
-
extractedText = await this.extractPptxText(buffer);
|
|
830
|
+
extractedText = await this.extractPptxText(buffer, ref.filename);
|
|
831
831
|
break;
|
|
832
832
|
case "video":
|
|
833
833
|
extractedText = await this.extractVideoContent(buffer, ref);
|
|
@@ -992,13 +992,13 @@ export class FileReferenceRegistry {
|
|
|
992
992
|
/**
|
|
993
993
|
* Extract text from a PowerPoint file using PptxProcessor.
|
|
994
994
|
*/
|
|
995
|
-
async extractPptxText(buffer) {
|
|
995
|
+
async extractPptxText(buffer, filename) {
|
|
996
996
|
try {
|
|
997
997
|
const { PptxProcessor } = await import("../processors/document/PptxProcessor.js");
|
|
998
998
|
return await PptxProcessor.extractText(buffer);
|
|
999
999
|
}
|
|
1000
1000
|
catch (err) {
|
|
1001
|
-
logger.warn(`[FileReferenceRegistry] PPTX extraction failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1001
|
+
logger.warn(`[FileReferenceRegistry] PPTX extraction failed for "${filename}": ${err instanceof Error ? err.message : String(err)}`);
|
|
1002
1002
|
return null;
|
|
1003
1003
|
}
|
|
1004
1004
|
}
|
package/dist/neurolink.js
CHANGED
|
@@ -8640,11 +8640,22 @@ Current user's request: ${currentInput}`;
|
|
|
8640
8640
|
}
|
|
8641
8641
|
yield fallbackChunk;
|
|
8642
8642
|
}
|
|
8643
|
+
// Read after the drain: a background-loop provider (native Vertex+Claude)
|
|
8644
|
+
// fills these while the stream is consumed, so the values taken before
|
|
8645
|
+
// the loop are still empty for it. Without this, a fallback that ran
|
|
8646
|
+
// tools reports none on the result and in the end-of-turn events, and
|
|
8647
|
+
// the 0-output gate below counts tool calls that had not appeared yet.
|
|
8648
|
+
const finalToolCalls = fallbackResult.toolCalls ?? [];
|
|
8649
|
+
const finalToolResults = fallbackResult.toolResults ?? [];
|
|
8643
8650
|
if (fallbackRealOutputChunks === 0 &&
|
|
8644
|
-
|
|
8645
|
-
|
|
8651
|
+
finalToolCalls.length === 0 &&
|
|
8652
|
+
finalToolResults.length === 0) {
|
|
8646
8653
|
throw new Error(`Fallback provider ${fallbackRoute.provider} also returned 0 real output chunks (chunkCount=${fallbackChunkCount}, sentinel-only or empty)`);
|
|
8647
8654
|
}
|
|
8655
|
+
streamState.toolCalls = finalToolCalls;
|
|
8656
|
+
streamState.toolResults = finalToolResults;
|
|
8657
|
+
streamState.finishReason =
|
|
8658
|
+
fallbackResult.finishReason ?? streamState.finishReason;
|
|
8648
8659
|
// Fallback succeeded - likely guardrails blocked primary
|
|
8649
8660
|
metadata.fallbackProvider = fallbackRoute.provider;
|
|
8650
8661
|
metadata.fallbackModel = fallbackRoute.model;
|
|
@@ -660,6 +660,7 @@ export class VideoProcessor extends BaseFileProcessor {
|
|
|
660
660
|
*/
|
|
661
661
|
async probeVideoWithFfmpeg(filePath) {
|
|
662
662
|
let report;
|
|
663
|
+
let exitFailure;
|
|
663
664
|
try {
|
|
664
665
|
const { stderr } = await runFfmpeg(["-hide_banner", "-i", filePath, "-t", "0", "-f", "null", "-"], { timeoutMs: VIDEO_CONFIG.FFPROBE_TIMEOUT_MS });
|
|
665
666
|
report = stderr;
|
|
@@ -668,18 +669,29 @@ export class VideoProcessor extends BaseFileProcessor {
|
|
|
668
669
|
// A non-zero exit still carries the stream report when the failure came
|
|
669
670
|
// after the input was opened.
|
|
670
671
|
const stderr = error.stderr;
|
|
672
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
671
673
|
if (typeof stderr !== "string") {
|
|
672
674
|
return {
|
|
673
675
|
success: false,
|
|
674
|
-
error: `ffmpeg probe failed: ${
|
|
676
|
+
error: `ffmpeg probe failed: ${message}`,
|
|
675
677
|
};
|
|
676
678
|
}
|
|
677
679
|
report = stderr;
|
|
680
|
+
exitFailure = message;
|
|
678
681
|
}
|
|
679
682
|
const data = this.parseFfmpegInputReport(report);
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
+
if (data) {
|
|
684
|
+
return { success: true, data };
|
|
685
|
+
}
|
|
686
|
+
// Keep ffmpeg's own words when it failed as well as found no duration:
|
|
687
|
+
// a file it cannot read at all is the common case, and the reason is the
|
|
688
|
+
// only thing the warning that follows can tell the reader.
|
|
689
|
+
return {
|
|
690
|
+
success: false,
|
|
691
|
+
error: exitFailure
|
|
692
|
+
? `ffmpeg reported no duration: ${exitFailure}`
|
|
693
|
+
: "ffmpeg reported no duration",
|
|
694
|
+
};
|
|
683
695
|
}
|
|
684
696
|
/**
|
|
685
697
|
* Parse the input section of an ffmpeg banner: `Duration:`, `bitrate:` and
|
|
@@ -52,6 +52,15 @@ export declare function buildNativeToolDeclarations(tools: Record<string, Tool>,
|
|
|
52
52
|
* duplicate side effects the moment the model repeats itself.
|
|
53
53
|
*/
|
|
54
54
|
export declare function buildDedupedEngineTools(declarations: NativeToolDeclarationsResult | undefined, tools: Record<string, Tool> | undefined, guards?: ToolExecutionGuards): NonNullable<AgenticLoopOptions["tools"]>;
|
|
55
|
+
/**
|
|
56
|
+
* Mid-turn tool sync for the native Gemini loops that build their snapshot
|
|
57
|
+
* via buildNativeToolDeclarations. `search_tools` (tools.discovery) hydrates
|
|
58
|
+
* discovered tools into the live record between steps; without this refresh
|
|
59
|
+
* they stay invisible to the rest of the turn and every call dies as
|
|
60
|
+
* TOOL_NOT_FOUND. Mutates the snapshot in place — the request config holds
|
|
61
|
+
* `toolsConfig` by reference — and returns the original names of the tools it
|
|
62
|
+
* added (empty when nothing was added).
|
|
63
|
+
*/
|
|
55
64
|
export declare function refreshNativeToolDeclarations(liveTools: Record<string, Tool> | undefined, current: NativeToolDeclarationsResult): string[];
|
|
56
65
|
/**
|
|
57
66
|
* Build the native @google/genai config object shared by stream and generate.
|
|
@@ -381,6 +381,15 @@ export function buildDedupedEngineTools(declarations, tools, guards) {
|
|
|
381
381
|
}
|
|
382
382
|
return engineTools;
|
|
383
383
|
}
|
|
384
|
+
/**
|
|
385
|
+
* Mid-turn tool sync for the native Gemini loops that build their snapshot
|
|
386
|
+
* via buildNativeToolDeclarations. `search_tools` (tools.discovery) hydrates
|
|
387
|
+
* discovered tools into the live record between steps; without this refresh
|
|
388
|
+
* they stay invisible to the rest of the turn and every call dies as
|
|
389
|
+
* TOOL_NOT_FOUND. Mutates the snapshot in place — the request config holds
|
|
390
|
+
* `toolsConfig` by reference — and returns the original names of the tools it
|
|
391
|
+
* added (empty when nothing was added).
|
|
392
|
+
*/
|
|
384
393
|
export function refreshNativeToolDeclarations(liveTools, current) {
|
|
385
394
|
if (!liveTools) {
|
|
386
395
|
return [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "12.47.
|
|
3
|
+
"version": "12.47.1",
|
|
4
4
|
"packageManager": "pnpm@10.15.1",
|
|
5
5
|
"description": "The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002/call) for routing, tool selection and context budgeting. MCP-native (4 transports), voice TTS/STT/realtime, RAG, agents, memory, compaction, 9 observability exporters. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · TypeSafe Jev and more.",
|
|
6
6
|
"author": {
|