@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
@@ -0,0 +1,77 @@
1
+ {
2
+ "$schema": "./provider-catalog.schema.json",
3
+ "id": "aihubmix",
4
+ "displayName": "AIHubMix",
5
+ "enumTypeName": "AIHubMixModels",
6
+ "aliases": [],
7
+ "tier": 2,
8
+ "wire": {
9
+ "baseURL": "https://aihubmix.com/v1"
10
+ },
11
+ "models": {
12
+ "default": "gpt-4o-mini",
13
+ "fallbacks": ["gpt-4o-mini"],
14
+ "defaultContextWindow": 128000,
15
+ "defaultMaxOutputTokens": 16384,
16
+ "catalog": {
17
+ "gpt-4o-mini": {
18
+ "contextWindow": 128000,
19
+ "maxOutputTokens": 16384,
20
+ "vision": false,
21
+ "status": "production",
22
+ "description": "AIHubMix's paid GPT 4o Mini. Exact context/output limits and active lifecycle are published in its own model-page record on 2026-10-09. This pilot declares text generate/stream; tools, vision, strict schema and effort controls await separate evidence and acceptance. Account/model access is unverified."
23
+ }
24
+ }
25
+ },
26
+ "capabilities": {
27
+ "text": true,
28
+ "streaming": true,
29
+ "tools": false,
30
+ "toolsWithStreaming": false,
31
+ "structuredOutput": false,
32
+ "structuredOutputWithTools": false,
33
+ "embeddings": false,
34
+ "thinking": false
35
+ },
36
+ "errorRules": [
37
+ {
38
+ "status": 401,
39
+ "class": "authentication",
40
+ "message": "Invalid or expired AIHubMix API key. Check {apiKeyEnvVar} and your key permissions."
41
+ },
42
+ {
43
+ "pattern": "insufficient_user_quota|insufficient.*quota",
44
+ "patternStatuses": [403],
45
+ "class": "provider",
46
+ "message": "AIHubMix quota or paid-model access is insufficient. The gpt-4o-mini default is usage-priced; verify account funding and model eligibility."
47
+ },
48
+ {
49
+ "pattern": "model_retired|model.*retired",
50
+ "patternStatuses": [404],
51
+ "class": "invalid-model",
52
+ "message": "The requested AIHubMix model '{model}' is retired. Select a current documented model; gpt-4o-free and gpt-5.5-free are excluded from this pilot."
53
+ }
54
+ ],
55
+ "setup": {
56
+ "url": "https://console.aihubmix.com",
57
+ "apiKeyFormat": null,
58
+ "billingPolicy": "free-tier",
59
+ "instructions": [
60
+ "1. Sign in to the AIHubMix console and create an API key.",
61
+ "2. The selected gpt-4o-mini default is paid and usage-priced. Verify your own account funding, model permissions and current rates before using it.",
62
+ "3. The vendor-level free-tier label refers to AIHubMix's separately documented, model-limited free programme. It does not grant free usage of gpt-4o-mini; current model eligibility and allowances are unverified.",
63
+ "4. Current model-specific guides mark gpt-4o-free and gpt-5.5-free retired. Do not use either as a default or fallback based on older free-model marketing.",
64
+ "5. Set {apiKeyEnvVar} for this provider. The documented base URL is https://aihubmix.com/v1."
65
+ ],
66
+ "description": "AIHubMix gateway text/stream pilot with a paid gpt-4o-mini default. A separate model-limited free programme is documented; no account entitlement, roster, live inference or delivery is verified."
67
+ },
68
+ "evidence": {
69
+ "rosterVerified": {
70
+ "date": "2026-10-09",
71
+ "code": "PUBLIC_DOCS_ONLY_NOT_ROSTER_VERIFIED",
72
+ "method": "Public documentation GETs of https://aihubmix.com/model/gpt-4o-mini and its /llms.txt guide. Exact own model-page record binds context_window=128000 and max_output=16384. No vendor roster, auth, billing or inference probe ran; this legacy field does not assert roster verification."
73
+ },
74
+ "liveMatrix": null,
75
+ "addedInPR": "https://github.com/juspay/neurolink/pull/1966"
76
+ }
77
+ }
@@ -125,6 +125,6 @@
125
125
  "date": "2026-09-17",
126
126
  "result": "Verified live against https://global.api-route.com/v1: GET /v1/models returned 70 models. Chat completions, streaming, function calling, streamed function calling, and structured JSON output (json_object and json_schema) verified. Multimodal vision verified on claude-sonnet-4-6, claude-haiku-4-5, and gemini-3.8-flash. Invalid API key returns 401 (无效的令牌) and invalid model returns 503 (model_not_found). Tools and response_format were only probed SEPARATELY, never together in one request, so structuredOutputWithTools is corrected to false here pending a dedicated tools_plus_schema probe — the individual passes do not demonstrate the combined capability."
127
127
  },
128
- "addedInPR": "pending"
128
+ "addedInPR": "https://github.com/juspay/neurolink/pull/1725"
129
129
  }
130
130
  }
@@ -258,6 +258,6 @@
258
258
  "status": 403
259
259
  },
260
260
  "liveMatrix": null,
261
- "addedInPR": "pending"
261
+ "addedInPR": "https://github.com/juspay/neurolink/pull/1625"
262
262
  }
263
263
  }
@@ -94,6 +94,6 @@
94
94
  "method": "Live against api.deepseek.com on 2026-09-24. GET /models lists only deepseek-flash and deepseek-v4-pro, with context_window 1048576 and max_output_tokens 393216 (the pricing page's 1M / 384K). deepseek-chat and deepseek-reasoner still answer 200 but report model deepseek-flash — deepseek-chat without reasoning_content, deepseek-reasoner with it — so both stay production as aliases rather than being marked retired. Vision was probed with a solid-blue PNG: deepseek-flash, deepseek-chat and deepseek-reasoner all named the colour; deepseek-v4-pro answered 200 but described a scene that was not in the image, so it keeps vision false, matching the pricing page. An unknown model id returns 400 'The supported API model names are …' with code invalid_request_error — never 'model_not_found' — so the invalid-model rule now matches that text. tools plus response_format json_object in one request returned 200 with tool_calls, confirming structuredOutputWithTools. billingPolicy is not re-verified."
95
95
  },
96
96
  "liveMatrix": null,
97
- "addedInPR": "TBD — integration-tested in a worktree before a PR was opened"
97
+ "addedInPR": "https://github.com/juspay/neurolink/pull/1781"
98
98
  }
99
99
  }
@@ -59,6 +59,6 @@
59
59
  "date": "2026-09-03",
60
60
  "result": "MiniMax-M3 accepted max_completion_tokens=524288 and rejected 1048576 with an explicit 524288 limit. Structured output: the endpoint ignores response_format (json_schema and json_object both return prose with no prompt hint; raw fetch, 2026-09-03), so the SDK's structured-output fallback re-asks with the schema spelled out in the system prompt and coerces the reply — generate({ schema }) returned a schema-valid structuredData 3/3 in the prompt-fallback experiment and again in the final live proof through dist/index.js. structuredOutput and structuredOutputWithTools are declared true on that basis."
61
61
  },
62
- "addedInPR": "pending"
62
+ "addedInPR": "https://github.com/juspay/neurolink/pull/1625"
63
63
  }
64
64
  }
@@ -401,6 +401,6 @@
401
401
  "method": "Live against router.huggingface.co on 2026-09-24. GET /v1/models returned 138 models; each of the 53 catalog ids was also sent a real chat call. 39 ids are absent from the roster AND rejected with 400 ('does not exist' / 'not supported by any provider'), so they are marked retired and keep their enumMember; the other 14 are on the roster and answered 402 (the test account's monthly credits are spent), which is a billing block, not a rejection, so they stay production. No id had the two signals disagree. topModels swaps the retired Mistral-Large-3 for zai-org/GLM-5, which is on the roster. billingPolicy is free-tier per https://huggingface.co/docs/inference-providers/pricing (free users get monthly credits, no card). apiKeyFormat accepts the hf_ + 34 characters a real token has; the previous hf_ + 37 pattern rejected it. structuredOutput, toolsWithStreaming and per-model vision are left unchanged: /v1/models reports them per serving provider, and the 402 prevented a live round trip that would prove them."
402
402
  },
403
403
  "liveMatrix": null,
404
- "addedInPR": "TBD — integration-tested in a worktree before a PR was opened"
404
+ "addedInPR": "https://github.com/juspay/neurolink/pull/1781"
405
405
  }
406
406
  }
@@ -70,6 +70,6 @@
70
70
  "date": "2026-09-03",
71
71
  "result": "Full capability sweep on mercury-2: chat, stream, system_role, content_text_parts, sampling_params, tools, tools_stream, tools_roundtrip_null, tool_choice_without_tools, json_object, json_schema and tools_plus_schema all 200. The smoke-test json_schema probe (max_tokens:60) came back truncated (finish_reason=length, null content) because this diffusion model spends part of the budget on reasoning tokens before content; re-probed live with max_tokens:300 and got a clean schema-conformant reply ({\"name\":\"Lila Whitaker\",\"age\":34}, finish_reason=stop), confirming structuredOutput:true is correct. vision is 400 (text-only; rejects image_url on mercury-2, matching the vendor docs' 'Supported Formats: Text'). bad_key is 401 'Incorrect API key provided'. bad_model is 400 with an array-shaped pydantic error.message; the shared OpenAI-compat wire-error builder coerces a non-string Error message via toString, so that array becomes the literal string \"[object Object]\" and no regex pattern can match the real text \u2014 errorRules is intentionally left empty rather than shipping a rule that can never fire (see notes)."
72
72
  },
73
- "addedInPR": "pending"
73
+ "addedInPR": "https://github.com/juspay/neurolink/pull/1625"
74
74
  }
75
75
  }
@@ -1,3 +1,3 @@
1
1
  import type { ProviderCatalogJson } from "../../types/index.js";
2
2
  export declare const CATALOG_JSON_ENTRIES: ProviderCatalogJson[];
3
- export declare const CATALOG_PROVIDER_IDS: readonly ["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"];
3
+ export declare const CATALOG_PROVIDER_IDS: readonly ["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"];
@@ -1,9 +1,11 @@
1
1
  // GENERATED FILE — do not edit. Regenerate with `pnpm run codegen:catalog`.
2
2
  // Source of truth: the per-provider JSON files in this directory.
3
3
  import a2agentJson from "./a2agent.json" with { type: "json" };
4
+ import abliterationJson from "./abliteration.json" with { type: "json" };
4
5
  import aboveDevJson from "./above-dev.json" with { type: "json" };
5
6
  import ai21Json from "./ai21.json" with { type: "json" };
6
7
  import aiandJson from "./aiand.json" with { type: "json" };
8
+ import aihubmixJson from "./aihubmix.json" with { type: "json" };
7
9
  import aionlabsJson from "./aionlabs.json" with { type: "json" };
8
10
  import ambientJson from "./ambient.json" with { type: "json" };
9
11
  import apiRouteJson from "./api-route.json" with { type: "json" };
@@ -49,6 +51,7 @@ import moarkJson from "./moark.json" with { type: "json" };
49
51
  import modelscopeJson from "./modelscope.json" with { type: "json" };
50
52
  import moonshotAiJson from "./moonshot-ai.json" with { type: "json" };
51
53
  import morphJson from "./morph.json" with { type: "json" };
54
+ import naverClovaStudioJson from "./naver-clova-studio.json" with { type: "json" };
52
55
  import nebiusJson from "./nebius.json" with { type: "json" };
53
56
  import neuralwattJson from "./neuralwatt.json" with { type: "json" };
54
57
  import novitaJson from "./novita.json" with { type: "json" };
@@ -83,9 +86,11 @@ import xaiJson from "./xai.json" with { type: "json" };
83
86
  import zAiJson from "./z-ai.json" with { type: "json" };
84
87
  export const CATALOG_JSON_ENTRIES = [
85
88
  a2agentJson,
89
+ abliterationJson,
86
90
  aboveDevJson,
87
91
  ai21Json,
88
92
  aiandJson,
93
+ aihubmixJson,
89
94
  aionlabsJson,
90
95
  ambientJson,
91
96
  apiRouteJson,
@@ -131,6 +136,7 @@ export const CATALOG_JSON_ENTRIES = [
131
136
  modelscopeJson,
132
137
  moonshotAiJson,
133
138
  morphJson,
139
+ naverClovaStudioJson,
134
140
  nebiusJson,
135
141
  neuralwattJson,
136
142
  novitaJson,
@@ -166,9 +172,11 @@ export const CATALOG_JSON_ENTRIES = [
166
172
  ];
167
173
  export const CATALOG_PROVIDER_IDS = [
168
174
  "a2agent",
175
+ "abliteration",
169
176
  "above-dev",
170
177
  "ai21",
171
178
  "aiand",
179
+ "aihubmix",
172
180
  "aionlabs",
173
181
  "ambient",
174
182
  "api-route",
@@ -214,6 +222,7 @@ export const CATALOG_PROVIDER_IDS = [
214
222
  "modelscope",
215
223
  "moonshot-ai",
216
224
  "morph",
225
+ "naver-clova-studio",
217
226
  "nebius",
218
227
  "neuralwatt",
219
228
  "novita",
@@ -453,7 +453,7 @@
453
453
  "date": "2026-09-03",
454
454
  "result": "SDK end-to-end via dist: generate, stream, tool call (nonce round-trip) and json_schema structured output all pass. Tools + schema: after a tool result the vendor answers finish_reason=tool_calls with no tool_calls and null content when JSON is requested (4/4 raw replays); the SDK now re-asks that step once with tool_choice none and recovers the JSON — passed 1/1 in the final live proof through dist/index.js, tool nonce present in structuredData. structuredOutputWithTools is declared true on that basis."
455
455
  },
456
- "addedInPR": "pending",
456
+ "addedInPR": "https://github.com/juspay/neurolink/pull/1625",
457
457
  "billingProbe": {
458
458
  "date": "2026-09-03",
459
459
  "status": 200,
@@ -176,6 +176,9 @@ export function buildCatalogEntries() {
176
176
  if (entry.quirks?.replayReasoningContent) {
177
177
  base.replayReasoningContent = true;
178
178
  }
179
+ if (entry.quirks?.rejectRequiredToolChoice !== undefined) {
180
+ base.rejectRequiredToolChoice = entry.quirks.rejectRequiredToolChoice;
181
+ }
179
182
  if (entry.quirks?.authHeaderStyle) {
180
183
  base.authHeaderStyle = entry.quirks.authHeaderStyle;
181
184
  }
@@ -187,6 +187,6 @@
187
187
  "date": "2026-09-03",
188
188
  "result": "18-probe harness on the free model mytholite: roster, chat, max_completion_tokens, system role, content parts, sampling params, SSE stream (usage chunk + [DONE]), json_schema (valid JSON matching schema) and json_object all 200; tools and tools+schema 400 BAD_PARAMETERS ('\"auto\" tool choice requires --enable-auto-tool-choice and --tool-call-parser') so tools are declared false; vision 200 but text-only (image ignored); bad key 401 'Bad API key'; unknown model 400 'Unknown Model'. Paid models could not be capability-probed without credits — tools may work there and can be re-declared once probed. Nightly matrix pins testModel mytholite."
189
189
  },
190
- "addedInPR": "pending"
190
+ "addedInPR": "https://github.com/juspay/neurolink/pull/1631"
191
191
  }
192
192
  }
@@ -92,6 +92,6 @@
92
92
  "date": "2026-09-05",
93
93
  "result": "Full wire probe on morph-v3-fast plus a focused tool-calling re-probe on both chat models. Non-tool basics all 200: chat, max_completion_tokens, system role, sampling params, SSE streaming (content deltas, usage chunk, [DONE]), json_schema (valid JSON matching schema) and json_object. Array-shaped message content is REJECTED with HTTP 500 'Request processing failed: text.charCodeAt is not a function' — same class of quirk as Cloudflare (PR #1587); messageContentFormat is set to 'string' and vision is false because the vision probe sends array parts and hits the identical 500. Tool calling does NOT work despite HTTP 200: morph-v3-large echoes the call as literal '<tool_call>{...}</tool_call>' text in message.content, and morph-v3-fast ignores the tools parameter and returns unrelated generated text (Python source) with finish_reason=length. tools and toolsWithStreaming are declared false on that basis; tools_plus_schema returning 200 is not evidence of structuredOutputWithTools since tools do not actually work. structuredOutput is true and strong (schema-conformant JSON, json_object accepted). The GET /v1/models roster lists 22 ids (morph-v3-fast, morph-v3-large, auto, morph-compactor, morph-warp-grep-v2.1, and passthrough ids for GLM/DeepSeek/Kimi/Qwen/MiniMax/Gemma/computer-use), but a deliberately invalid model name on /chat/completions came back 'is not served by this endpoint; accepted models: morph-v0, morph-v2, morph-v3-fast, morph-v3-large' — none of those other 18 roster ids were live-verified against chat/completions, so only the two confirmed working chat models are catalogued; context window (262144) and max output tokens (131072) for both come from Morph's public model metadata (https://www.morphllm.com/api/models/json), which also supplied pricing. Morph is a code-application (Fast Apply) specialist, not a general chat vendor: morph-v3-large is catalogued as the default because it is the general-purpose one of the pair, while morph-v3-fast is purpose-built for edit application rather than open-ended conversation."
94
94
  },
95
- "addedInPR": "pending"
95
+ "addedInPR": "https://github.com/juspay/neurolink/pull/1647"
96
96
  }
97
97
  }
@@ -0,0 +1,68 @@
1
+ {
2
+ "$schema": "./provider-catalog.schema.json",
3
+ "id": "naver-clova-studio",
4
+ "displayName": "NAVER CLOVA Studio",
5
+ "aliases": [],
6
+ "tier": 2,
7
+ "wire": {
8
+ "baseURL": "https://clovastudio.stream.ntruss.com/v1/openai"
9
+ },
10
+ "models": {
11
+ "default": "HCX-005",
12
+ "fallbacks": ["HCX-005"],
13
+ "defaultContextWindow": 128000,
14
+ "defaultMaxOutputTokens": 4096,
15
+ "catalog": {
16
+ "HCX-005": {
17
+ "contextWindow": 128000,
18
+ "maxOutputTokens": 4096,
19
+ "vision": false,
20
+ "tools": true,
21
+ "status": "production",
22
+ "description": "Own Chat Completions v3 documentation names HCX-005 and states a combined input/output limit of 128,000 tokens and a maximum of 4,096 output tokens. The OpenAI-compatible guide uses the same model ID. This initial entry declares text and tool paths, not endpoint-specific image or native strict-schema acceptance. Account availability is unverified."
23
+ }
24
+ },
25
+ "topModels": ["HCX-005"]
26
+ },
27
+ "capabilities": {
28
+ "text": true,
29
+ "streaming": true,
30
+ "tools": true,
31
+ "toolsWithStreaming": true,
32
+ "structuredOutput": false,
33
+ "structuredOutputWithTools": false,
34
+ "embeddings": false,
35
+ "thinking": false
36
+ },
37
+ "errorRules": [],
38
+ "quirks": {
39
+ "rejectRequiredToolChoice": true
40
+ },
41
+ "setup": {
42
+ "url": "https://console.ncloud.com",
43
+ "apiKeyFormat": null,
44
+ "billingPolicy": "promotional-credit",
45
+ "instructions": [
46
+ "Use an eligible NAVER Cloud account and subscribe to CLOVA Studio in the Korea region on Classic or VPC, following https://guide.ncloud-docs.com/docs/en/clovastudio-start.",
47
+ "The official new-signup program at https://www.ncloud.com/main/creditEvent, retrieved 2026-10-09, offers 100,000 KRW discount credit after payment-method registration and a credit application. Eligibility follows the policy at application time; prior recipients of other discounts cannot apply.",
48
+ "Issued credit is usable for three months against NAVER Cloud service charges. Free/trial products, Marketplace purchases and NAVER WORKS are excluded. This is a conditional promotional program, not a no-card free tier or proof of your current entitlement or balance.",
49
+ "Create a CLOVA Studio test or service API key under the CLOVA Studio API-key menu. Its OpenAI-compatible API accepts those keys with Authorization: Bearer; a general NAVER Cloud access key is not a substitute. Set {apiKeyEnvVar}.",
50
+ "The chat base is https://clovastudio.stream.ntruss.com/v1/openai and default model is HCX-005. Auto, none and named tool choices retain their documented meanings. Required tool choice is unsupported and explicitly rejected, never silently downgraded to auto."
51
+ ],
52
+ "description": "Documentation-based source pilot. CLOVA Studio is usage-priced; conditional NAVER Cloud promotional credit requires payment-method registration/application and eligibility. Actual account subscription, models, credit balance and live API execution remain unverified. JSON-schema name/strict controls are ignored by the shim, so native strict-schema and tools-plus-schema support are not declared."
53
+ },
54
+ "evidence": {
55
+ "rosterVerified": {
56
+ "date": "2026-10-09",
57
+ "code": "DOCS_ONLY_ROSTER_NOT_OBSERVED",
58
+ "method": "Public first-party HCX-005 Chat v3 limits, OpenAI compatibility and function-calling documents only. No authenticated model roster, account/key lookup or inference performed. Limits 128,000 / 4,096 are explicit per-model source constraints, not K/M expansion."
59
+ },
60
+ "billingProbe": {
61
+ "date": "2026-10-09",
62
+ "code": "PUBLIC_PROMOTIONAL_POLICY_ONLY",
63
+ "method": "Public https://guide.ncloud-docs.com/docs/en/clovastudio-spec confirms usage fees; https://www.ncloud.com/main/creditEvent publishes conditional 100,000 KRW credit, valid three months, after payment-method registration and application. No card-only requirement or current account entitlement is claimed."
64
+ },
65
+ "liveMatrix": null,
66
+ "addedInPR": "https://github.com/juspay/neurolink/pull/1966"
67
+ }
68
+ }
@@ -109,6 +109,6 @@
109
109
  "date": "2026-09-05",
110
110
  "result": "18-probe harness against zai-org/glm-5.3-flash (Novita's own served default) via https://api.novita.ai/openai/v1. Wire mechanics: chat, system role, array content parts, sampling params and max_completion_tokens all 200; SSE streaming 200 with a usage chunk and a terminal [DONE]. Reasoning is genuinely heavy on this model \u2014 several 200 responses show finish_reason=length with only 8 completion tokens, entirely spent on an unfinished reasoning_content and an empty visible content \u2014 but `default` and `testModel` are both pinned to the probed model itself (zai-org/glm-5.3-flash, Novita's own served default), which is exactly what the capability matrix below exercises — see the structured-output findings further down for why the reasoning-heavy default is still the right choice over the plain meta-llama/llama-3.3-70b-instruct fallback. Tool calling: a real tool_call (get_time, parsed tz argument) on the first turn, a null-content assistant tool_calls turn accepted on replay, and a tool_call surfacing mid-SSE-stream \u2014 tools and toolsWithStreaming true; tool_choice is tolerated even with no tools declared. response_format json_object returns 200 cleanly \u2014 structuredOutput true. response_format json_schema also returns 200, but at this token budget the content is unfinished reasoning text rather than schema-conformant JSON, same reasoning-budget effect as above \u2014 NeuroLink's coerceJsonToSchema repair layer is the safety net here, as for other reasoning-heavy vendors. Sending tools and a JSON schema together in one request succeeds (200, real tool_call returned) at the wire level. That single-shot result alone does not demonstrate the combined capability \u2014 the follow-up turn where the model must answer the original JSON-schema question from a tool result was untested at raw-probe time. It was proved separately through the SDK's public surface: `generate({ provider: 'novita', schema, ...})` with a registered getTime tool called end-to-end via dist/index.js \u2014 the model invoked getTime(tz='Asia/Tokyo') (toolsUsed=['getTime'], stepsUsed=2), then answered `{\"city\":\"Tokyo\",\"time\":\"09:00\"}`, which parsed against the request schema with no repair needed (jsonRepaired/jsonTruncated both unset) \u2014 passed 1/1 in the final live proof. structuredOutputWithTools is declared true on that basis. Vision: a 64x64 PNG sent to glm-5.3-flash (not a vision-tagged model; Novita has no live-verified vision model) returns 200 with reasoning_content correctly describing 'a solid blue rectangle' but an EMPTY final content \u2014 not a working, schema-usable vision reply, so no catalog entry is marked vision:true and models.visionModel is left unset. bad key -> 401 {\"code\":401,\"reason\":\"FAILED_TO_AUTH\",\"message\":\"failed to authenticate API key\"}; bad model -> 404 {\"code\":404,\"reason\":\"MODEL_NOT_FOUND\",\"message\":\"model not found\"}. Novita funds this account with a $1 Model API voucher, so only this one model was live-probed end-to-end; the other four catalog entries are roster-verified by id only, not capability-probed. Structured output on Novita is per-model, and the catalog schema can only express it per provider. Verified 2026-09-05 at the wire: meta-llama/llama-3.3-70b-instruct returns HTTP 400 for response_format json_schema (\"does not support 'json_schema' response format. Supported formats: json_object\") and also 400 for json_object, while zai-org/glm-5.3-flash returns 200 for json_object, json_schema and tools+json_schema together. The default is therefore zai-org/glm-5.3-flash, which is also Novita's own served default, so the provider-level structuredOutput and structuredOutputWithTools flags are true for the model a default-configuration caller actually gets. meta-llama/llama-3.3-70b-instruct remains a fallback for plain chat. Tool calling was verified separately on both models (finish_reason=tool_calls with a structured call). Note that glm-5.3-flash is a reasoning model: at very small max_tokens (observed at 40) the budget is consumed by reasoning and content comes back empty; the entry's defaultMaxOutputTokens of 4096 is well clear of that."
111
111
  },
112
- "addedInPR": "pending"
112
+ "addedInPR": "https://github.com/juspay/neurolink/pull/1647"
113
113
  }
114
114
  }
@@ -121,10 +121,16 @@
121
121
  "description": "Default: models.default."
122
122
  },
123
123
  "defaultContextWindow": {
124
- "type": "number"
124
+ "type": "integer",
125
+ "minimum": 1,
126
+ "maximum": 9007199254740991,
127
+ "description": "Source-supported positive safe integer model default ceiling; unknown values are incomplete authoring drafts."
125
128
  },
126
129
  "defaultMaxOutputTokens": {
127
- "type": "number"
130
+ "type": "integer",
131
+ "minimum": 1,
132
+ "maximum": 9007199254740991,
133
+ "description": "Source-supported positive safe integer model default ceiling; unknown values are incomplete authoring drafts."
128
134
  },
129
135
  "catalog": {
130
136
  "type": "object",
@@ -215,7 +221,14 @@
215
221
  "type": "boolean"
216
222
  },
217
223
  "tools": {
218
- "anyOf": [{ "type": "boolean" }, { "const": "model-dependent" }],
224
+ "anyOf": [
225
+ {
226
+ "type": "boolean"
227
+ },
228
+ {
229
+ "const": "model-dependent"
230
+ }
231
+ ],
219
232
  "description": "\"model-dependent\" when tool support varies per served model and the vendor doesn't reject tools for unsupported ones (e.g. HuggingFace's router)."
220
233
  },
221
234
  "toolsWithStreaming": {
@@ -288,6 +301,10 @@
288
301
  "type": "boolean",
289
302
  "description": "Vendor wants each assistant turn's reasoning_content sent back on every later request of the conversation (DeepSeek documents a 400 without it once tools are in play). Off for every other provider, since strict OpenAI-compatible backends reject the unknown field."
290
303
  },
304
+ "rejectRequiredToolChoice": {
305
+ "type": "boolean",
306
+ "description": "Reject tool_choice=required with nonempty tools before sending. Preserve auto/none/named choices and do not silently downgrade caller intent. Omitted leaves other providers unchanged."
307
+ },
291
308
  "authHeaderStyle": {
292
309
  "type": "string",
293
310
  "enum": ["x-api-key"],
@@ -325,7 +342,13 @@
325
342
  },
326
343
  "billingPolicy": {
327
344
  "type": "string",
328
- "enum": ["free-tier", "free-with-card", "no-free-tier"]
345
+ "enum": [
346
+ "free-tier",
347
+ "free-with-card",
348
+ "no-free-tier",
349
+ "promotional-credit"
350
+ ],
351
+ "description": "Promotional credit is conditional; setup instructions must state activation, eligibility and expiry without asserting current account entitlement."
329
352
  },
330
353
  "instructions": {
331
354
  "type": "array",
@@ -77,8 +77,8 @@ export declare const providerCatalogJsonSchema: z.ZodObject<{
77
77
  network: "network";
78
78
  provider: "provider";
79
79
  authentication: "authentication";
80
- "invalid-model": "invalid-model";
81
80
  "rate-limit": "rate-limit";
81
+ "invalid-model": "invalid-model";
82
82
  }>;
83
83
  message: z.ZodString;
84
84
  }, z.core.$strict>>;
@@ -88,6 +88,7 @@ export declare const providerCatalogJsonSchema: z.ZodObject<{
88
88
  registryDefaultIgnoresModelEnvVar: z.ZodOptional<z.ZodBoolean>;
89
89
  responseFormatDowngrade: z.ZodOptional<z.ZodLiteral<"json-schema-to-json-object">>;
90
90
  replayReasoningContent: z.ZodOptional<z.ZodBoolean>;
91
+ rejectRequiredToolChoice: z.ZodOptional<z.ZodBoolean>;
91
92
  authHeaderStyle: z.ZodOptional<z.ZodLiteral<"x-api-key">>;
92
93
  }, z.core.$strict>>;
93
94
  timeouts: z.ZodOptional<z.ZodObject<{
@@ -102,6 +103,7 @@ export declare const providerCatalogJsonSchema: z.ZodObject<{
102
103
  "no-free-tier": "no-free-tier";
103
104
  "free-tier": "free-tier";
104
105
  "free-with-card": "free-with-card";
106
+ "promotional-credit": "promotional-credit";
105
107
  }>;
106
108
  instructions: z.ZodArray<z.ZodString>;
107
109
  description: z.ZodOptional<z.ZodString>;
@@ -150,6 +150,7 @@ const catalogQuirksSchema = z.strictObject({
150
150
  registryDefaultIgnoresModelEnvVar: z.boolean().optional(),
151
151
  responseFormatDowngrade: z.literal("json-schema-to-json-object").optional(),
152
152
  replayReasoningContent: z.boolean().optional(),
153
+ rejectRequiredToolChoice: z.boolean().optional(),
153
154
  authHeaderStyle: z.literal("x-api-key").optional(),
154
155
  });
155
156
  const catalogTimeoutsSchema = z.strictObject({
@@ -160,6 +161,7 @@ const catalogBillingPolicySchema = z.enum([
160
161
  "free-tier",
161
162
  "free-with-card",
162
163
  "no-free-tier",
164
+ "promotional-credit",
163
165
  ]);
164
166
  const catalogSetupSchema = z.strictObject({
165
167
  url: z.string(),
@@ -202,8 +204,19 @@ const catalogModelsSchema = z
202
204
  fallbacks: z.array(z.string()),
203
205
  fallbackModelName: z.string().optional(),
204
206
  registryDefaultModel: z.string().optional(),
205
- defaultContextWindow: z.number(),
206
- defaultMaxOutputTokens: z.number(),
207
+ // Catalog defaults are real model ceilings, never an unknown/zero
208
+ // placeholder. Caller maxTokens and optional model overrides are separate
209
+ // contracts and deliberately keep their existing validation semantics.
210
+ defaultContextWindow: z
211
+ .number()
212
+ .int()
213
+ .positive()
214
+ .max(Number.MAX_SAFE_INTEGER),
215
+ defaultMaxOutputTokens: z
216
+ .number()
217
+ .int()
218
+ .positive()
219
+ .max(Number.MAX_SAFE_INTEGER),
207
220
  catalog: z.record(z.string(), catalogModelSpecSchema),
208
221
  topModels: z.array(z.string()).min(1).optional(),
209
222
  visionModel: z.string().optional(),
@@ -160,6 +160,6 @@
160
160
  "code": "invalid_api_key"
161
161
  },
162
162
  "liveMatrix": null,
163
- "addedInPR": "pending"
163
+ "addedInPR": "https://github.com/juspay/neurolink/pull/1625"
164
164
  }
165
165
  }
@@ -8,6 +8,7 @@ import { createCohereConfig, getProviderModel, validateApiKey, } from "../utils/
8
8
  import { createTimeoutController } from "../utils/timeout.js";
9
9
  import { stripTrailingSlash } from "./openaiChatCompletionsClient.js";
10
10
  import { OpenAIChatCompletionsProvider } from "./openaiChatCompletionsBase.js";
11
+ import { isFiniteEmbeddingVector } from "./embeddingResponseParsing.js";
11
12
  const COHERE_DEFAULT_BASE_URL = "https://api.cohere.com/compatibility/v1";
12
13
  const getCohereApiKey = () => validateApiKey(createCohereConfig());
13
14
  const getDefaultCohereModel = () => getProviderModel("COHERE_MODEL", CohereModels.COMMAND_R_PLUS);
@@ -172,8 +173,12 @@ export class CohereProvider extends OpenAIChatCompletionsProvider {
172
173
  const json = (await response.json());
173
174
  const floatVecs = json.embeddings?.float ??
174
175
  (Array.isArray(json.embeddings) ? json.embeddings : undefined);
175
- if (!floatVecs || floatVecs.length !== batch.length) {
176
- throw new ProviderError(`Cohere /v2/embed returned ${floatVecs?.length ?? 0} embeddings for ${batch.length} inputs.`, "cohere");
176
+ if (!Array.isArray(floatVecs) || floatVecs.length !== batch.length) {
177
+ throw new ProviderError(`Cohere /v2/embed returned ${Array.isArray(floatVecs) ? floatVecs.length : 0} embeddings for ${batch.length} inputs.`, "cohere");
178
+ }
179
+ if (!floatVecs.every(isFiniteEmbeddingVector)) {
180
+ const index = floatVecs.findIndex((v) => !isFiniteEmbeddingVector(v));
181
+ throw new ProviderError(`Cohere /v2/embed returned an invalid embedding at index ${index}: expected a nonempty array of finite numbers.`, "cohere");
177
182
  }
178
183
  results.push(...floatVecs);
179
184
  }
@@ -1,3 +1,4 @@
1
+ import { ProviderError } from "../types/index.js";
1
2
  import { logger } from "../utils/logger.js";
2
3
  import { redactUrlCredentials } from "../utils/logSanitize.js";
3
4
  import { getProviderModel, resolveOpenAICompatConfig, } from "../utils/providerConfig.js";
@@ -165,6 +166,12 @@ export class ConfiguredOpenAICompatProvider extends OpenAIChatCompletionsProvide
165
166
  }
166
167
  adjustRequestBody(body, modelId) {
167
168
  const adjusted = super.adjustRequestBody(body, modelId);
169
+ if (this.entry.rejectRequiredToolChoice === true &&
170
+ adjusted.tool_choice === "required" &&
171
+ Array.isArray(adjusted.tools) &&
172
+ adjusted.tools.length > 0) {
173
+ throw new ProviderError("tool_choice='required' is not supported by this endpoint; use auto, none or a named tool choice", this.providerName);
174
+ }
168
175
  if (this.entry.messageContentFormat !== "string") {
169
176
  return adjusted;
170
177
  }
@@ -1 +1,16 @@
1
+ /**
2
+ * Shared embeddings-response validation for Voyage and Jina — both call
3
+ * POST /embeddings with an identical `{input, model}` body and get back an
4
+ * identical `{data: [{embedding, index}]}` shape, and both had byte-identical
5
+ * validation logic (same three message templates, same sort-by-index +
6
+ * 0..n-1 coverage check) before this was factored out.
7
+ *
8
+ * A plain function, not a shared base class: Voyage throws typed
9
+ * `ProviderError` here, Jina throws plain `Error` (a real, pre-existing
10
+ * inconsistency between the two — not something this refactor silently
11
+ * fixes). `makeError` preserves each provider's exact existing behavior;
12
+ * `providerLabel` preserves each provider's exact existing message text
13
+ * ("Voyage ..." / "Jina ...").
14
+ */
15
+ export declare const isFiniteEmbeddingVector: (value: unknown) => value is number[];
1
16
  export declare function parseIndexedEmbeddingsResponse(data: unknown, inputCount: number, providerLabel: string, makeError: (message: string) => Error): number[][];
@@ -12,14 +12,15 @@
12
12
  * `providerLabel` preserves each provider's exact existing message text
13
13
  * ("Voyage ..." / "Jina ...").
14
14
  */
15
+ export const isFiniteEmbeddingVector = (value) => Array.isArray(value) &&
16
+ value.length > 0 &&
17
+ value.every((n) => typeof n === "number" && Number.isFinite(n));
15
18
  const isIndexedEmbedding = (value) => {
16
19
  if (typeof value !== "object" || value === null) {
17
20
  return false;
18
21
  }
19
22
  const { embedding, index } = value;
20
- return (Number.isInteger(index) &&
21
- Array.isArray(embedding) &&
22
- embedding.every((n) => typeof n === "number" && Number.isFinite(n)));
23
+ return Number.isInteger(index) && isFiniteEmbeddingVector(embedding);
23
24
  };
24
25
  export function parseIndexedEmbeddingsResponse(data, inputCount, providerLabel, makeError) {
25
26
  const entries = typeof data === "object" && data !== null