@juspay/neurolink 12.49.14 → 12.49.15

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.
Files changed (55) hide show
  1. package/CHANGELOG.md +2 -2
  2. package/dist/browser/neurolink.min.js +411 -421
  3. package/dist/cli/factories/commandFactory.d.ts +12 -0
  4. package/dist/cli/factories/commandFactory.js +11 -1
  5. package/dist/constants/bedrockIdentifiers.d.ts +16 -0
  6. package/dist/constants/bedrockIdentifiers.js +79 -0
  7. package/dist/constants/contextWindows.d.ts +6 -15
  8. package/dist/constants/contextWindows.js +18 -52
  9. package/dist/constants/enums.d.ts +12 -0
  10. package/dist/constants/enums.js +15 -0
  11. package/dist/constants/tokens.js +9 -0
  12. package/dist/core/loopEngine.js +8 -0
  13. package/dist/providers/amazonBedrock/client.js +3 -0
  14. package/dist/providers/amazonBedrock/inferenceProfile.js +9 -5
  15. package/dist/providers/catalog/abliteration.json +85 -0
  16. package/dist/providers/catalog/aihubmix.json +77 -0
  17. package/dist/providers/catalog/api-route.json +1 -1
  18. package/dist/providers/catalog/baseten.json +1 -1
  19. package/dist/providers/catalog/deepseek.json +1 -1
  20. package/dist/providers/catalog/gmicloud.json +1 -1
  21. package/dist/providers/catalog/huggingface.json +1 -1
  22. package/dist/providers/catalog/inception-labs.json +1 -1
  23. package/dist/providers/catalog/index.generated.d.ts +1 -1
  24. package/dist/providers/catalog/index.generated.js +9 -0
  25. package/dist/providers/catalog/io-intelligence.json +1 -1
  26. package/dist/providers/catalog/loader.js +3 -0
  27. package/dist/providers/catalog/mancer.json +1 -1
  28. package/dist/providers/catalog/morph.json +1 -1
  29. package/dist/providers/catalog/naver-clova-studio.json +68 -0
  30. package/dist/providers/catalog/novita.json +1 -1
  31. package/dist/providers/catalog/provider-catalog.schema.json +27 -4
  32. package/dist/providers/catalog/schema.d.ts +3 -1
  33. package/dist/providers/catalog/schema.js +15 -2
  34. package/dist/providers/catalog/upstage.json +1 -1
  35. package/dist/providers/cohere.js +7 -2
  36. package/dist/providers/configuredOpenAICompat.js +7 -0
  37. package/dist/providers/embeddingResponseParsing.d.ts +15 -0
  38. package/dist/providers/embeddingResponseParsing.js +4 -3
  39. package/dist/providers/googleAiStudio/client.js +104 -26
  40. package/dist/rag/chunking/htmlChunker.js +4 -11
  41. package/dist/rag/document/loaders.js +12 -14
  42. package/dist/server/openapi/generator.d.ts +0 -6
  43. package/dist/server/openapi/generator.js +2 -72
  44. package/dist/types/cli.d.ts +6 -0
  45. package/dist/types/loopEngine.d.ts +7 -0
  46. package/dist/types/providerCatalog.d.ts +15 -1
  47. package/dist/types/providerCatalog.generated.d.ts +2 -2
  48. package/dist/types/providers.d.ts +15 -0
  49. package/dist/types/safeFetch.d.ts +1 -1
  50. package/dist/utils/htmlEntities.d.ts +8 -0
  51. package/dist/utils/htmlEntities.js +29 -0
  52. package/dist/utils/safeFetch.js +11 -3
  53. package/dist/utils/tokenLimits.js +23 -4
  54. package/docs-site/static/search-index.json +27 -16
  55. package/package.json +5 -3
@@ -7,6 +7,7 @@ import { AuthenticationError, InvalidModelError, NetworkError, ProviderError, Ra
7
7
  import { ERROR_CODES, NeuroLinkError } from "../../utils/errorHandling.js";
8
8
  import { logger } from "../../utils/logger.js";
9
9
  import { drainDetachedPump } from "../../utils/drainDetachedPump.js";
10
+ import { attachStreamCancel, cancelStream, releaseIterator, } from "../../utils/streamCancellation.js";
10
11
  import { createGeminiLoopAdapter } from "../../core/geminiLoopAdapter.js";
11
12
  import { isDirectTTSRequest } from "../../core/resolveRequestKind.js";
12
13
  import { runAgenticLoop } from "../../core/loopEngine.js";
@@ -2186,21 +2187,58 @@ export class GoogleAIStudioProvider extends BaseProvider {
2186
2187
  "gemini-2.5-flash-preview-native-audio-dialog";
2187
2188
  // Simple async queue for yielding audio events to the outer AsyncIterable
2188
2189
  const queue = [];
2189
- let resolveNext = null;
2190
+ let notifyNext = null;
2190
2191
  let done = false;
2191
- const push = (item) => {
2192
- if (done) {
2193
- return;
2192
+ let terminalQueued = false;
2193
+ let cleanupRequested = false;
2194
+ let liveSession = undefined;
2195
+ let sessionCloseIssued = false;
2196
+ let inputIterator;
2197
+ let removeAbortListener = () => undefined;
2198
+ const wake = () => {
2199
+ const notify = notifyNext;
2200
+ notifyNext = null;
2201
+ notify?.();
2202
+ };
2203
+ const cleanup = () => {
2204
+ if (!cleanupRequested) {
2205
+ cleanupRequested = true;
2206
+ removeAbortListener();
2207
+ cancelStream(options.input?.audio?.frames);
2208
+ if (inputIterator) {
2209
+ releaseIterator(inputIterator);
2210
+ inputIterator = undefined;
2211
+ }
2194
2212
  }
2195
- if (item.type === "audio") {
2196
- if (resolveNext) {
2197
- const fn = resolveNext;
2198
- resolveNext = null;
2199
- fn({ value: { type: "audio", audio: item.audio }, done: false });
2200
- return;
2213
+ // A close callback can run just before connect() yields its session.
2214
+ // Keep cleanup repeatable so that newly acquired handle is still closed.
2215
+ if (liveSession && !sessionCloseIssued) {
2216
+ sessionCloseIssued = true;
2217
+ try {
2218
+ void Promise.resolve(liveSession.close?.()).catch(() => { });
2219
+ }
2220
+ catch {
2221
+ // Cleanup cannot replace the stream's original outcome.
2201
2222
  }
2202
2223
  }
2224
+ };
2225
+ const push = (item) => {
2226
+ if (done || terminalQueued) {
2227
+ return;
2228
+ }
2203
2229
  queue.push(item);
2230
+ if (item.type !== "audio") {
2231
+ terminalQueued = true;
2232
+ cleanup();
2233
+ }
2234
+ // A consumer can already be parked before either end or error arrives.
2235
+ wake();
2236
+ };
2237
+ const cancel = () => {
2238
+ done = true;
2239
+ queue.length = 0;
2240
+ cleanup();
2241
+ wake();
2204
2242
  };
2205
2243
  // @google/genai 2.x waits inside connect() for the server's setupComplete
2206
2244
  // message and does not reject when the socket errors or closes first, so a
@@ -2219,6 +2257,9 @@ export class GoogleAIStudioProvider extends BaseProvider {
2219
2257
  // no-op
2220
2258
  },
2221
2259
  onmessage: async (message) => {
2260
+ if (done || terminalQueued) {
2261
+ return;
2262
+ }
2222
2263
  try {
2223
2264
  const audio = message?.serverContent?.modelTurn?.parts?.[0]?.inlineData;
2224
2265
  if (audio?.data) {
@@ -2260,16 +2301,50 @@ export class GoogleAIStudioProvider extends BaseProvider {
2260
2301
  },
2261
2302
  });
2262
2303
  const session = await Promise.race([connecting, connectFailed]);
2304
+ liveSession = session;
2263
2305
  sessionOpen = true;
2306
+ if (cleanupRequested) {
2307
+ cleanup();
2308
+ }
2309
+ const signal = options.abortSignal;
2310
+ if (signal && !cleanupRequested) {
2311
+ const onAbort = () => {
2312
+ push({
2313
+ type: "error",
2314
+ error: signal.reason instanceof Error
2315
+ ? signal.reason
2316
+ : new DOMException("The operation was aborted", "AbortError"),
2317
+ });
2318
+ };
2319
+ signal.addEventListener("abort", onAbort, { once: true });
2320
+ removeAbortListener = () => signal.removeEventListener("abort", onAbort);
2321
+ if (signal.aborted) {
2322
+ onAbort();
2323
+ }
2324
+ }
2264
2325
  // Feed upstream audio frames concurrently
2265
2326
  (async () => {
2266
2327
  try {
2328
+ if (cleanupRequested) {
2329
+ return;
2330
+ }
2267
2331
  const spec = options.input?.audio;
2268
2332
  if (!spec) {
2269
2333
  logger.debug("[GeminiLive] No audio spec found on input; skipping upstream send");
2270
2334
  return;
2271
2335
  }
2272
- for await (const frame of spec.frames) {
2336
+ const framesIterator = spec.frames[Symbol.asyncIterator]();
2337
+ inputIterator = framesIterator;
2338
+ while (!cleanupRequested) {
2339
+ const nextFrame = await framesIterator.next();
2340
+ if (cleanupRequested) {
2341
+ return;
2342
+ }
2343
+ if (nextFrame.done) {
2344
+ inputIterator = undefined;
2345
+ break;
2346
+ }
2347
+ const frame = nextFrame.value;
2273
2348
  // Zero-length frame acts as a 'flush' control signal
2274
2349
  if (!frame || frame.byteLength === 0) {
2275
2350
  try {
@@ -2294,6 +2369,9 @@ export class GoogleAIStudioProvider extends BaseProvider {
2294
2369
  media: { data: base64, mimeType },
2295
2370
  });
2296
2371
  }
2372
+ if (cleanupRequested) {
2373
+ return;
2374
+ }
2297
2375
  // Best-effort flush signal if supported
2298
2376
  try {
2299
2377
  if (session.sendInput) {
@@ -2320,40 +2398,40 @@ export class GoogleAIStudioProvider extends BaseProvider {
2320
2398
  [Symbol.asyncIterator]() {
2321
2399
  return {
2322
2400
  async next() {
2323
- if (queue.length > 0) {
2401
+ while (true) {
2324
2402
  const item = queue.shift();
2325
- if (!item) {
2326
- // `done: true` selects the IteratorReturnResult arm, whose
2327
- // value type accepts undefined.
2328
- return { value: undefined, done: true };
2329
- }
2330
- if (item.type === "audio") {
2403
+ if (item?.type === "audio") {
2331
2404
  return {
2332
2405
  value: { type: "audio", audio: item.audio },
2333
2406
  done: false,
2334
2407
  };
2335
2408
  }
2336
- if (item.type === "end") {
2409
+ if (item?.type === "end") {
2337
2410
  done = true;
2338
2411
  return { value: undefined, done: true };
2339
2412
  }
2340
- if (item.type === "error") {
2413
+ if (item?.type === "error") {
2341
2414
  done = true;
2342
2415
  throw item.error instanceof Error
2343
2416
  ? item.error
2344
2417
  : new Error(String(item.error));
2345
2418
  }
2419
+ if (done) {
2420
+ return { value: undefined, done: true };
2421
+ }
2422
+ await new Promise((resolve) => {
2423
+ notifyNext = resolve;
2424
+ });
2346
2425
  }
2347
- if (done) {
2348
- return { value: undefined, done: true };
2349
- }
2350
- return await new Promise((resolve) => {
2351
- resolveNext = resolve;
2352
- });
2426
+ },
2427
+ async return() {
2428
+ cancel();
2429
+ return { value: undefined, done: true };
2353
2430
  },
2354
2431
  };
2355
2432
  },
2356
2433
  };
2434
+ attachStreamCancel(asyncIterable, cancel);
2357
2435
  return {
2358
2436
  stream: asyncIterable,
2359
2437
  provider: this.providerName,
@@ -5,6 +5,7 @@
5
5
  * Best for web pages, email templates, and structured HTML content.
6
6
  */
7
7
  import { randomUUID } from "crypto";
8
+ import { decodeHtmlTextEntitiesOnce } from "../../utils/htmlEntities.js";
8
9
  /**
9
10
  * HTML-aware chunker implementation
10
11
  * Splits based on HTML structure (tags, elements)
@@ -170,7 +171,7 @@ export class HTMLChunker {
170
171
  * Extract plain text from HTML
171
172
  */
172
173
  extractText(html) {
173
- return (html
174
+ return decodeHtmlTextEntitiesOnce(html
174
175
  // Remove script and style elements
175
176
  .replace(/<script[\s\S]*?<\/script>/gi, "")
176
177
  .replace(/<style[\s\S]*?<\/style>/gi, "")
@@ -179,17 +180,9 @@ export class HTMLChunker {
179
180
  // Replace block elements with newlines
180
181
  .replace(/<\/(p|div|br|h[1-6]|li|tr)>/gi, "\n")
181
182
  // Remove remaining tags
182
- .replace(/<[^>]+>/g, "")
183
- // Decode common HTML entities
184
- .replace(/&nbsp;/gi, " ")
185
- .replace(/&amp;/gi, "&")
186
- .replace(/&lt;/gi, "<")
187
- .replace(/&gt;/gi, ">")
188
- .replace(/&quot;/gi, '"')
189
- .replace(/&#039;/gi, "'")
190
- // Normalize whitespace
183
+ .replace(/<[^>]+>/g, ""), { nbsp: " ", amp: "&", lt: "<", gt: ">", quot: '"', "#039": "'" })
191
184
  .replace(/\s+/g, " ")
192
- .trim());
185
+ .trim();
193
186
  }
194
187
  /**
195
188
  * Split content that exceeds max size
@@ -27,6 +27,7 @@ import { existsSync } from "fs";
27
27
  import { readFile } from "fs/promises";
28
28
  import { basename, extname } from "path";
29
29
  import { logger } from "../../utils/logger.js";
30
+ import { decodeHtmlTextEntitiesOnce } from "../../utils/htmlEntities.js";
30
31
  import { CSVProcessor, stripBom, stripMetadataLine, splitCsvLines, dedupeColumnNames, sanitizeColumnName, } from "../../utils/csvProcessor.js";
31
32
  import { splitCSVFields } from "../../utils/csvUtils.js";
32
33
  import { MDocument } from "./MDocument.js";
@@ -428,7 +429,7 @@ export class WebLoader {
428
429
  * Convert HTML to plain text
429
430
  */
430
431
  htmlToText(html) {
431
- return (html
432
+ return decodeHtmlTextEntitiesOnce(html
432
433
  // Remove script and style elements
433
434
  .replace(/<script[\s\S]*?<\/script>/gi, "")
434
435
  .replace(/<style[\s\S]*?<\/style>/gi, "")
@@ -438,21 +439,18 @@ export class WebLoader {
438
439
  .replace(/<\/(p|div|h[1-6]|br|li|tr|blockquote)>/gi, "\n")
439
440
  .replace(/<(br|hr)\s*\/?>/gi, "\n")
440
441
  // Remove remaining tags
441
- .replace(/<[^>]+>/g, "")
442
- // Decode common HTML entities
443
- .replace(/&nbsp;/gi, " ")
444
- .replace(/&amp;/gi, "&")
445
- .replace(/&lt;/gi, "<")
446
- .replace(/&gt;/gi, ">")
447
- .replace(/&quot;/gi, '"')
448
- .replace(/&#039;/gi, "'")
449
- .replace(/&apos;/gi, "'")
450
- // Decode numeric entities
451
- .replace(/&#(\d+);/g, (_, code) => String.fromCharCode(Number(code)))
452
- // Normalize whitespace
442
+ .replace(/<[^>]+>/g, ""), {
443
+ nbsp: " ",
444
+ amp: "&",
445
+ lt: "<",
446
+ gt: ">",
447
+ quot: '"',
448
+ "#039": "'",
449
+ apos: "'",
450
+ }, true)
453
451
  .replace(/\n\s*\n/g, "\n\n")
454
452
  .replace(/[ \t]+/g, " ")
455
- .trim());
453
+ .trim();
456
454
  }
457
455
  }
458
456
  /**
@@ -29,7 +29,6 @@ export declare class OpenAPIGenerator {
29
29
  toJSON(pretty?: boolean): string;
30
30
  /**
31
31
  * Generate OpenAPI spec as YAML string
32
- * Note: For full YAML support, use a YAML library
33
32
  */
34
33
  toYAML(): string;
35
34
  private generateInfo;
@@ -41,11 +40,6 @@ export declare class OpenAPIGenerator {
41
40
  private generateComponents;
42
41
  private convertPath;
43
42
  private extractPathParameters;
44
- /**
45
- * Simple JSON to YAML converter
46
- * For production use, consider using a proper YAML library
47
- */
48
- private jsonToYaml;
49
43
  }
50
44
  /**
51
45
  * Create an OpenAPI generator with default configuration
@@ -2,6 +2,7 @@
2
2
  * OpenAPI 3.1 Specification Generator
3
3
  * Generates OpenAPI documentation from NeuroLink server routes
4
4
  */
5
+ import { dump } from "js-yaml";
5
6
  import { OpenAPISchemas } from "./schemas.js";
6
7
  import { CommonParameters, createDeleteOperation, createGetOperation, createPostOperation, createStreamingPostOperation, DefaultServers, NeuroLinkApiInfo, StandardErrorResponses, StandardTags, BearerSecurityScheme, ApiKeySecurityScheme, } from "./templates.js";
7
8
  // ============================================
@@ -63,11 +64,9 @@ export class OpenAPIGenerator {
63
64
  }
64
65
  /**
65
66
  * Generate OpenAPI spec as YAML string
66
- * Note: For full YAML support, use a YAML library
67
67
  */
68
68
  toYAML() {
69
- const spec = this.generate();
70
- return this.jsonToYaml(spec);
69
+ return dump(this.generate(), { noRefs: true, lineWidth: -1 });
71
70
  }
72
71
  // ============================================
73
72
  // Private Methods
@@ -291,75 +290,6 @@ export class OpenAPIGenerator {
291
290
  }
292
291
  return params;
293
292
  }
294
- /**
295
- * Simple JSON to YAML converter
296
- * For production use, consider using a proper YAML library
297
- */
298
- jsonToYaml(obj, indent = 0) {
299
- const spaces = " ".repeat(indent);
300
- if (obj === null || obj === undefined) {
301
- return "null";
302
- }
303
- if (typeof obj === "boolean" || typeof obj === "number") {
304
- return String(obj);
305
- }
306
- if (typeof obj === "string") {
307
- // Check if string needs quotes
308
- if (obj.includes("\n") ||
309
- obj.includes(":") ||
310
- obj.includes("#") ||
311
- obj.includes("'") ||
312
- obj.includes('"') ||
313
- obj.startsWith(" ") ||
314
- obj.endsWith(" ")) {
315
- // Use block scalar for multiline
316
- if (obj.includes("\n")) {
317
- const lines = obj.split("\n");
318
- return `|\n${lines.map((line) => spaces + " " + line).join("\n")}`;
319
- }
320
- return `"${obj.replace(/"/g, '\\"')}"`;
321
- }
322
- return obj;
323
- }
324
- if (Array.isArray(obj)) {
325
- if (obj.length === 0) {
326
- return "[]";
327
- }
328
- return obj
329
- .map((item) => {
330
- const itemYaml = this.jsonToYaml(item, indent + 1);
331
- if (typeof item === "object" && item !== null) {
332
- return `${spaces}- ${itemYaml
333
- .trim()
334
- .replace(/^\s+/gm, (match) => spaces + " " + match.trim() + "\n")
335
- .trim()}`;
336
- }
337
- return `${spaces}- ${itemYaml}`;
338
- })
339
- .join("\n");
340
- }
341
- if (typeof obj === "object") {
342
- const entries = Object.entries(obj);
343
- if (entries.length === 0) {
344
- return "{}";
345
- }
346
- return entries
347
- .map(([key, value]) => {
348
- const valueYaml = this.jsonToYaml(value, indent + 1);
349
- if (typeof value === "object" &&
350
- value !== null &&
351
- !Array.isArray(value)) {
352
- return `${spaces}${key}:\n${valueYaml}`;
353
- }
354
- if (Array.isArray(value)) {
355
- return `${spaces}${key}:\n${valueYaml}`;
356
- }
357
- return `${spaces}${key}: ${valueYaml}`;
358
- })
359
- .join("\n");
360
- }
361
- return String(obj);
362
- }
363
293
  }
364
294
  // ============================================
365
295
  // Factory Functions
@@ -70,6 +70,8 @@ export type GenerateCommandArgs = BaseCommandArgs & CliToolRoutingFlags & CliCla
70
70
  context?: string;
71
71
  /** Disable tools */
72
72
  disableTools?: boolean;
73
+ /** Keep the request on the selected provider and model. */
74
+ disableInternalFallback?: boolean;
73
75
  /** Maximum steps for multi-turn */
74
76
  maxSteps?: number;
75
77
  /** Output file */
@@ -133,6 +135,8 @@ export type StreamCommandArgs = BaseCommandArgs & CliToolRoutingFlags & CliClass
133
135
  maxTokens?: number;
134
136
  /** Disable tools */
135
137
  disableTools?: boolean;
138
+ /** Keep the request on the selected provider and model. */
139
+ disableInternalFallback?: boolean;
136
140
  /** Enable extended thinking/reasoning */
137
141
  thinking?: boolean;
138
142
  /** Token budget for thinking */
@@ -164,6 +168,8 @@ export type BatchCommandArgs = BaseCommandArgs & CliToolRoutingFlags & CliClassi
164
168
  output?: string;
165
169
  /** Disable tools */
166
170
  disableTools?: boolean;
171
+ /** Keep the request on the selected provider and model. */
172
+ disableInternalFallback?: boolean;
167
173
  };
168
174
  /**
169
175
  * MCP command arguments - Enhanced with transport and server management
@@ -1,6 +1,7 @@
1
1
  import type Anthropic from "@anthropic-ai/sdk";
2
2
  import type { Span } from "@opentelemetry/api";
3
3
  import type { Tool } from "./tools.js";
4
+ import type { GenerateStopReason } from "./generate.js";
4
5
  import type { CollectedChunkResult, NativeFunctionCall, NativeToolDeclarationsResult } from "./providers.js";
5
6
  import type { ExecutionControlDecision, ExecutionControlStepContext } from "./stream.js";
6
7
  /**
@@ -603,6 +604,12 @@ export type AgenticLoopResult<TConversation> = {
603
604
  usage: AgenticLoopUsage;
604
605
  finishReason: string;
605
606
  rawStopReason: string | undefined;
607
+ /**
608
+ * A known engine-owned termination condition, distinct from the provider's
609
+ * raw reason. Optional for adapters/fixtures that predate this field; the
610
+ * engine reports step-cap or aborted when it has observed that condition.
611
+ */
612
+ stopReason?: GenerateStopReason;
606
613
  conversation: TConversation;
607
614
  /**
608
615
  * True when the turn ended because its abort signal fired rather than
@@ -91,6 +91,10 @@ export type CatalogQuirks = {
91
91
  * without it once tools are in play). ConfiguredOpenAICompatProvider
92
92
  * turns on the replay; every other provider leaves the field off. */
93
93
  replayReasoningContent?: boolean;
94
+ /** The endpoint accepts auto/none/named tool choices but not required.
95
+ * Reject that explicit caller intent with nonempty tools before sending;
96
+ * never silently downgrade it to auto. Omitted preserves existing behavior. */
97
+ rejectRequiredToolChoice?: boolean;
94
98
  /** Vendor's chat-completions endpoint authenticates with a vendor-specific
95
99
  * header instead of the OpenAI-standard `Authorization: Bearer <key>`.
96
100
  * "x-api-key": send `X-Api-Key: <key>` and omit Authorization entirely —
@@ -101,7 +105,9 @@ export type CatalogQuirks = {
101
105
  * inherited Bearer default. */
102
106
  authHeaderStyle?: "x-api-key";
103
107
  };
104
- export type CatalogBillingPolicy = "free-tier" | "free-with-card" | "no-free-tier";
108
+ /** Promotional allowance is conditional; setup text must describe eligibility,
109
+ * activation and expiry without implying current account entitlement. */
110
+ export type CatalogBillingPolicy = "free-tier" | "free-with-card" | "no-free-tier" | "promotional-credit";
105
111
  export type CatalogSetup = {
106
112
  url: string;
107
113
  apiKeyFormat: string | null;
@@ -122,6 +128,9 @@ export type CatalogProbeEvidence = {
122
128
  method?: string;
123
129
  };
124
130
  export type CatalogEvidence = {
131
+ /** Historical dated evidence. Its presence does not reverify an account
132
+ * roster or live execution; the authoring admission command reports those
133
+ * states separately without changing existing catalog records. */
125
134
  rosterVerified: CatalogProbeEvidence;
126
135
  authProbe?: CatalogProbeEvidence;
127
136
  billingProbe?: CatalogProbeEvidence;
@@ -129,6 +138,8 @@ export type CatalogEvidence = {
129
138
  date: string;
130
139
  result: string;
131
140
  } | null;
141
+ /** Introduction PR identity, or PENDING_PR during local source authoring.
142
+ * A string/URL alone is not proof of PR association, merge or release. */
132
143
  addedInPR: string;
133
144
  };
134
145
  export type CatalogCapabilities = {
@@ -198,7 +209,10 @@ export type ProviderCatalogJson = {
198
209
  * registryDefaultModel is MISTRAL_LARGE_LATEST while its defaultModel
199
210
  * is not). Must be a models.catalog key (validated). */
200
211
  registryDefaultModel?: string;
212
+ /** Source-supported positive safe integer model ceiling; never a zero
213
+ * placeholder. This does not change caller maxTokens: 0 semantics. */
201
214
  defaultContextWindow: number;
215
+ /** Source-supported positive safe integer output ceiling. */
202
216
  defaultMaxOutputTokens: number;
203
217
  catalog: Record<string, CatalogModelSpec>;
204
218
  /** Ordered curated subset of `catalog` keys for wizard/choice surfaces
@@ -1,2 +1,2 @@
1
- export type CatalogProviderName = "a2agent" | "above-dev" | "ai21" | "aiand" | "aionlabs" | "ambient" | "api-route" | "arcee" | "atlas-cloud" | "avian-io" | "baidu-qianfan" | "baseten" | "bee-heossi" | "byteplus-modelark" | "bytez" | "cerebras" | "charm-hyper" | "chutes" | "cloudflare" | "crusoe" | "dashscope" | "deepinfra" | "deepseek" | "empiriolabs" | "featherless-ai" | "fireworks" | "friendli" | "gmicloud" | "gradientai" | "groq" | "hetzner-inference" | "huggingface" | "inception-labs" | "inceptron" | "inco" | "inference-net" | "io-intelligence" | "koscompute" | "lemonfox-ai" | "lilac" | "llmtech" | "mancer" | "meta-model-api" | "minimax" | "mistral" | "moark" | "modelscope" | "moonshot-ai" | "morph" | "nebius" | "neuralwatt" | "novita" | "ovhcloud" | "parasail" | "pareto-inference" | "perplexity" | "poolside" | "prime-intellect" | "reka" | "sakana-ai" | "sambanova" | "sarvam" | "scaleway" | "siliconflow" | "stackit" | "stepfun" | "subconscious" | "synthetic" | "telnyx" | "thinking-machines" | "tinfoil" | "together-ai" | "umans-ai" | "upstage" | "venice-ai" | "vispark" | "vultr-inference" | "wafer" | "wandb-inference" | "xai" | "z-ai";
2
- export type CatalogCredentialKey = "a2agent" | "aboveDev" | "ai21" | "aiand" | "aionlabs" | "ambient" | "apiRoute" | "arcee" | "atlasCloud" | "avianIo" | "baiduQianfan" | "baseten" | "beeHeossi" | "byteplusModelark" | "bytez" | "cerebras" | "charmHyper" | "chutes" | "cloudflare" | "crusoe" | "dashscope" | "deepinfra" | "deepseek" | "empiriolabs" | "featherlessAi" | "fireworks" | "friendli" | "gmicloud" | "gradientai" | "groq" | "hetznerInference" | "huggingFace" | "inceptionLabs" | "inceptron" | "inco" | "inferenceNet" | "ioIntelligence" | "koscompute" | "lemonfoxAi" | "lilac" | "llmtech" | "mancer" | "metaModelApi" | "minimax" | "mistral" | "moark" | "modelscope" | "moonshotAi" | "morph" | "nebius" | "neuralwatt" | "novita" | "ovhcloud" | "parasail" | "paretoInference" | "perplexity" | "poolside" | "primeIntellect" | "reka" | "sakanaAi" | "sambanova" | "sarvam" | "scaleway" | "siliconflow" | "stackit" | "stepfun" | "subconscious" | "synthetic" | "telnyx" | "thinkingMachines" | "tinfoil" | "together" | "umansAi" | "upstage" | "veniceAi" | "vispark" | "vultrInference" | "wafer" | "wandbInference" | "xai" | "zAi";
1
+ export type CatalogProviderName = "a2agent" | "abliteration" | "above-dev" | "ai21" | "aiand" | "aihubmix" | "aionlabs" | "ambient" | "api-route" | "arcee" | "atlas-cloud" | "avian-io" | "baidu-qianfan" | "baseten" | "bee-heossi" | "byteplus-modelark" | "bytez" | "cerebras" | "charm-hyper" | "chutes" | "cloudflare" | "crusoe" | "dashscope" | "deepinfra" | "deepseek" | "empiriolabs" | "featherless-ai" | "fireworks" | "friendli" | "gmicloud" | "gradientai" | "groq" | "hetzner-inference" | "huggingface" | "inception-labs" | "inceptron" | "inco" | "inference-net" | "io-intelligence" | "koscompute" | "lemonfox-ai" | "lilac" | "llmtech" | "mancer" | "meta-model-api" | "minimax" | "mistral" | "moark" | "modelscope" | "moonshot-ai" | "morph" | "naver-clova-studio" | "nebius" | "neuralwatt" | "novita" | "ovhcloud" | "parasail" | "pareto-inference" | "perplexity" | "poolside" | "prime-intellect" | "reka" | "sakana-ai" | "sambanova" | "sarvam" | "scaleway" | "siliconflow" | "stackit" | "stepfun" | "subconscious" | "synthetic" | "telnyx" | "thinking-machines" | "tinfoil" | "together-ai" | "umans-ai" | "upstage" | "venice-ai" | "vispark" | "vultr-inference" | "wafer" | "wandb-inference" | "xai" | "z-ai";
2
+ export type CatalogCredentialKey = "a2agent" | "abliteration" | "aboveDev" | "ai21" | "aiand" | "aihubmix" | "aionlabs" | "ambient" | "apiRoute" | "arcee" | "atlasCloud" | "avianIo" | "baiduQianfan" | "baseten" | "beeHeossi" | "byteplusModelark" | "bytez" | "cerebras" | "charmHyper" | "chutes" | "cloudflare" | "crusoe" | "dashscope" | "deepinfra" | "deepseek" | "empiriolabs" | "featherlessAi" | "fireworks" | "friendli" | "gmicloud" | "gradientai" | "groq" | "hetznerInference" | "huggingFace" | "inceptionLabs" | "inceptron" | "inco" | "inferenceNet" | "ioIntelligence" | "koscompute" | "lemonfoxAi" | "lilac" | "llmtech" | "mancer" | "metaModelApi" | "minimax" | "mistral" | "moark" | "modelscope" | "moonshotAi" | "morph" | "naverClovaStudio" | "nebius" | "neuralwatt" | "novita" | "ovhcloud" | "parasail" | "paretoInference" | "perplexity" | "poolside" | "primeIntellect" | "reka" | "sakanaAi" | "sambanova" | "sarvam" | "scaleway" | "siliconflow" | "stackit" | "stepfun" | "subconscious" | "synthetic" | "telnyx" | "thinkingMachines" | "tinfoil" | "together" | "umansAi" | "upstage" | "veniceAi" | "vispark" | "vultrInference" | "wafer" | "wandbInference" | "xai" | "zAi";
@@ -196,6 +196,10 @@ export type NeurolinkCredentials = {
196
196
  apiKey?: string;
197
197
  baseURL?: string;
198
198
  };
199
+ abliteration?: {
200
+ apiKey?: string;
201
+ baseURL?: string;
202
+ };
199
203
  aboveDev?: {
200
204
  apiKey?: string;
201
205
  baseURL?: string;
@@ -208,6 +212,10 @@ export type NeurolinkCredentials = {
208
212
  apiKey?: string;
209
213
  baseURL?: string;
210
214
  };
215
+ aihubmix?: {
216
+ apiKey?: string;
217
+ baseURL?: string;
218
+ };
211
219
  aionlabs?: {
212
220
  apiKey?: string;
213
221
  baseURL?: string;
@@ -389,6 +397,10 @@ export type NeurolinkCredentials = {
389
397
  apiKey?: string;
390
398
  baseURL?: string;
391
399
  };
400
+ naverClovaStudio?: {
401
+ apiKey?: string;
402
+ baseURL?: string;
403
+ };
392
404
  nebius?: {
393
405
  apiKey?: string;
394
406
  baseURL?: string;
@@ -1126,6 +1138,9 @@ export type OpenAICompatCatalogEntry = {
1126
1138
  /** See CatalogQuirks.replayReasoningContent — a vendor that wants each
1127
1139
  * assistant turn's `reasoning_content` sent back on later requests. */
1128
1140
  replayReasoningContent?: boolean;
1141
+ /** See CatalogQuirks.rejectRequiredToolChoice. Applied only to a nonempty
1142
+ * tools request that explicitly requires a tool; no silent auto downgrade. */
1143
+ rejectRequiredToolChoice?: boolean;
1129
1144
  /** See CatalogQuirks.authHeaderStyle — a vendor whose chat-completions
1130
1145
  * endpoint authenticates via `X-Api-Key` instead of the inherited
1131
1146
  * `Authorization: Bearer` default. */
@@ -19,6 +19,6 @@ export type SafeDownloadOptions = {
19
19
  label: string;
20
20
  /** Optional abort signal for caller-driven cancellation. */
21
21
  signal?: AbortSignal;
22
- /** Optional per-call request timeout (ms). Default: 60_000. */
22
+ /** Optional HTTP transfer timeout (ms), covering headers and body after URL validation. Default: 60_000. */
23
23
  timeoutMs?: number;
24
24
  };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Decode a caller's supported HTML text references exactly once.
3
+ *
4
+ * Replacement scans the original string: a decoded ampersand is never
5
+ * reinterpreted as the beginning of another reference. Unknown references
6
+ * stay literal. This is text conversion, not HTML output sanitization.
7
+ */
8
+ export declare function decodeHtmlTextEntitiesOnce(value: string, entities: Readonly<Record<string, string>>, decodeDecimalReferences?: boolean): string;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Decode a caller's supported HTML text references exactly once.
3
+ *
4
+ * Replacement scans the original string: a decoded ampersand is never
5
+ * reinterpreted as the beginning of another reference. Unknown references
6
+ * stay literal. This is text conversion, not HTML output sanitization.
7
+ */
8
+ export function decodeHtmlTextEntitiesOnce(value, entities, decodeDecimalReferences = false) {
9
+ return value.replace(/&([a-z]+|#\d+);/gi, (match, body) => {
10
+ const reference = body.toLowerCase();
11
+ const decoded = Object.hasOwn(entities, reference)
12
+ ? entities[reference]
13
+ : undefined;
14
+ if (typeof decoded === "string") {
15
+ return decoded;
16
+ }
17
+ if (!decodeDecimalReferences || !reference.startsWith("#")) {
18
+ return match;
19
+ }
20
+ const codePoint = Number(reference.slice(1));
21
+ if (!Number.isInteger(codePoint) ||
22
+ codePoint <= 0 ||
23
+ codePoint > 0x10ffff ||
24
+ (codePoint >= 0xd800 && codePoint <= 0xdfff)) {
25
+ return match;
26
+ }
27
+ return String.fromCodePoint(codePoint);
28
+ });
29
+ }
@@ -140,14 +140,23 @@ export async function safeDownload(url, options) {
140
140
  const composedSignal = options.signal
141
141
  ? AbortSignal.any([options.signal, timeoutCtrl.signal])
142
142
  : timeoutCtrl.signal;
143
- let response;
144
143
  try {
145
- response = await undiciFetch(validatedUrl, {
144
+ const response = await undiciFetch(validatedUrl, {
146
145
  method: "GET",
147
146
  signal: composedSignal,
148
147
  redirect: "manual", // a 3xx → private-IP redirect would bypass the guard
149
148
  dispatcher,
150
149
  });
150
+ // fetch resolves at the headers. Keep the same deadline and abort signal
151
+ // active while the bounded reader drains the response body as well.
152
+ return await readDownloadResponse(response, url, options);
153
+ }
154
+ catch (error) {
155
+ // A status/size rejection may happen before the body is consumed. Stop
156
+ // that request so a pinned agent can close and a shared proxy agent does
157
+ // not retain an unused response stream.
158
+ timeoutCtrl.abort();
159
+ throw error;
151
160
  }
152
161
  finally {
153
162
  clearTimeout(timeoutId);
@@ -157,5 +166,4 @@ export async function safeDownload(url, options) {
157
166
  dispatcher.close().catch(() => undefined);
158
167
  }
159
168
  }
160
- return readDownloadResponse(response, url, options);
161
169
  }