@juspay/neurolink 11.5.1 → 11.6.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.
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Bedrock cross-region inference profile resolution.
3
+ *
4
+ * Most current Bedrock models cannot be invoked by their bare model id. Each
5
+ * model card carries a Regional Availability table with three columns, and for
6
+ * a great many model/region pairs the In-Region column is "No" — Claude Opus
7
+ * 4.5 is No in every region AWS lists, Sonnet 4.6 is Yes only in eu-west-2.
8
+ * Those models are reachable only through a cross-region inference profile: the
9
+ * same id with a geography prefix (`us.`, `eu.`, `au.`, `jp.`, `apac.`) or the
10
+ * worldwide `global.` prefix.
11
+ *
12
+ * Sending the bare id where In-Region is No fails with a ValidationException
13
+ * telling you to use an inference profile. This module detects that specific
14
+ * failure and works out which prefixed id to retry with.
15
+ *
16
+ * ## Why prefixes are derived rather than tabulated
17
+ *
18
+ * The obvious implementation — a table of model to supported prefixes — is the
19
+ * one to avoid. The prefix set is per-model, not per-region: from the same
20
+ * caller region `ap-northeast-1`, Claude Opus 4.5 accepts only `global.`,
21
+ * Sonnet 4.5 also accepts `jp.`, and Haiku 4.5 works bare. A static table
22
+ * would need a row per model per region and would be wrong the day a model
23
+ * ships. AWS itself moved this data out of a central page and onto each
24
+ * model's detail page for the same reason.
25
+ *
26
+ * So resolution is empirical: on the specific error, try the geography that
27
+ * covers the caller's region, then `global.`, and remember what worked.
28
+ *
29
+ * `bedrock:ListInferenceProfiles` would give an authoritative answer, but it
30
+ * is a control-plane call needing a separate SDK client and an IAM permission
31
+ * a caller with plain inference access will not necessarily hold. Deriving
32
+ * candidates needs neither, and costs at most two extra attempts once per
33
+ * model/region pair.
34
+ */
35
+ /**
36
+ * Does this error mean "this model needs an inference profile in this region"?
37
+ *
38
+ * Deliberately narrow. A ValidationException covers many unrelated causes —
39
+ * a malformed body, an unknown parameter, a model the account has no access to
40
+ * — and retrying those with a different model id would turn one clear failure
41
+ * into two confusing ones. Both the exception name and the distinctive phrase
42
+ * must match.
43
+ *
44
+ * The phrase is matched loosely enough to survive AWS rewording the sentence
45
+ * around it, and the retry is a no-op when nothing resolves, so a miss here
46
+ * costs nothing beyond the original error surfacing unchanged — which is the
47
+ * behaviour without this module at all.
48
+ */
49
+ export declare function isInferenceProfileRequiredError(error: unknown): boolean;
50
+ /**
51
+ * Candidate ids to retry, most specific first: the caller's own geography,
52
+ * then worldwide.
53
+ *
54
+ * Returns an empty array when the id already carries a prefix — a prefixed id
55
+ * that still fails is a real error, not something to re-prefix.
56
+ */
57
+ export declare function inferenceProfileCandidates(modelId: string, region: string): string[];
58
+ /** The id that previously worked for this model in this region, if any. */
59
+ export declare function getResolvedModelId(modelId: string, region: string): string | undefined;
60
+ export declare function rememberResolvedModelId(modelId: string, region: string, resolvedId: string): void;
61
+ /** Exposed so tests can start from a known state. */
62
+ export declare function clearResolvedModelIds(): void;
63
+ /**
64
+ * Run `send` against the given model id, falling back to inference-profile
65
+ * ids if — and only if — Bedrock says the bare id needs one.
66
+ *
67
+ * A previously resolved id is used straight away. Any error other than the
68
+ * inference-profile one propagates untouched on the first attempt, so this
69
+ * cannot mask an unrelated failure or silently change behaviour for a caller
70
+ * whose bare ids already work.
71
+ */
72
+ export declare function withInferenceProfileFallback<T>(modelId: string, region: string, send: (effectiveModelId: string) => Promise<T>): Promise<T>;
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Bedrock cross-region inference profile resolution.
3
+ *
4
+ * Most current Bedrock models cannot be invoked by their bare model id. Each
5
+ * model card carries a Regional Availability table with three columns, and for
6
+ * a great many model/region pairs the In-Region column is "No" — Claude Opus
7
+ * 4.5 is No in every region AWS lists, Sonnet 4.6 is Yes only in eu-west-2.
8
+ * Those models are reachable only through a cross-region inference profile: the
9
+ * same id with a geography prefix (`us.`, `eu.`, `au.`, `jp.`, `apac.`) or the
10
+ * worldwide `global.` prefix.
11
+ *
12
+ * Sending the bare id where In-Region is No fails with a ValidationException
13
+ * telling you to use an inference profile. This module detects that specific
14
+ * failure and works out which prefixed id to retry with.
15
+ *
16
+ * ## Why prefixes are derived rather than tabulated
17
+ *
18
+ * The obvious implementation — a table of model to supported prefixes — is the
19
+ * one to avoid. The prefix set is per-model, not per-region: from the same
20
+ * caller region `ap-northeast-1`, Claude Opus 4.5 accepts only `global.`,
21
+ * Sonnet 4.5 also accepts `jp.`, and Haiku 4.5 works bare. A static table
22
+ * would need a row per model per region and would be wrong the day a model
23
+ * ships. AWS itself moved this data out of a central page and onto each
24
+ * model's detail page for the same reason.
25
+ *
26
+ * So resolution is empirical: on the specific error, try the geography that
27
+ * covers the caller's region, then `global.`, and remember what worked.
28
+ *
29
+ * `bedrock:ListInferenceProfiles` would give an authoritative answer, but it
30
+ * is a control-plane call needing a separate SDK client and an IAM permission
31
+ * a caller with plain inference access will not necessarily hold. Deriving
32
+ * candidates needs neither, and costs at most two extra attempts once per
33
+ * model/region pair.
34
+ */
35
+ import { logger } from "../../utils/logger.js";
36
+ /**
37
+ * Geography prefixes, in the order AWS documents them. `global.` is not a
38
+ * geography — it routes anywhere and is tried last, as the broadest option.
39
+ */
40
+ const GLOBAL_PREFIX = "global";
41
+ /**
42
+ * Maps an AWS region to the geography whose inference profile covers it.
43
+ *
44
+ * Derived from the Geo inference tables on the model cards: the US geography
45
+ * covers `us-*` plus the Canadian regions, EU covers `eu-*` plus Israel, the
46
+ * Middle East and Africa, Japan covers the two Japanese regions, and Australia
47
+ * covers Sydney, Melbourne and New Zealand. Everything else in Asia Pacific
48
+ * falls under `apac`.
49
+ */
50
+ function geoPrefixForRegion(region) {
51
+ if (/^(us|ca)-/.test(region)) {
52
+ return "us";
53
+ }
54
+ if (/^(eu|il|me|af)-/.test(region)) {
55
+ return "eu";
56
+ }
57
+ if (/^ap-northeast-[13]$/.test(region)) {
58
+ return "jp";
59
+ }
60
+ if (/^ap-southeast-[246]$/.test(region)) {
61
+ return "au";
62
+ }
63
+ if (/^ap-/.test(region)) {
64
+ return "apac";
65
+ }
66
+ return undefined;
67
+ }
68
+ /** True when `modelId` already carries a geography or global prefix. */
69
+ function hasProfilePrefix(modelId) {
70
+ return /^(us|eu|au|jp|apac|global)\./.test(modelId);
71
+ }
72
+ /**
73
+ * Does this error mean "this model needs an inference profile in this region"?
74
+ *
75
+ * Deliberately narrow. A ValidationException covers many unrelated causes —
76
+ * a malformed body, an unknown parameter, a model the account has no access to
77
+ * — and retrying those with a different model id would turn one clear failure
78
+ * into two confusing ones. Both the exception name and the distinctive phrase
79
+ * must match.
80
+ *
81
+ * The phrase is matched loosely enough to survive AWS rewording the sentence
82
+ * around it, and the retry is a no-op when nothing resolves, so a miss here
83
+ * costs nothing beyond the original error surfacing unchanged — which is the
84
+ * behaviour without this module at all.
85
+ */
86
+ export function isInferenceProfileRequiredError(error) {
87
+ if (typeof error !== "object" || error === null) {
88
+ return false;
89
+ }
90
+ const candidate = error;
91
+ const name = typeof candidate.name === "string" ? candidate.name : "";
92
+ const message = typeof candidate.message === "string" ? candidate.message : "";
93
+ if (!/ValidationException/i.test(name) &&
94
+ !/ValidationException/i.test(message)) {
95
+ return false;
96
+ }
97
+ return (/inference profile/i.test(message) && /on-demand throughput/i.test(message));
98
+ }
99
+ /**
100
+ * Candidate ids to retry, most specific first: the caller's own geography,
101
+ * then worldwide.
102
+ *
103
+ * Returns an empty array when the id already carries a prefix — a prefixed id
104
+ * that still fails is a real error, not something to re-prefix.
105
+ */
106
+ export function inferenceProfileCandidates(modelId, region) {
107
+ // An ARN already names a concrete model or inference-profile resource, and
108
+ // this provider accepts one as a model id (see `extractRegionFromArn`).
109
+ // Prefixing it yields a malformed identifier whose ValidationException would
110
+ // then replace the original, accurate error.
111
+ if (modelId.startsWith("arn:") || hasProfilePrefix(modelId)) {
112
+ return [];
113
+ }
114
+ const candidates = [];
115
+ const geo = geoPrefixForRegion(region);
116
+ if (geo) {
117
+ candidates.push(`${geo}.${modelId}`);
118
+ }
119
+ candidates.push(`${GLOBAL_PREFIX}.${modelId}`);
120
+ return candidates;
121
+ }
122
+ /**
123
+ * Remembers which id actually worked, keyed by region and bare model id, so
124
+ * the probing happens once rather than on every call.
125
+ */
126
+ const resolved = new Map();
127
+ function cacheKey(region, modelId) {
128
+ return `${region}::${modelId}`;
129
+ }
130
+ /** The id that previously worked for this model in this region, if any. */
131
+ export function getResolvedModelId(modelId, region) {
132
+ return resolved.get(cacheKey(region, modelId));
133
+ }
134
+ export function rememberResolvedModelId(modelId, region, resolvedId) {
135
+ resolved.set(cacheKey(region, modelId), resolvedId);
136
+ logger.debug("[Bedrock] Cached inference-profile resolution for subsequent calls", { region });
137
+ }
138
+ /** Exposed so tests can start from a known state. */
139
+ export function clearResolvedModelIds() {
140
+ resolved.clear();
141
+ }
142
+ /**
143
+ * Run `send` against the given model id, falling back to inference-profile
144
+ * ids if — and only if — Bedrock says the bare id needs one.
145
+ *
146
+ * A previously resolved id is used straight away. Any error other than the
147
+ * inference-profile one propagates untouched on the first attempt, so this
148
+ * cannot mask an unrelated failure or silently change behaviour for a caller
149
+ * whose bare ids already work.
150
+ */
151
+ export async function withInferenceProfileFallback(modelId, region, send) {
152
+ const cached = getResolvedModelId(modelId, region);
153
+ if (cached && cached !== modelId) {
154
+ return send(cached);
155
+ }
156
+ try {
157
+ return await send(modelId);
158
+ }
159
+ catch (error) {
160
+ if (!isInferenceProfileRequiredError(error)) {
161
+ throw error;
162
+ }
163
+ const candidates = inferenceProfileCandidates(modelId, region);
164
+ if (candidates.length === 0) {
165
+ throw error;
166
+ }
167
+ logger.warn("[Bedrock] Model requires a cross-region inference profile in this region; retrying with a prefixed id", { region, candidates: candidates.length });
168
+ for (const candidate of candidates) {
169
+ try {
170
+ const result = await send(candidate);
171
+ rememberResolvedModelId(modelId, region, candidate);
172
+ return result;
173
+ }
174
+ catch (retryError) {
175
+ if (!isInferenceProfileRequiredError(retryError)) {
176
+ // A different failure against the prefixed id — for example the
177
+ // account lacking access to that geography — is the more
178
+ // informative error, so surface it rather than the original.
179
+ throw retryError;
180
+ }
181
+ }
182
+ }
183
+ throw error;
184
+ }
185
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "11.5.1",
3
+ "version": "11.6.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": {
@@ -86,6 +86,7 @@
86
86
  "test:adjust-body-after-400": "npx tsx test/continuous-test-suite-adjust-body-after-400.ts",
87
87
  "test:error-classification-e2e": "npx tsx test/continuous-test-suite-error-classification-e2e.ts",
88
88
  "test:error-classifier-contract": "npx tsx test/continuous-test-suite-error-classifier-contract.ts",
89
+ "test:bedrock-inference-profile": "npx tsx test/continuous-test-suite-bedrock-inference-profile.ts",
89
90
  "test:loop-engine": "npx tsx test/continuous-test-suite-loop-engine.ts",
90
91
  "test:middleware": "npx tsx test/continuous-test-suite-middleware.ts",
91
92
  "test:observability": "npx tsx test/continuous-test-suite-observability.ts",