@juspay/neurolink 11.26.1 → 11.27.0
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/providerImageAdapter.js +20 -0
- package/dist/browser/neurolink.min.js +397 -397
- package/dist/constants/contextWindows.d.ts +18 -4
- package/dist/constants/contextWindows.js +35 -4
- package/dist/core/baseProvider.js +16 -0
- package/dist/core/constants.js +59 -1
- package/dist/models/anthropicModels.d.ts +3 -6
- package/dist/models/anthropicModels.js +97 -123
- package/dist/models/manifestRegistry.d.ts +9 -0
- package/dist/models/manifestRegistry.js +23 -0
- package/dist/models/manifests/anthropic.js +5 -3
- package/dist/models/manifests/azure.js +6 -1
- package/dist/models/modelRegistry.d.ts +22 -2
- package/dist/models/modelRegistry.js +143 -8
- package/dist/providers/amazonSagemaker.d.ts +1 -1
- package/dist/providers/anthropic/client.js +5 -4
- package/dist/providers/googleAiStudio/client.js +3 -2
- package/dist/providers/googleVertex/client.js +5 -4
- package/dist/providers/sagemaker/language-model.d.ts +2 -2
- package/dist/utils/drainDetachedPump.d.ts +31 -0
- package/dist/utils/drainDetachedPump.js +39 -0
- package/dist/utils/pricing.js +51 -0
- package/package.json +1 -1
|
@@ -6,10 +6,15 @@
|
|
|
6
6
|
import { DEFAULT_MODEL_ALIASES } from "../types/index.js";
|
|
7
7
|
import { logger } from "../utils/logger.js";
|
|
8
8
|
import { AIProviderName, OpenAIModels, AzureOpenAIModels, GoogleAIModels, AnthropicModels, BedrockModels, MistralModels, OllamaModels, } from "../constants/enums.js";
|
|
9
|
+
import { getAllManifestProviders, getManifestForProvider, } from "./manifestRegistry.js";
|
|
9
10
|
/**
|
|
10
|
-
* Comprehensive model registry
|
|
11
|
+
* Comprehensive model registry — hand-authored base. Every id defined here
|
|
12
|
+
* that the manifest doesn't also carry a priced entry for stays exactly as
|
|
13
|
+
* authored below; see buildManifestDerivedEntries()/MODEL_REGISTRY's
|
|
14
|
+
* docblock further down for how the two are merged and why (Task 9, model
|
|
15
|
+
* metadata consolidation plan).
|
|
11
16
|
*/
|
|
12
|
-
|
|
17
|
+
const LEGACY_MODEL_REGISTRY = {
|
|
13
18
|
// OpenAI Models
|
|
14
19
|
[OpenAIModels.GPT_4O]: {
|
|
15
20
|
id: OpenAIModels.GPT_4O,
|
|
@@ -2450,6 +2455,129 @@ export const MODEL_REGISTRY = {
|
|
|
2450
2455
|
},
|
|
2451
2456
|
// Note: Azure models like O3, O4-mini, GPT-4o share IDs with OpenAI and use the OpenAI registry entries
|
|
2452
2457
|
};
|
|
2458
|
+
function deriveSpeed(id) {
|
|
2459
|
+
if (/mini|nano|haiku|flash|lite/i.test(id)) {
|
|
2460
|
+
return "fast";
|
|
2461
|
+
}
|
|
2462
|
+
if (/opus|^o1|^o3|-pro$/i.test(id)) {
|
|
2463
|
+
return "slow";
|
|
2464
|
+
}
|
|
2465
|
+
return "medium";
|
|
2466
|
+
}
|
|
2467
|
+
function deriveQuality(entry) {
|
|
2468
|
+
return entry.reasoning ? "high" : "medium";
|
|
2469
|
+
}
|
|
2470
|
+
/**
|
|
2471
|
+
* Builds the manifest-derived slice of MODEL_REGISTRY: every manifest model
|
|
2472
|
+
* that carries pricingPerMTok (ModelInfo.pricing is a required field, and
|
|
2473
|
+
* the manifest's pricingPerMTok is deliberately optional for un-priced
|
|
2474
|
+
* models like claude-sonnet-5 — see ProviderModelManifestEntry's docblock),
|
|
2475
|
+
* excluding each provider's synthetic "_default" entry.
|
|
2476
|
+
*
|
|
2477
|
+
* performance/useCases/category use entry.curated verbatim when present —
|
|
2478
|
+
* the ids that already had a hand-tuned LEGACY_MODEL_REGISTRY row before
|
|
2479
|
+
* this migration (5 Anthropic, 20 OpenAI) carry that exact triple forward
|
|
2480
|
+
* unchanged. Every other entry has no curated block and falls back to
|
|
2481
|
+
* mechanical derivation from the tracked capability flags.
|
|
2482
|
+
*/
|
|
2483
|
+
function buildManifestDerivedEntries() {
|
|
2484
|
+
const entries = {};
|
|
2485
|
+
for (const provider of getAllManifestProviders()) {
|
|
2486
|
+
const manifest = getManifestForProvider(provider);
|
|
2487
|
+
if (!manifest) {
|
|
2488
|
+
continue;
|
|
2489
|
+
}
|
|
2490
|
+
for (const [id, entry] of Object.entries(manifest.models)) {
|
|
2491
|
+
if (id === "_default" || !entry.pricingPerMTok) {
|
|
2492
|
+
continue;
|
|
2493
|
+
}
|
|
2494
|
+
// Keep-first on cross-manifest id collisions (Azure/OpenAI share ids):
|
|
2495
|
+
// getAllManifestProviders() order is deterministic, and silently
|
|
2496
|
+
// overwriting an earlier provider's row would make "which provider owns
|
|
2497
|
+
// this id" depend on iteration order.
|
|
2498
|
+
if (entries[id]) {
|
|
2499
|
+
logger.debug(`[modelRegistry] Manifest id collision: "${id}" already derived from ${entries[id].provider}; keeping first, skipping ${provider}`);
|
|
2500
|
+
continue;
|
|
2501
|
+
}
|
|
2502
|
+
const reasoningScore = entry.reasoning ? 9 : 6;
|
|
2503
|
+
const legacyRow = LEGACY_MODEL_REGISTRY[id];
|
|
2504
|
+
entries[id] = {
|
|
2505
|
+
id,
|
|
2506
|
+
name: entry.displayName ?? id,
|
|
2507
|
+
provider: provider,
|
|
2508
|
+
description: entry.displayName ?? id,
|
|
2509
|
+
capabilities: {
|
|
2510
|
+
vision: entry.vision,
|
|
2511
|
+
functionCalling: entry.functionCalling,
|
|
2512
|
+
codeGeneration: true,
|
|
2513
|
+
reasoning: entry.reasoning ?? false,
|
|
2514
|
+
multimodal: entry.vision || entry.nativeAudio === true,
|
|
2515
|
+
streaming: true,
|
|
2516
|
+
jsonMode: entry.jsonMode ?? false,
|
|
2517
|
+
samplingParams: entry.samplingParams,
|
|
2518
|
+
},
|
|
2519
|
+
pricing: {
|
|
2520
|
+
inputCostPer1K: entry.pricingPerMTok.input / 1000,
|
|
2521
|
+
outputCostPer1K: entry.pricingPerMTok.output / 1000,
|
|
2522
|
+
currency: "USD",
|
|
2523
|
+
},
|
|
2524
|
+
performance: entry.curated?.performance ?? {
|
|
2525
|
+
speed: deriveSpeed(id),
|
|
2526
|
+
quality: deriveQuality(entry),
|
|
2527
|
+
accuracy: deriveQuality(entry),
|
|
2528
|
+
},
|
|
2529
|
+
limits: {
|
|
2530
|
+
maxContextTokens: entry.contextWindow,
|
|
2531
|
+
maxOutputTokens: entry.maxOutputTokens,
|
|
2532
|
+
// Rate limits are operational knowledge the manifests don't track;
|
|
2533
|
+
// carry the hand-authored value forward rather than dropping it.
|
|
2534
|
+
...(legacyRow?.limits?.maxRequestsPerMinute !== undefined
|
|
2535
|
+
? { maxRequestsPerMinute: legacyRow.limits.maxRequestsPerMinute }
|
|
2536
|
+
: {}),
|
|
2537
|
+
},
|
|
2538
|
+
useCases: entry.curated?.useCases ?? {
|
|
2539
|
+
coding: entry.functionCalling ? 8 : 5,
|
|
2540
|
+
creative: entry.vision ? 7 : 6,
|
|
2541
|
+
analysis: reasoningScore,
|
|
2542
|
+
conversation: 7,
|
|
2543
|
+
reasoning: reasoningScore,
|
|
2544
|
+
translation: 6,
|
|
2545
|
+
summarization: 7,
|
|
2546
|
+
},
|
|
2547
|
+
aliases: entry.aliases,
|
|
2548
|
+
// Deprecation and release dates are editorial facts the manifests do
|
|
2549
|
+
// not track — a manifest-priced model must not silently un-deprecate
|
|
2550
|
+
// a row the hand-authored registry explicitly flagged.
|
|
2551
|
+
deprecated: legacyRow?.deprecated ?? false,
|
|
2552
|
+
...(legacyRow?.releaseDate !== undefined
|
|
2553
|
+
? { releaseDate: legacyRow.releaseDate }
|
|
2554
|
+
: {}),
|
|
2555
|
+
isLocal: provider === "ollama" ||
|
|
2556
|
+
provider === "lm-studio" ||
|
|
2557
|
+
provider === "llamacpp",
|
|
2558
|
+
category: entry.curated?.category ??
|
|
2559
|
+
(entry.reasoning ? "reasoning" : "general"),
|
|
2560
|
+
};
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
return entries;
|
|
2564
|
+
}
|
|
2565
|
+
/**
|
|
2566
|
+
* Public MODEL_REGISTRY: the manifest-derived entries merged over the
|
|
2567
|
+
* hand-authored LEGACY_MODEL_REGISTRY base. Spread order matters — manifest
|
|
2568
|
+
* entries are spread last, so a manifest-priced model wins outright
|
|
2569
|
+
* (whole-entry replacement, not a per-field merge) over a legacy row with
|
|
2570
|
+
* the same id. Any legacy id the manifest doesn't also price (no
|
|
2571
|
+
* pricingPerMTok, or no manifest entry at all — e.g. AnthropicModels.
|
|
2572
|
+
* CLAUDE_OPUS_5/CLAUDE_FABLE_5, which have no manifest counterpart yet)
|
|
2573
|
+
* passes through unchanged, which is what keeps resolveSamplingParams/
|
|
2574
|
+
* modelSupportsSamplingParams behaviour identical for those "non-manifest"
|
|
2575
|
+
* models: their capabilities.samplingParams (or absence of it) is untouched.
|
|
2576
|
+
*/
|
|
2577
|
+
export const MODEL_REGISTRY = {
|
|
2578
|
+
...LEGACY_MODEL_REGISTRY,
|
|
2579
|
+
...buildManifestDerivedEntries(),
|
|
2580
|
+
};
|
|
2453
2581
|
/**
|
|
2454
2582
|
* Model aliases registry for quick resolution
|
|
2455
2583
|
*/
|
|
@@ -2644,14 +2772,21 @@ export function getModelsByProvider(provider) {
|
|
|
2644
2772
|
return Object.values(MODEL_REGISTRY).filter((model) => model.provider === provider);
|
|
2645
2773
|
}
|
|
2646
2774
|
/**
|
|
2647
|
-
* Get available providers
|
|
2775
|
+
* Get available providers.
|
|
2776
|
+
*
|
|
2777
|
+
* Deliberately does NOT derive from MODEL_REGISTRY membership: a registry
|
|
2778
|
+
* keyed only by "real, priced, named models" under-reports providers whose
|
|
2779
|
+
* manifest carries only a `_default` entry (the minimal-tier providers —
|
|
2780
|
+
* e.g. voyage, jina, stability — have no priced named model and so
|
|
2781
|
+
* contribute zero MODEL_REGISTRY rows). Reading getAllManifestProviders()
|
|
2782
|
+
* directly gives every provider NeuroLink actually knows a manifest for,
|
|
2783
|
+
* decoupled from MODEL_REGISTRY membership entirely. Manifest keys are
|
|
2784
|
+
* literally the AIProviderName enum's kebab-case values by construction
|
|
2785
|
+
* (see manifestRegistry.ts), so the cast is a same-value relabel, not a
|
|
2786
|
+
* narrowing assumption.
|
|
2648
2787
|
*/
|
|
2649
2788
|
export function getAvailableProviders() {
|
|
2650
|
-
|
|
2651
|
-
Object.values(MODEL_REGISTRY).forEach((model) => {
|
|
2652
|
-
providers.add(model.provider);
|
|
2653
|
-
});
|
|
2654
|
-
return Array.from(providers);
|
|
2789
|
+
return getAllManifestProviders();
|
|
2655
2790
|
}
|
|
2656
2791
|
/**
|
|
2657
2792
|
* Calculate estimated cost for a request
|
|
@@ -91,7 +91,7 @@ export declare class AmazonSageMakerProvider extends BaseProvider {
|
|
|
91
91
|
provider: string;
|
|
92
92
|
specificationVersion: "v2";
|
|
93
93
|
endpointName: string;
|
|
94
|
-
modelType: "huggingface" | "mistral" | "custom" | "
|
|
94
|
+
modelType: "huggingface" | "mistral" | "custom" | "llama" | "claude" | "jumpstart" | undefined;
|
|
95
95
|
region: string;
|
|
96
96
|
} | {
|
|
97
97
|
capabilities: {
|
|
@@ -15,6 +15,7 @@ import { getCapturedLimitSnapshot, getCapturedResponseHeaders, logClaudeLimitSna
|
|
|
15
15
|
import { AuthenticationError, NetworkError, ProviderError, RateLimitError, } from "../../types/index.js";
|
|
16
16
|
import { classifyProviderError } from "../../utils/errorClassifier.js";
|
|
17
17
|
import { logger } from "../../utils/logger.js";
|
|
18
|
+
import { drainDetachedPump } from "../../utils/drainDetachedPump.js";
|
|
18
19
|
import { ANTHROPIC_ELISION_NOTE, planAnthropicLoopReclaim, previewAnthropicToolResultText, } from "../../context/anthropicLoopGuard.js";
|
|
19
20
|
import { getAvailableInputTokens } from "../../constants/contextWindows.js";
|
|
20
21
|
import { estimateTokens } from "../../utils/tokenEstimation.js";
|
|
@@ -1749,15 +1750,15 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
1749
1750
|
// formatted one the caller received, which is why the existing
|
|
1750
1751
|
// `loopPromise.catch` guard below does not cover it.
|
|
1751
1752
|
//
|
|
1752
|
-
//
|
|
1753
|
-
//
|
|
1754
|
-
//
|
|
1753
|
+
// Every detached-drain site in the codebase now goes through
|
|
1754
|
+
// drainDetachedPump(), which adopts the rejection and logs the reason at
|
|
1755
|
+
// debug instead of discarding it silently.
|
|
1755
1756
|
let result;
|
|
1756
1757
|
try {
|
|
1757
1758
|
result = await resultPromise;
|
|
1758
1759
|
}
|
|
1759
1760
|
catch (error) {
|
|
1760
|
-
await pump
|
|
1761
|
+
await drainDetachedPump(pump, "Anthropic");
|
|
1761
1762
|
throw error;
|
|
1762
1763
|
}
|
|
1763
1764
|
await pump;
|
|
@@ -6,6 +6,7 @@ import { ATTR, tracers, withClientSpan, withClientStreamSpan, withSpan, } from "
|
|
|
6
6
|
import { AuthenticationError, InvalidModelError, NetworkError, ProviderError, RateLimitError, } from "../../types/index.js";
|
|
7
7
|
import { ERROR_CODES, NeuroLinkError } from "../../utils/errorHandling.js";
|
|
8
8
|
import { logger } from "../../utils/logger.js";
|
|
9
|
+
import { drainDetachedPump } from "../../utils/drainDetachedPump.js";
|
|
9
10
|
import { createGeminiLoopAdapter } from "../../core/geminiLoopAdapter.js";
|
|
10
11
|
import { runAgenticLoop } from "../../core/loopEngine.js";
|
|
11
12
|
import { DEFAULT_TOOL_MAX_RETRIES } from "../../core/constants.js";
|
|
@@ -861,7 +862,7 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
861
862
|
engineResult = await resultPromise;
|
|
862
863
|
}
|
|
863
864
|
catch (error) {
|
|
864
|
-
await pump
|
|
865
|
+
await drainDetachedPump(pump, "GoogleAIStudio");
|
|
865
866
|
logger.error("[GoogleAIStudio] Native SDK error", error);
|
|
866
867
|
throw this.handleProviderError(error);
|
|
867
868
|
}
|
|
@@ -1173,7 +1174,7 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
1173
1174
|
engineResult = await resultPromise;
|
|
1174
1175
|
}
|
|
1175
1176
|
catch (error) {
|
|
1176
|
-
await drain
|
|
1177
|
+
await drainDetachedPump(drain, "GoogleAIStudio");
|
|
1177
1178
|
logger.error("[GoogleAIStudio] Native SDK generate error", error);
|
|
1178
1179
|
throw this.handleProviderError(error);
|
|
1179
1180
|
}
|
|
@@ -20,6 +20,7 @@ import { applyVertexAnthropicCacheBreakpoints } from "../../utils/anthropicCache
|
|
|
20
20
|
import { FileDetector } from "../../utils/fileDetector.js";
|
|
21
21
|
import { mergeMediaFileAliases, normalizeVisionImageFormats, processUnifiedFilesArray, } from "../../utils/messageBuilder.js";
|
|
22
22
|
import { logger } from "../../utils/logger.js";
|
|
23
|
+
import { drainDetachedPump } from "../../utils/drainDetachedPump.js";
|
|
23
24
|
import { GEMINI_ELISION_NOTE, planGeminiLoopReclaim, previewGeminiToolResponseText, } from "../../context/geminiLoopGuard.js";
|
|
24
25
|
import { ANTHROPIC_ELISION_NOTE, planAnthropicLoopReclaim, previewAnthropicToolResultText, } from "../../context/anthropicLoopGuard.js";
|
|
25
26
|
import { hasRestrictedOutputLimit, RESTRICTED_OUTPUT_TOKEN_LIMIT, toVertexAnthropicModelId, } from "../../utils/modelDetection.js";
|
|
@@ -1814,7 +1815,7 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
1814
1815
|
// rethrow the very error the branch below has already decided to absorb —
|
|
1815
1816
|
// which is what turned both turn-clock cases into failures instead of
|
|
1816
1817
|
// clean deadline exits.
|
|
1817
|
-
await pump
|
|
1818
|
+
await drainDetachedPump(pump, "GoogleVertex");
|
|
1818
1819
|
if (turnFailure !== undefined) {
|
|
1819
1820
|
// A mid-drain abort surfaces as an AbortError. End gracefully into the
|
|
1820
1821
|
// terminal block instead of re-throwing — a re-throw would route the
|
|
@@ -2598,7 +2599,7 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
2598
2599
|
engineResult = await resultPromise;
|
|
2599
2600
|
}
|
|
2600
2601
|
catch (error) {
|
|
2601
|
-
await pump
|
|
2602
|
+
await drainDetachedPump(pump, "GoogleVertex");
|
|
2602
2603
|
// A mid-drain abort surfaces as an AbortError. End gracefully into the
|
|
2603
2604
|
// terminal block instead of re-throwing — a re-throw would route the
|
|
2604
2605
|
// caller's abort into a second unbounded fallback stream().
|
|
@@ -3687,7 +3688,7 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
3687
3688
|
// Drained tolerantly and exactly once: when a turn ends by abort the
|
|
3688
3689
|
// channel rejects too, and re-awaiting a settled rejection would rethrow
|
|
3689
3690
|
// the error the branch below has already decided to absorb.
|
|
3690
|
-
await pump
|
|
3691
|
+
await drainDetachedPump(pump, "GoogleVertex");
|
|
3691
3692
|
if (turnFailure !== undefined) {
|
|
3692
3693
|
if (internalAbort.signal.aborted || isAbortError(turnFailure)) {
|
|
3693
3694
|
wasAborted = true;
|
|
@@ -4686,7 +4687,7 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
4686
4687
|
catch (error) {
|
|
4687
4688
|
turnFailure = error;
|
|
4688
4689
|
}
|
|
4689
|
-
await pump
|
|
4690
|
+
await drainDetachedPump(pump, "GoogleVertex");
|
|
4690
4691
|
if (turnFailure !== undefined) {
|
|
4691
4692
|
if (internalAbort.signal.aborted || isAbortError(turnFailure)) {
|
|
4692
4693
|
wasAborted = true;
|
|
@@ -153,7 +153,7 @@ export declare class SageMakerLanguageModel implements SageMakerAsLanguageModel
|
|
|
153
153
|
provider: string;
|
|
154
154
|
specificationVersion: "v2";
|
|
155
155
|
endpointName: string;
|
|
156
|
-
modelType: "huggingface" | "mistral" | "custom" | "
|
|
156
|
+
modelType: "huggingface" | "mistral" | "custom" | "llama" | "claude" | "jumpstart" | undefined;
|
|
157
157
|
region: string;
|
|
158
158
|
};
|
|
159
159
|
/**
|
|
@@ -200,7 +200,7 @@ export declare class SageMakerLanguageModel implements SageMakerAsLanguageModel
|
|
|
200
200
|
provider: string;
|
|
201
201
|
specificationVersion: "v2";
|
|
202
202
|
endpointName: string;
|
|
203
|
-
modelType: "huggingface" | "mistral" | "custom" | "
|
|
203
|
+
modelType: "huggingface" | "mistral" | "custom" | "llama" | "claude" | "jumpstart" | undefined;
|
|
204
204
|
region: string;
|
|
205
205
|
};
|
|
206
206
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Await a detached stream pump, swallowing its rejection but not its evidence.
|
|
3
|
+
*
|
|
4
|
+
* Several providers drain their engine's channel with a pump started outside
|
|
5
|
+
* the promise chain the caller awaits:
|
|
6
|
+
*
|
|
7
|
+
* const pump = (async () => { for await (const chunk of stream) { ... } })();
|
|
8
|
+
* const result = await resultPromise; // can throw
|
|
9
|
+
* await pump; // never reached if it does
|
|
10
|
+
*
|
|
11
|
+
* When the turn fails, `resultPromise` and the pump reject together — the
|
|
12
|
+
* engine calls `channel.error(err)` before closing — so the pump must be
|
|
13
|
+
* adopted on the error path too. Nothing adopting it is not a cosmetic leak:
|
|
14
|
+
* an unhandled rejection TERMINATES the process, so a caller who correctly
|
|
15
|
+
* try/catches the streaming error still dies. That was a real bug in the
|
|
16
|
+
* Anthropic path.
|
|
17
|
+
*
|
|
18
|
+
* The established remedy is `await pump.catch(() => {})`, which every site
|
|
19
|
+
* already uses. The gap this closes is the second half: `() => {}` throws the
|
|
20
|
+
* reason away, so the raw upstream error — the one carrying the provider's
|
|
21
|
+
* actual wire response — was invisible in traces at every one of the seven
|
|
22
|
+
* sites (six named `pump`, plus one named `drain` in googleAiStudio's
|
|
23
|
+
* non-streaming path, which a search for `pump` does not find). It is logged at DEBUG rather than WARN deliberately: on a failing turn
|
|
24
|
+
* this reason is almost always a duplicate of the error the caller is already
|
|
25
|
+
* being handed, and on an aborted turn it is the expected AbortError. It is
|
|
26
|
+
* diagnostic detail, not a new event worth alerting on.
|
|
27
|
+
*
|
|
28
|
+
* Behaviour is otherwise identical to `await pump.catch(() => {})`: it awaits,
|
|
29
|
+
* it never rethrows.
|
|
30
|
+
*/
|
|
31
|
+
export declare function drainDetachedPump(pump: Promise<unknown>, providerLabel: string): Promise<void>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { logger } from "./logger.js";
|
|
2
|
+
/**
|
|
3
|
+
* Await a detached stream pump, swallowing its rejection but not its evidence.
|
|
4
|
+
*
|
|
5
|
+
* Several providers drain their engine's channel with a pump started outside
|
|
6
|
+
* the promise chain the caller awaits:
|
|
7
|
+
*
|
|
8
|
+
* const pump = (async () => { for await (const chunk of stream) { ... } })();
|
|
9
|
+
* const result = await resultPromise; // can throw
|
|
10
|
+
* await pump; // never reached if it does
|
|
11
|
+
*
|
|
12
|
+
* When the turn fails, `resultPromise` and the pump reject together — the
|
|
13
|
+
* engine calls `channel.error(err)` before closing — so the pump must be
|
|
14
|
+
* adopted on the error path too. Nothing adopting it is not a cosmetic leak:
|
|
15
|
+
* an unhandled rejection TERMINATES the process, so a caller who correctly
|
|
16
|
+
* try/catches the streaming error still dies. That was a real bug in the
|
|
17
|
+
* Anthropic path.
|
|
18
|
+
*
|
|
19
|
+
* The established remedy is `await pump.catch(() => {})`, which every site
|
|
20
|
+
* already uses. The gap this closes is the second half: `() => {}` throws the
|
|
21
|
+
* reason away, so the raw upstream error — the one carrying the provider's
|
|
22
|
+
* actual wire response — was invisible in traces at every one of the seven
|
|
23
|
+
* sites (six named `pump`, plus one named `drain` in googleAiStudio's
|
|
24
|
+
* non-streaming path, which a search for `pump` does not find). It is logged at DEBUG rather than WARN deliberately: on a failing turn
|
|
25
|
+
* this reason is almost always a duplicate of the error the caller is already
|
|
26
|
+
* being handed, and on an aborted turn it is the expected AbortError. It is
|
|
27
|
+
* diagnostic detail, not a new event worth alerting on.
|
|
28
|
+
*
|
|
29
|
+
* Behaviour is otherwise identical to `await pump.catch(() => {})`: it awaits,
|
|
30
|
+
* it never rethrows.
|
|
31
|
+
*/
|
|
32
|
+
export async function drainDetachedPump(pump, providerLabel) {
|
|
33
|
+
try {
|
|
34
|
+
await pump;
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
logger.debug(`[${providerLabel}] detached stream pump rejected; reason absorbed because the turn's own error is authoritative`, error);
|
|
38
|
+
}
|
|
39
|
+
}
|
package/dist/utils/pricing.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getManifestForProvider, resolveManifestEntryExact, } from "../models/manifestRegistry.js";
|
|
1
2
|
/**
|
|
2
3
|
* Per-token pricing data (USD per token). Updated Feb 2026.
|
|
3
4
|
* Sources:
|
|
@@ -728,6 +729,22 @@ function isVersionOnlySuffix(model, key) {
|
|
|
728
729
|
}
|
|
729
730
|
return VERSION_SUFFIX_RE.test(model.slice(key.length));
|
|
730
731
|
}
|
|
732
|
+
/**
|
|
733
|
+
* Whether `model` matches a provider manifest by the manifest's own
|
|
734
|
+
* canonical id or by one of its declared aliases — deliberately excludes
|
|
735
|
+
* resolveManifestEntryExact's longest-prefix fallback. See the call site in
|
|
736
|
+
* findRates for why the prefix path is unsafe to use for pricing precedence.
|
|
737
|
+
*/
|
|
738
|
+
function isManifestNamedMatch(provider, model) {
|
|
739
|
+
const manifest = getManifestForProvider(provider);
|
|
740
|
+
if (!manifest) {
|
|
741
|
+
return false;
|
|
742
|
+
}
|
|
743
|
+
if (Object.prototype.hasOwnProperty.call(manifest.models, model)) {
|
|
744
|
+
return true;
|
|
745
|
+
}
|
|
746
|
+
return Object.values(manifest.models).some((entry) => entry.aliases.includes(model));
|
|
747
|
+
}
|
|
731
748
|
function findRates(provider, model,
|
|
732
749
|
/**
|
|
733
750
|
* Set to true when the rates came from a literal table key rather than a
|
|
@@ -778,6 +795,40 @@ matchKind) {
|
|
|
778
795
|
const modelKey = stripped === "bedrock" || stripped === "amazonbedrock"
|
|
779
796
|
? model.replace(/^.*\banthropic\./, "")
|
|
780
797
|
: model;
|
|
798
|
+
// Manifest-backed exact/alias match takes precedence over legacy PRICING
|
|
799
|
+
// (see Task 8 of the model metadata consolidation plan). Deliberately
|
|
800
|
+
// gated to a NAMED match only (manifest's own id or a declared alias) —
|
|
801
|
+
// never resolveManifestEntryExact's longest-prefix fallback. The
|
|
802
|
+
// manifest's model set is coarser-grained than PRICING's for several
|
|
803
|
+
// live ids today (e.g. openai's manifest names only "gpt-5" while
|
|
804
|
+
// PRICING.openai carries distinct exact entries for
|
|
805
|
+
// gpt-5.1/5.4/5.5/5.6-sol/terra/luna); letting the manifest's prefix
|
|
806
|
+
// match run first would silently steal a more specific PRICING entry's
|
|
807
|
+
// rate — confirmed: resolveManifestEntryExact("openai", "gpt-5.4")
|
|
808
|
+
// prefix-matches onto the "gpt-5" entry, $1.25/$10 instead of the
|
|
809
|
+
// correct $2.5/$15. Everything the manifest doesn't name exactly or
|
|
810
|
+
// alias — including a named entry with no pricingPerMTok, e.g.
|
|
811
|
+
// claude-sonnet-5 — falls straight through to PRICING's unchanged
|
|
812
|
+
// exact+prefix match below, which is exactly "legacy PRICING as the
|
|
813
|
+
// fallback for entries the manifest lacks."
|
|
814
|
+
if (isManifestNamedMatch(normalizedProvider, modelKey)) {
|
|
815
|
+
const manifestEntry = resolveManifestEntryExact(normalizedProvider, modelKey);
|
|
816
|
+
if (manifestEntry?.pricingPerMTok) {
|
|
817
|
+
if (matchKind) {
|
|
818
|
+
matchKind.exact = true;
|
|
819
|
+
}
|
|
820
|
+
return {
|
|
821
|
+
input: manifestEntry.pricingPerMTok.input / 1_000_000,
|
|
822
|
+
output: manifestEntry.pricingPerMTok.output / 1_000_000,
|
|
823
|
+
cacheRead: manifestEntry.pricingPerMTok.cacheRead !== undefined
|
|
824
|
+
? manifestEntry.pricingPerMTok.cacheRead / 1_000_000
|
|
825
|
+
: undefined,
|
|
826
|
+
cacheCreation: manifestEntry.pricingPerMTok.cacheWrite !== undefined
|
|
827
|
+
? manifestEntry.pricingPerMTok.cacheWrite / 1_000_000
|
|
828
|
+
: undefined,
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
}
|
|
781
832
|
// Exact match
|
|
782
833
|
if (providerPricing[modelKey]) {
|
|
783
834
|
if (matchKind) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.27.0",
|
|
4
4
|
"packageManager": "pnpm@10.15.1",
|
|
5
5
|
"description": "TypeScript AI SDK with 24+ LLM providers behind one consistent API. MCP-native (connect any MCP server), voice TTS/STT/realtime, RAG, agents, memory, context compaction. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · DeepSeek · NVIDIA NIM and more.",
|
|
6
6
|
"author": {
|