@juspay/neurolink 12.46.1 → 12.47.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -2
- package/README.md +75 -50
- package/dist/adapters/video/ffmpegAdapter.d.ts +3 -1
- package/dist/adapters/video/ffmpegAdapter.js +13 -2
- package/dist/browser/neurolink.min.js +421 -421
- package/dist/cli/commands/decide.js +2 -2
- package/dist/cli/commands/setup.js +8 -6
- package/dist/cli/factories/commandFactory.js +1 -1
- package/dist/cli/proxy-clients/copilot.js +55 -1
- package/dist/constants/contextWindows.js +12 -0
- package/dist/constants/enums.d.ts +16 -0
- package/dist/constants/enums.js +17 -0
- package/dist/core/toolExecutionGuards.d.ts +1 -9
- package/dist/core/toolExecutionGuards.js +1 -9
- package/dist/factories/providerDescriptors.js +84 -5
- package/dist/factories/providerFactory.js +16 -1
- package/dist/factories/providerRegistry.js +10 -1
- package/dist/files/fileReferenceRegistry.js +3 -3
- package/dist/models/manifestRegistry.js +2 -0
- package/dist/models/manifests/cloudflareClef.d.ts +16 -0
- package/dist/models/manifests/cloudflareClef.js +42 -0
- package/dist/neurolink.js +13 -2
- package/dist/processors/media/VideoProcessor.js +16 -4
- package/dist/providers/cloudflareClef.d.ts +52 -0
- package/dist/providers/cloudflareClef.js +331 -0
- package/dist/providers/googleNativeGemini3/utils.d.ts +9 -0
- package/dist/providers/googleNativeGemini3/utils.js +9 -0
- package/dist/providers/systemOneDecision.d.ts +12 -1
- package/dist/providers/systemOneDecision.js +70 -18
- package/dist/types/decision.d.ts +22 -0
- package/dist/types/providers.d.ts +15 -0
- package/dist/utils/modelChoices.js +13 -1
- package/dist/utils/pricing.js +12 -0
- package/dist/utils/providerConfig.d.ts +7 -0
- package/dist/utils/providerConfig.js +19 -0
- package/docs-site/static/search-index.json +78 -56
- package/package.json +2 -1
|
@@ -191,7 +191,7 @@ export const decideCommand = {
|
|
|
191
191
|
type: "string",
|
|
192
192
|
array: true,
|
|
193
193
|
nargs: 1,
|
|
194
|
-
describe: "Image for the model to read: a file path or a data: URL. Repeat for several (XOR and Perplexity take up to 8)",
|
|
194
|
+
describe: "Image for the model to read: a file path or a data: URL. Repeat for several (XOR and Perplexity take up to 8, Cloudflare Clef up to 4)",
|
|
195
195
|
})
|
|
196
196
|
.option("video", {
|
|
197
197
|
type: "string",
|
|
@@ -217,7 +217,7 @@ export const decideCommand = {
|
|
|
217
217
|
})
|
|
218
218
|
.example('$0 decide "Refund request for a damaged item" --questions \'{"urgent":{"type":"boolean","instructions":"Is this urgent?"}}\'', "Ask a single yes/no question")
|
|
219
219
|
.example("$0 decide --state-file ticket.json --questions-file questions.json --format json", "Read state and questions from files, emit raw JSON")
|
|
220
|
-
.example('$0 decide "What color is this?" --provider xor --image ./photo.png --questions \'{"color":{"type":"choice","instructions":"What color is the image?","criteria":{"red":"red","blue":"blue"}}}\'', "Ask about an image (XOR and
|
|
220
|
+
.example('$0 decide "What color is this?" --provider xor --image ./photo.png --questions \'{"color":{"type":"choice","instructions":"What color is the image?","criteria":{"red":"red","blue":"blue"}}}\'', "Ask about an image (XOR, Perplexity and Cloudflare Clef read images)"),
|
|
221
221
|
handler: async (argv) => {
|
|
222
222
|
const outputFormat = argv.format ?? "text";
|
|
223
223
|
// --- Validate before any provider work ---------------------------------
|
|
@@ -19,8 +19,9 @@ import { handleBedrockSetup } from "./setup-bedrock.js";
|
|
|
19
19
|
import { handleGCPSetup } from "./setup-gcp.js";
|
|
20
20
|
import { handleHuggingFaceSetup } from "./setup-huggingface.js";
|
|
21
21
|
import { handleMistralSetup } from "./setup-mistral.js";
|
|
22
|
+
import { OpenRouterModels, } from "../../constants/enums.js";
|
|
22
23
|
import { PROVIDER_DESCRIPTORS_BY_NAME } from "../../factories/providerDescriptors.js";
|
|
23
|
-
import { createCohereConfig, createDeepSeekConfig, createIdeogramConfig, createJinaConfig, createNvidiaNimConfig, createOpenAICompatibleConfig, createRecraftConfig, createReplicateConfig, createStabilityConfig, createVoyageConfig, createTypeSafeConfig, createLayaConfig, createXorConfig, createPerplexityDeciderConfig, satisfiesFallbacks, } from "../../utils/providerConfig.js";
|
|
24
|
+
import { createCohereConfig, createDeepSeekConfig, createIdeogramConfig, createJinaConfig, createNvidiaNimConfig, createOpenAICompatibleConfig, createRecraftConfig, createReplicateConfig, createStabilityConfig, createVoyageConfig, createTypeSafeConfig, createLayaConfig, createXorConfig, createPerplexityDeciderConfig, createCloudflareClefConfig, satisfiesFallbacks, } from "../../utils/providerConfig.js";
|
|
24
25
|
import { getCatalogJsonEntries, buildCatalogConfigOptions, } from "../../providers/catalog/loader.js";
|
|
25
26
|
// Provider information database
|
|
26
27
|
const PROVIDERS = [
|
|
@@ -172,6 +173,7 @@ export const EXTRA_PROVIDER_CONFIGS = {
|
|
|
172
173
|
laya: createLayaConfig(),
|
|
173
174
|
xor: createXorConfig(),
|
|
174
175
|
"perplexity-decider": createPerplexityDeciderConfig(),
|
|
176
|
+
"cloudflare-clef": createCloudflareClefConfig(),
|
|
175
177
|
...Object.fromEntries(getCatalogJsonEntries()
|
|
176
178
|
.filter((e) => e.id !== "mistral")
|
|
177
179
|
.map((e) => [e.id, buildCatalogConfigOptions(e)])),
|
|
@@ -570,13 +572,13 @@ async function handleOpenRouterSetup() {
|
|
|
570
572
|
logger.always(chalk.cyan(" export OPENROUTER_API_KEY=your_api_key_here"));
|
|
571
573
|
logger.always("");
|
|
572
574
|
logger.always(chalk.yellow("Step 3: Test the configuration"));
|
|
573
|
-
logger.always(chalk.cyan(
|
|
575
|
+
logger.always(chalk.cyan(` neurolink generate "Hello!" --provider openrouter --model ${OpenRouterModels.GEMINI_2_5_FLASH}`));
|
|
574
576
|
logger.always("");
|
|
575
577
|
logger.always(chalk.green("Available models include:"));
|
|
576
|
-
logger.always(
|
|
577
|
-
logger.always(
|
|
578
|
-
logger.always(
|
|
579
|
-
logger.always(
|
|
578
|
+
logger.always(` • ${OpenRouterModels.CLAUDE_SONNET_4_6} - Best for analysis`);
|
|
579
|
+
logger.always(` • ${OpenRouterModels.GPT_4O} - Industry standard`);
|
|
580
|
+
logger.always(` • ${OpenRouterModels.GEMINI_2_5_FLASH} - Fast and efficient`);
|
|
581
|
+
logger.always(` • ${OpenRouterModels.LLAMA_3_1_70B} - Open source`);
|
|
580
582
|
logger.always("");
|
|
581
583
|
logger.always(chalk.gray("See all models at: https://openrouter.ai/models"));
|
|
582
584
|
}
|
|
@@ -616,7 +616,7 @@ export class CLICommandFactory {
|
|
|
616
616
|
},
|
|
617
617
|
classifierStrategy: {
|
|
618
618
|
type: "string",
|
|
619
|
-
description: "Classifier strategy: 'auto' (default — 'jev' when a decision provider is configured, such as TYPESAFE_API_KEY, LAYA_API_KEY with LAYA_BASE_URL, XOR_API_KEY with XOR_BASE_URL, or
|
|
619
|
+
description: "Classifier strategy: 'auto' (default — 'jev' when a decision provider is configured, such as TYPESAFE_API_KEY, LAYA_API_KEY with LAYA_BASE_URL, XOR_API_KEY with XOR_BASE_URL, PERPLEXITY_API_KEY, or CLOUDFLARE_API_KEY with CLOUDFLARE_ACCOUNT_ID, else 'heuristic'), 'heuristic' (no LLM), 'llm' (a cheap model picks per prompt), or 'jev' (a System One decision model — TypeSafe Jev, Laya, XOR, Perplexity or Cloudflare Clef — with calibrated confidence).",
|
|
620
620
|
choices: ["auto", "heuristic", "llm", "jev"],
|
|
621
621
|
alias: "classifier-strategy",
|
|
622
622
|
},
|
|
@@ -46,12 +46,66 @@ function getCopilotEnvPath() {
|
|
|
46
46
|
function getProfileCandidates() {
|
|
47
47
|
return [".zshrc", ".zprofile", ".bashrc", ".bash_profile", ".profile"].map((name) => join(homedir(), name));
|
|
48
48
|
}
|
|
49
|
+
/** A command that is `source`/`.` of a path ending in exactly `copilot-env.sh`, optionally after `then`/`else`/`do`. */
|
|
50
|
+
const ENV_SCRIPT_SOURCE_COMMAND = /^\s*(?:(?:then|else|do)\s+)?(?:source|\.)\s+(["']?)(?:[^\s"']*\/)?copilot-env\.sh\1(?:\s|$)/;
|
|
51
|
+
/**
|
|
52
|
+
* One profile line as the commands it runs, comment dropped. Quote-aware, so a
|
|
53
|
+
* `;` or `#` inside quotes neither splits nor starts a comment. Heredocs,
|
|
54
|
+
* multi-line strings and line continuations are not modelled, so a source line
|
|
55
|
+
* inside one is still counted, and a source built with `eval` is missed. Both
|
|
56
|
+
* are rare; this guards against the common mentions (a comment, an `echo`, a
|
|
57
|
+
* file-existence test, a `.bak` name) that suppressed the note before.
|
|
58
|
+
*/
|
|
59
|
+
function splitProfileCommands(line) {
|
|
60
|
+
const commands = [];
|
|
61
|
+
let current = "";
|
|
62
|
+
let quote = null;
|
|
63
|
+
for (let i = 0; i < line.length; i++) {
|
|
64
|
+
const char = line[i];
|
|
65
|
+
if (quote) {
|
|
66
|
+
current += char;
|
|
67
|
+
if (quote === '"' && char === "\\") {
|
|
68
|
+
current += line[i + 1] ?? "";
|
|
69
|
+
i++;
|
|
70
|
+
}
|
|
71
|
+
else if (char === quote) {
|
|
72
|
+
quote = null;
|
|
73
|
+
}
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (char === "\\") {
|
|
77
|
+
current += char + (line[i + 1] ?? "");
|
|
78
|
+
i++;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (char === '"' || char === "'") {
|
|
82
|
+
quote = char;
|
|
83
|
+
current += char;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (char === "#" &&
|
|
87
|
+
(current === "" || /\s/.test(current[current.length - 1]))) {
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
if (char === ";" || char === "&" || char === "|") {
|
|
91
|
+
commands.push(current);
|
|
92
|
+
current = "";
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
current += char;
|
|
96
|
+
}
|
|
97
|
+
commands.push(current);
|
|
98
|
+
return commands;
|
|
99
|
+
}
|
|
49
100
|
/** Whether any shell profile already sources the generated script. */
|
|
50
101
|
async function isEnvScriptSourced() {
|
|
51
102
|
const fs = await import("fs");
|
|
52
103
|
for (const profile of getProfileCandidates()) {
|
|
53
104
|
try {
|
|
54
|
-
if (fs
|
|
105
|
+
if (fs
|
|
106
|
+
.readFileSync(profile, "utf8")
|
|
107
|
+
.split(/\r?\n/)
|
|
108
|
+
.some((line) => splitProfileCommands(line).some((command) => ENV_SCRIPT_SOURCE_COMMAND.test(command)))) {
|
|
55
109
|
return true;
|
|
56
110
|
}
|
|
57
111
|
}
|
|
@@ -122,6 +122,11 @@ export const MODEL_CONTEXT_WINDOWS = {
|
|
|
122
122
|
"claude-fable-5-1": 1_000_000,
|
|
123
123
|
// Claude 5 (mid 2026) — 1M context window
|
|
124
124
|
"claude-sonnet-5": 1_000_000,
|
|
125
|
+
"claude-opus-5": 1_000_000,
|
|
126
|
+
"claude-fable-5": 1_000_000,
|
|
127
|
+
// Claude 4.7 / 4.8 — 1M context window (platform.claude.com/docs/en/models)
|
|
128
|
+
"claude-opus-4-8": 1_000_000,
|
|
129
|
+
"claude-opus-4-7": 1_000_000,
|
|
125
130
|
// Claude 4.6 (Feb 2026) — 1M context window
|
|
126
131
|
"claude-opus-4-6": 1_000_000,
|
|
127
132
|
"claude-sonnet-4-6": 1_000_000,
|
|
@@ -220,6 +225,13 @@ export const MODEL_CONTEXT_WINDOWS = {
|
|
|
220
225
|
"claude-sonnet-5-5": 1_000_000,
|
|
221
226
|
"claude-fable-5-1": 1_000_000,
|
|
222
227
|
"claude-sonnet-5": 1_000_000,
|
|
228
|
+
// Placed before the 200K `claude-opus-4` prefix key below: the lookup takes
|
|
229
|
+
// the first prefix that matches, so a dated `claude-opus-4-8@...` id would
|
|
230
|
+
// otherwise land on 200K.
|
|
231
|
+
"claude-opus-5": 1_000_000,
|
|
232
|
+
"claude-fable-5": 1_000_000,
|
|
233
|
+
"claude-opus-4-8": 1_000_000,
|
|
234
|
+
"claude-opus-4-7": 1_000_000,
|
|
223
235
|
"claude-opus-4-6": 1_000_000,
|
|
224
236
|
"claude-sonnet-4-6": 1_000_000,
|
|
225
237
|
"claude-sonnet-4-5": 200_000,
|
|
@@ -115,6 +115,12 @@ export declare enum AIProviderName {
|
|
|
115
115
|
* inference type only. Distinct from `PERPLEXITY`, the Sonar text provider.
|
|
116
116
|
*/
|
|
117
117
|
PERPLEXITY_DECIDER = "perplexity-decider",
|
|
118
|
+
/**
|
|
119
|
+
* Cloudflare Clef (`@cf/cloudflare/clef`, `@cf/cloudflare/clef-flash`) on
|
|
120
|
+
* Workers AI — serves the `decide` inference type only. Distinct from
|
|
121
|
+
* `CLOUDFLARE`, the Workers AI text provider.
|
|
122
|
+
*/
|
|
123
|
+
CLOUDFLARE_CLEF = "cloudflare-clef",
|
|
118
124
|
AUTO = "auto"
|
|
119
125
|
}
|
|
120
126
|
/**
|
|
@@ -1781,3 +1787,13 @@ export declare enum XorModels {
|
|
|
1781
1787
|
export declare enum PerplexityDeciderModels {
|
|
1782
1788
|
PPLX_DECIDER_V1_27B = "pplx-decider-v1-27b"
|
|
1783
1789
|
}
|
|
1790
|
+
/**
|
|
1791
|
+
* Cloudflare Clef decision models, named as the Workers AI API names them in
|
|
1792
|
+
* the request body (the path adds the `@cf/cloudflare/` prefix). Hand-written:
|
|
1793
|
+
* Clef is a Tier-3 provider, so it is not in the provider catalog and codegen
|
|
1794
|
+
* never touches this. `CLEF` is the 27B model, `CLEF_FLASH` the 9B one.
|
|
1795
|
+
*/
|
|
1796
|
+
export declare enum CloudflareClefModels {
|
|
1797
|
+
CLEF = "clef",
|
|
1798
|
+
CLEF_FLASH = "clef-flash"
|
|
1799
|
+
}
|
package/dist/constants/enums.js
CHANGED
|
@@ -121,6 +121,12 @@ export var AIProviderName;
|
|
|
121
121
|
* inference type only. Distinct from `PERPLEXITY`, the Sonar text provider.
|
|
122
122
|
*/
|
|
123
123
|
AIProviderName["PERPLEXITY_DECIDER"] = "perplexity-decider";
|
|
124
|
+
/**
|
|
125
|
+
* Cloudflare Clef (`@cf/cloudflare/clef`, `@cf/cloudflare/clef-flash`) on
|
|
126
|
+
* Workers AI — serves the `decide` inference type only. Distinct from
|
|
127
|
+
* `CLOUDFLARE`, the Workers AI text provider.
|
|
128
|
+
*/
|
|
129
|
+
AIProviderName["CLOUDFLARE_CLEF"] = "cloudflare-clef";
|
|
124
130
|
AIProviderName["AUTO"] = "auto";
|
|
125
131
|
})(AIProviderName || (AIProviderName = {}));
|
|
126
132
|
/**
|
|
@@ -2103,3 +2109,14 @@ export var PerplexityDeciderModels;
|
|
|
2103
2109
|
(function (PerplexityDeciderModels) {
|
|
2104
2110
|
PerplexityDeciderModels["PPLX_DECIDER_V1_27B"] = "pplx-decider-v1-27b";
|
|
2105
2111
|
})(PerplexityDeciderModels || (PerplexityDeciderModels = {}));
|
|
2112
|
+
/**
|
|
2113
|
+
* Cloudflare Clef decision models, named as the Workers AI API names them in
|
|
2114
|
+
* the request body (the path adds the `@cf/cloudflare/` prefix). Hand-written:
|
|
2115
|
+
* Clef is a Tier-3 provider, so it is not in the provider catalog and codegen
|
|
2116
|
+
* never touches this. `CLEF` is the 27B model, `CLEF_FLASH` the 9B one.
|
|
2117
|
+
*/
|
|
2118
|
+
export var CloudflareClefModels;
|
|
2119
|
+
(function (CloudflareClefModels) {
|
|
2120
|
+
CloudflareClefModels["CLEF"] = "clef";
|
|
2121
|
+
CloudflareClefModels["CLEF_FLASH"] = "clef-flash";
|
|
2122
|
+
})(CloudflareClefModels || (CloudflareClefModels = {}));
|
|
@@ -13,15 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import type { Tool, ToolExecutionGuards } from "../types/index.js";
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
17
|
-
* via buildNativeToolDeclarations. `search_tools` (tools.discovery) hydrates
|
|
18
|
-
* discovered tools into the live record between steps; without this refresh
|
|
19
|
-
* they stay invisible to the rest of the turn and every call dies as
|
|
20
|
-
* TOOL_NOT_FOUND. Mutates the snapshot in place — the request config holds
|
|
21
|
-
* `toolsConfig` by reference — and returns true when anything was added.
|
|
22
|
-
*/
|
|
23
|
-
/**
|
|
24
|
-
* Everything a native Gemini loop wraps around a tool call that the shared
|
|
16
|
+
* Everything a native provider loop wraps around a tool call that the shared
|
|
25
17
|
* engine does not do itself.
|
|
26
18
|
*
|
|
27
19
|
* Order matters. `raceWithAbort` sits INSIDE `withTimeout` so a turn-level
|
|
@@ -13,15 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { raceWithAbort, withTimeout } from "../utils/async/index.js";
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
17
|
-
* via buildNativeToolDeclarations. `search_tools` (tools.discovery) hydrates
|
|
18
|
-
* discovered tools into the live record between steps; without this refresh
|
|
19
|
-
* they stay invisible to the rest of the turn and every call dies as
|
|
20
|
-
* TOOL_NOT_FOUND. Mutates the snapshot in place — the request config holds
|
|
21
|
-
* `toolsConfig` by reference — and returns true when anything was added.
|
|
22
|
-
*/
|
|
23
|
-
/**
|
|
24
|
-
* Everything a native Gemini loop wraps around a tool call that the shared
|
|
16
|
+
* Everything a native provider loop wraps around a tool call that the shared
|
|
25
17
|
* engine does not do itself.
|
|
26
18
|
*
|
|
27
19
|
* Order matters. `raceWithAbort` sits INSIDE `withTimeout` so a turn-level
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AIProviderName } from "../constants/enums.js";
|
|
2
|
-
import { GoogleAIModels, OpenAIModels, AnthropicModels, VertexModels, OllamaModels, LiteLLMModels, NvidiaNimModels, OpenRouterModels, CohereModels, VoyageModels, TypeSafeModels, LayaModels, XorModels, PerplexityDeciderModels, JinaModels, StabilityModels, IdeogramModels, RecraftModels, ReplicateModels, } from "../constants/enums.js";
|
|
2
|
+
import { GoogleAIModels, OpenAIModels, AnthropicModels, VertexModels, OllamaModels, LiteLLMModels, NvidiaNimModels, OpenRouterModels, CohereModels, VoyageModels, TypeSafeModels, LayaModels, XorModels, PerplexityDeciderModels, CloudflareClefModels, JinaModels, StabilityModels, IdeogramModels, RecraftModels, ReplicateModels, } from "../constants/enums.js";
|
|
3
3
|
import { API_KEY_FORMATS } from "../utils/providerConfig.js";
|
|
4
4
|
import { getCatalogJsonEntries, catalogCredentialsKey, catalogEnvVar, } from "../providers/catalog/loader.js";
|
|
5
5
|
import { DEFAULT_INFERENCE_KINDS } from "../types/index.js";
|
|
@@ -451,7 +451,8 @@ const HAND_DESCRIPTORS = [
|
|
|
451
451
|
timeouts: { decideMs: 5000 },
|
|
452
452
|
setupUrl: "https://console.typesafe.ai/keys",
|
|
453
453
|
},
|
|
454
|
-
// Laya MUST stay after TypeSafe, XOR after Laya,
|
|
454
|
+
// Laya MUST stay after TypeSafe, XOR after Laya, Perplexity after XOR, and
|
|
455
|
+
// Cloudflare Clef after Perplexity.
|
|
455
456
|
// resolveDefaultDecisionProvider() returns the first configured
|
|
456
457
|
// DECISION_PROVIDERS entry, in this order, so a host configured for several
|
|
457
458
|
// keeps Jev for every built-in consumer and reaches the others only by
|
|
@@ -606,6 +607,79 @@ const HAND_DESCRIPTORS = [
|
|
|
606
607
|
},
|
|
607
608
|
setupUrl: "https://console.perplexity.ai",
|
|
608
609
|
},
|
|
610
|
+
{
|
|
611
|
+
name: AIProviderName.CLOUDFLARE_CLEF,
|
|
612
|
+
aliases: [],
|
|
613
|
+
credentialsKey: "cloudflareClef",
|
|
614
|
+
envVars: {
|
|
615
|
+
// The same token and account id the `cloudflare` text provider reads, so
|
|
616
|
+
// ambient Workers AI settings configure this provider too. It sits last
|
|
617
|
+
// in this list, which is what keeps that from displacing any other
|
|
618
|
+
// decision provider a host has configured.
|
|
619
|
+
apiKey: "CLOUDFLARE_API_KEY",
|
|
620
|
+
// Cloudflare's own API is the endpoint, so a base URL is optional; but
|
|
621
|
+
// the path carries the account id, so a token alone is useless. Both are
|
|
622
|
+
// required for it to count as configured, from the environment or from
|
|
623
|
+
// credentials.cloudflareClef.
|
|
624
|
+
extraRequired: ["CLOUDFLARE_ACCOUNT_ID"],
|
|
625
|
+
extraRequiredCredentialFields: { CLOUDFLARE_ACCOUNT_ID: "accountId" },
|
|
626
|
+
baseURL: "CLOUDFLARE_CLEF_BASE_URL",
|
|
627
|
+
model: "CLOUDFLARE_CLEF_MODEL",
|
|
628
|
+
},
|
|
629
|
+
defaultModel: CloudflareClefModels.CLEF,
|
|
630
|
+
// Serves only `decide`, like the other decision providers — the one
|
|
631
|
+
// declaration that keeps it out of every generation code path, and out of
|
|
632
|
+
// the way of the `cloudflare` text provider.
|
|
633
|
+
inferenceKinds: ["decide"],
|
|
634
|
+
toolSupport: "none",
|
|
635
|
+
localRuntime: false,
|
|
636
|
+
healthCheck: "env-only",
|
|
637
|
+
// Deliberately NO autoSelectPriority / autoSelectPreference /
|
|
638
|
+
// defaultHealthSweepPriority, for the same reason as TypeSafe above.
|
|
639
|
+
//
|
|
640
|
+
// Measured 2026-10-03: 0.3 to 1.0 s for a small request, 1.1 s for 64 questions on
|
|
641
|
+
// clef-flash and 1.3 s on clef, and 2.2 s at most for any of 60 requests
|
|
642
|
+
// sent at once. On 2026-10-04, 64 questions took 1.5 s (flash) / 2.3 s (clef).
|
|
643
|
+
// 5s covers those observations and keeps a fail-open consumer
|
|
644
|
+
// from waiting on a stuck call.
|
|
645
|
+
timeouts: { decideMs: 5_000 },
|
|
646
|
+
// The Workers AI endpoint ignores state text past about 2,048 tokens
|
|
647
|
+
// (hosted service or model: unknown), despite the documented 64K. The local
|
|
648
|
+
// 1,500-token estimate refuses before every measured cut. On 2026-10-04,
|
|
649
|
+
// both models read facts at the original clef-flash lower bounds for logs,
|
|
650
|
+
// number lists, digit arrays and compact JSON, but not about 2.5% further on;
|
|
651
|
+
// English prose and random CJK already matched on both. Digits cost 1,
|
|
652
|
+
// ASCII symbols 0.75, BMP non-ASCII 1.5 and astral characters 3 tokens.
|
|
653
|
+
// Natural Chinese, Japanese, Korean and Hindi prose and emoji-rich English
|
|
654
|
+
// were also safe under those rates on clef. A many-key object cut between
|
|
655
|
+
// 128 and 134 preceding keys on both models: its lower bound was 4,883
|
|
656
|
+
// compact-JSON characters, estimated at 2,299 tokens. The probe used an
|
|
657
|
+
// explicit field-name question and zero-padded keys together after the
|
|
658
|
+
// control failed three times. It then passed; necessity was not established.
|
|
659
|
+
// Other object shapes and Unicode sequences may differ.
|
|
660
|
+
decisionLimits: {
|
|
661
|
+
maxStateTokens: 1_500,
|
|
662
|
+
maxQuestions: 64,
|
|
663
|
+
nonAsciiTokensPerChar: 1.5,
|
|
664
|
+
digitTokensPerChar: 1,
|
|
665
|
+
symbolTokensPerChar: 0.75,
|
|
666
|
+
astralTokensPerChar: 3,
|
|
667
|
+
// Images only. Exactly four tiny images succeeded and five were refused
|
|
668
|
+
// on both models. image/jpg also succeeded and is normalized to JPEG.
|
|
669
|
+
// Retain the conservative 256,000-byte encoded-body cap: on 2026-10-04
|
|
670
|
+
// both models accepted 520,000 text characters and refused 525,000 with
|
|
671
|
+
// 413/code 5021. On 2026-10-03, clef-flash accepted 262,000 and refused
|
|
672
|
+
// 270,000. Estimates match encoded body characters / 4, rounded up. The
|
|
673
|
+
// threshold moved from 65,527 accepted / 67,527 refused to 130,026 /
|
|
674
|
+
// 131,276 (clef) and 130,027 / 131,277 (flash); errors still print 65,536.
|
|
675
|
+
// Inference: the new interval contains 131,072 (twice the printed
|
|
676
|
+
// figure); reason unknown.
|
|
677
|
+
// The new image-byte ceiling was not measured; the old 195/202 KB PNG boundary
|
|
678
|
+
// is historical. See the guide for exact model and date coverage.
|
|
679
|
+
media: { maxImages: 4, video: false, maxRequestBytes: 256_000 },
|
|
680
|
+
},
|
|
681
|
+
setupUrl: "https://dash.cloudflare.com/profile/api-tokens",
|
|
682
|
+
},
|
|
609
683
|
];
|
|
610
684
|
/**
|
|
611
685
|
* Builds a ProviderDescriptor for every JSON-catalog provider. Every field
|
|
@@ -724,9 +798,14 @@ function isDecisionProviderConfigured(descriptor, credentials) {
|
|
|
724
798
|
const hasKey = [descriptor.envVars.apiKey, ...(descriptor.envVars.fallbacks ?? [])].some(inEnv) ||
|
|
725
799
|
isSet(slice?.apiKey) ||
|
|
726
800
|
isSet(slice?.gatewayApiKey);
|
|
727
|
-
// A required base URL can also come from credentials.<key>.baseURL
|
|
728
|
-
|
|
729
|
-
|
|
801
|
+
// A required base URL can also come from credentials.<key>.baseURL, and any
|
|
802
|
+
// other required value the descriptor maps to a field of that slice.
|
|
803
|
+
const hasRequired = (descriptor.envVars.extraRequired ?? []).every((name) => {
|
|
804
|
+
const field = descriptor.envVars.extraRequiredCredentialFields?.[name];
|
|
805
|
+
return (inEnv(name) ||
|
|
806
|
+
(name === descriptor.envVars.baseURL && isSet(slice?.baseURL)) ||
|
|
807
|
+
(field !== undefined && isSet(slice?.[field])));
|
|
808
|
+
});
|
|
730
809
|
return hasKey && hasRequired;
|
|
731
810
|
}
|
|
732
811
|
/**
|
|
@@ -22,6 +22,17 @@ export function resolveCredentialKey(providerName) {
|
|
|
22
22
|
const normalized = providerName.toLowerCase();
|
|
23
23
|
return (ProviderFactory.getDescriptor(normalized)?.credentialsKey ?? normalized);
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* registerProvider() takes a class as well as a factory (ProviderConstructor),
|
|
27
|
+
* and calling a class without `new` throws. A prototype test cannot tell them
|
|
28
|
+
* apart, because every non-arrow function's prototype points back at itself,
|
|
29
|
+
* so the source text decides. A factory is therefore never constructed and
|
|
30
|
+
* never retried after it throws.
|
|
31
|
+
*/
|
|
32
|
+
function isClassConstructor(candidate) {
|
|
33
|
+
return (typeof candidate === "function" &&
|
|
34
|
+
/^class[\s{]/.test(Function.prototype.toString.call(candidate)));
|
|
35
|
+
}
|
|
25
36
|
/**
|
|
26
37
|
* True Factory Pattern implementation for AI Providers
|
|
27
38
|
* Uses registration-based approach to eliminate switch statements
|
|
@@ -113,7 +124,11 @@ export class ProviderFactory {
|
|
|
113
124
|
if (typeof registration.constructor !== "function") {
|
|
114
125
|
throw new Error(`Invalid constructor for provider ${providerName}: not a function`);
|
|
115
126
|
}
|
|
116
|
-
|
|
127
|
+
// A factory keeps being called as a method of the registration, as it
|
|
128
|
+
// always was; only a class is constructed.
|
|
129
|
+
const factoryResult = isClassConstructor(registration.constructor)
|
|
130
|
+
? new registration.constructor(model, resolvedProviderName, sdk, region, scopedCredentials)
|
|
131
|
+
: registration.constructor(model, resolvedProviderName, sdk, region, scopedCredentials);
|
|
117
132
|
const result = factoryResult instanceof Promise ? await factoryResult : factoryResult;
|
|
118
133
|
return result;
|
|
119
134
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ProviderFactory } from "./providerFactory.js";
|
|
2
2
|
import { logger } from "../utils/logger.js";
|
|
3
|
-
import { AIProviderName, GoogleAIModels, OpenAIModels, AnthropicModels, VertexModels, OllamaModels, LiteLLMModels, NvidiaNimModels, OpenRouterModels, CohereModels, VoyageModels, JinaModels, StabilityModels, IdeogramModels, RecraftModels, TypeSafeModels, LayaModels, XorModels, PerplexityDeciderModels, ReplicateModels, } from "../constants/enums.js";
|
|
3
|
+
import { AIProviderName, GoogleAIModels, OpenAIModels, AnthropicModels, VertexModels, OllamaModels, LiteLLMModels, NvidiaNimModels, OpenRouterModels, CohereModels, VoyageModels, JinaModels, StabilityModels, IdeogramModels, RecraftModels, TypeSafeModels, LayaModels, XorModels, PerplexityDeciderModels, CloudflareClefModels, ReplicateModels, } from "../constants/enums.js";
|
|
4
4
|
import { PROVIDER_DESCRIPTORS_BY_NAME } from "./providerDescriptors.js";
|
|
5
5
|
import { OPENAI_COMPAT_CATALOG } from "../providers/openaiCompatCatalog.js";
|
|
6
6
|
import { providerChoicesFor } from "./mediaHandlerCatalog.js";
|
|
@@ -260,6 +260,15 @@ export class ProviderRegistry {
|
|
|
260
260
|
return new PerplexityDeciderProvider(modelName, sdk, undefined, perplexityDeciderCreds);
|
|
261
261
|
}, process.env.PERPLEXITY_DECIDER_MODEL ||
|
|
262
262
|
PerplexityDeciderModels.PPLX_DECIDER_V1_27B, [], PROVIDER_DESCRIPTORS_BY_NAME.get(AIProviderName.PERPLEXITY_DECIDER));
|
|
263
|
+
// Register Cloudflare Clef — a `decide` provider reached through the
|
|
264
|
+
// Workers AI REST API. Its descriptor declares inferenceKinds:
|
|
265
|
+
// ["decide"], so nothing in the generation fallback chain can reach it,
|
|
266
|
+
// and it is registered apart from the `cloudflare` text provider.
|
|
267
|
+
ProviderFactory.registerProvider(AIProviderName.CLOUDFLARE_CLEF, async (modelName, _providerName, sdk, _region, credentials) => {
|
|
268
|
+
const cloudflareClefCreds = credentials;
|
|
269
|
+
const { CloudflareClefProvider } = await import("../providers/cloudflareClef.js");
|
|
270
|
+
return new CloudflareClefProvider(modelName, sdk, undefined, cloudflareClefCreds);
|
|
271
|
+
}, process.env.CLOUDFLARE_CLEF_MODEL || CloudflareClefModels.CLEF, [], PROVIDER_DESCRIPTORS_BY_NAME.get(AIProviderName.CLOUDFLARE_CLEF));
|
|
263
272
|
logger.debug("All AI providers registered successfully");
|
|
264
273
|
// ===== MEDIA HANDLER REGISTRATION =====
|
|
265
274
|
// Single registration path (Task 11): each ecosystem barrel (voice,
|
|
@@ -827,7 +827,7 @@ export class FileReferenceRegistry {
|
|
|
827
827
|
extractedText = await this.extractWordText(buffer, ref);
|
|
828
828
|
break;
|
|
829
829
|
case "pptx":
|
|
830
|
-
extractedText = await this.extractPptxText(buffer);
|
|
830
|
+
extractedText = await this.extractPptxText(buffer, ref.filename);
|
|
831
831
|
break;
|
|
832
832
|
case "video":
|
|
833
833
|
extractedText = await this.extractVideoContent(buffer, ref);
|
|
@@ -992,13 +992,13 @@ export class FileReferenceRegistry {
|
|
|
992
992
|
/**
|
|
993
993
|
* Extract text from a PowerPoint file using PptxProcessor.
|
|
994
994
|
*/
|
|
995
|
-
async extractPptxText(buffer) {
|
|
995
|
+
async extractPptxText(buffer, filename) {
|
|
996
996
|
try {
|
|
997
997
|
const { PptxProcessor } = await import("../processors/document/PptxProcessor.js");
|
|
998
998
|
return await PptxProcessor.extractText(buffer);
|
|
999
999
|
}
|
|
1000
1000
|
catch (err) {
|
|
1001
|
-
logger.warn(`[FileReferenceRegistry] PPTX extraction failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1001
|
+
logger.warn(`[FileReferenceRegistry] PPTX extraction failed for "${filename}": ${err instanceof Error ? err.message : String(err)}`);
|
|
1002
1002
|
return null;
|
|
1003
1003
|
}
|
|
1004
1004
|
}
|
|
@@ -23,6 +23,7 @@ import { typesafeManifest } from "./manifests/typesafe.js";
|
|
|
23
23
|
import { layaManifest } from "./manifests/laya.js";
|
|
24
24
|
import { xorManifest } from "./manifests/xor.js";
|
|
25
25
|
import { perplexityDeciderManifest } from "./manifests/perplexityDecider.js";
|
|
26
|
+
import { cloudflareClefManifest } from "./manifests/cloudflareClef.js";
|
|
26
27
|
import { cohereManifest } from "./manifests/cohere.js";
|
|
27
28
|
import { togetherAiManifest } from "./manifests/together-ai.js";
|
|
28
29
|
import { fireworksManifest } from "./manifests/fireworks.js";
|
|
@@ -124,6 +125,7 @@ export const MANIFEST_REGISTRY = {
|
|
|
124
125
|
laya: layaManifest,
|
|
125
126
|
xor: xorManifest,
|
|
126
127
|
"perplexity-decider": perplexityDeciderManifest,
|
|
128
|
+
"cloudflare-clef": cloudflareClefManifest,
|
|
127
129
|
cerebras: catalogManifest("cerebras"),
|
|
128
130
|
sambanova: catalogManifest("sambanova"),
|
|
129
131
|
cohere: cohereManifest,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ProviderModelManifest } from "../../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Cloudflare Clef on Workers AI — the `decide` inference type, not text
|
|
4
|
+
* generation, and not the Workers AI text models of the `cloudflare` provider.
|
|
5
|
+
*
|
|
6
|
+
* `contextWindow` is the figure Cloudflare documents (65,536 tokens). The
|
|
7
|
+
* Workers AI endpoint ignores state text past about 2,048 tokens without an
|
|
8
|
+
* error (hosted service or model: unknown), so NeuroLink enforces the limit on
|
|
9
|
+
* the descriptor's `decisionLimits`, not this. `vision` describes `generate()`
|
|
10
|
+
* input, which this provider does not serve; its image input is
|
|
11
|
+
* `DecisionRequest.images`.
|
|
12
|
+
*
|
|
13
|
+
* `maxOutputTokens` is required by the manifest type but has no honest value
|
|
14
|
+
* for a model that emits no text, so a small non-zero figure is used.
|
|
15
|
+
*/
|
|
16
|
+
export declare const cloudflareClefManifest: ProviderModelManifest;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare Clef on Workers AI — the `decide` inference type, not text
|
|
3
|
+
* generation, and not the Workers AI text models of the `cloudflare` provider.
|
|
4
|
+
*
|
|
5
|
+
* `contextWindow` is the figure Cloudflare documents (65,536 tokens). The
|
|
6
|
+
* Workers AI endpoint ignores state text past about 2,048 tokens without an
|
|
7
|
+
* error (hosted service or model: unknown), so NeuroLink enforces the limit on
|
|
8
|
+
* the descriptor's `decisionLimits`, not this. `vision` describes `generate()`
|
|
9
|
+
* input, which this provider does not serve; its image input is
|
|
10
|
+
* `DecisionRequest.images`.
|
|
11
|
+
*
|
|
12
|
+
* `maxOutputTokens` is required by the manifest type but has no honest value
|
|
13
|
+
* for a model that emits no text, so a small non-zero figure is used.
|
|
14
|
+
*/
|
|
15
|
+
export const cloudflareClefManifest = {
|
|
16
|
+
defaultContextWindow: 65_536,
|
|
17
|
+
models: {
|
|
18
|
+
_default: {
|
|
19
|
+
aliases: [],
|
|
20
|
+
contextWindow: 65_536,
|
|
21
|
+
maxOutputTokens: 256,
|
|
22
|
+
vision: false,
|
|
23
|
+
functionCalling: false,
|
|
24
|
+
},
|
|
25
|
+
clef: {
|
|
26
|
+
aliases: ["@cf/cloudflare/clef"],
|
|
27
|
+
displayName: "Cloudflare Clef (27B)",
|
|
28
|
+
contextWindow: 65_536,
|
|
29
|
+
maxOutputTokens: 256,
|
|
30
|
+
vision: false,
|
|
31
|
+
functionCalling: false,
|
|
32
|
+
},
|
|
33
|
+
"clef-flash": {
|
|
34
|
+
aliases: ["@cf/cloudflare/clef-flash"],
|
|
35
|
+
displayName: "Cloudflare Clef-flash (9B)",
|
|
36
|
+
contextWindow: 65_536,
|
|
37
|
+
maxOutputTokens: 256,
|
|
38
|
+
vision: false,
|
|
39
|
+
functionCalling: false,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
};
|
package/dist/neurolink.js
CHANGED
|
@@ -8640,11 +8640,22 @@ Current user's request: ${currentInput}`;
|
|
|
8640
8640
|
}
|
|
8641
8641
|
yield fallbackChunk;
|
|
8642
8642
|
}
|
|
8643
|
+
// Read after the drain: a background-loop provider (native Vertex+Claude)
|
|
8644
|
+
// fills these while the stream is consumed, so the values taken before
|
|
8645
|
+
// the loop are still empty for it. Without this, a fallback that ran
|
|
8646
|
+
// tools reports none on the result and in the end-of-turn events, and
|
|
8647
|
+
// the 0-output gate below counts tool calls that had not appeared yet.
|
|
8648
|
+
const finalToolCalls = fallbackResult.toolCalls ?? [];
|
|
8649
|
+
const finalToolResults = fallbackResult.toolResults ?? [];
|
|
8643
8650
|
if (fallbackRealOutputChunks === 0 &&
|
|
8644
|
-
|
|
8645
|
-
|
|
8651
|
+
finalToolCalls.length === 0 &&
|
|
8652
|
+
finalToolResults.length === 0) {
|
|
8646
8653
|
throw new Error(`Fallback provider ${fallbackRoute.provider} also returned 0 real output chunks (chunkCount=${fallbackChunkCount}, sentinel-only or empty)`);
|
|
8647
8654
|
}
|
|
8655
|
+
streamState.toolCalls = finalToolCalls;
|
|
8656
|
+
streamState.toolResults = finalToolResults;
|
|
8657
|
+
streamState.finishReason =
|
|
8658
|
+
fallbackResult.finishReason ?? streamState.finishReason;
|
|
8648
8659
|
// Fallback succeeded - likely guardrails blocked primary
|
|
8649
8660
|
metadata.fallbackProvider = fallbackRoute.provider;
|
|
8650
8661
|
metadata.fallbackModel = fallbackRoute.model;
|
|
@@ -660,6 +660,7 @@ export class VideoProcessor extends BaseFileProcessor {
|
|
|
660
660
|
*/
|
|
661
661
|
async probeVideoWithFfmpeg(filePath) {
|
|
662
662
|
let report;
|
|
663
|
+
let exitFailure;
|
|
663
664
|
try {
|
|
664
665
|
const { stderr } = await runFfmpeg(["-hide_banner", "-i", filePath, "-t", "0", "-f", "null", "-"], { timeoutMs: VIDEO_CONFIG.FFPROBE_TIMEOUT_MS });
|
|
665
666
|
report = stderr;
|
|
@@ -668,18 +669,29 @@ export class VideoProcessor extends BaseFileProcessor {
|
|
|
668
669
|
// A non-zero exit still carries the stream report when the failure came
|
|
669
670
|
// after the input was opened.
|
|
670
671
|
const stderr = error.stderr;
|
|
672
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
671
673
|
if (typeof stderr !== "string") {
|
|
672
674
|
return {
|
|
673
675
|
success: false,
|
|
674
|
-
error: `ffmpeg probe failed: ${
|
|
676
|
+
error: `ffmpeg probe failed: ${message}`,
|
|
675
677
|
};
|
|
676
678
|
}
|
|
677
679
|
report = stderr;
|
|
680
|
+
exitFailure = message;
|
|
678
681
|
}
|
|
679
682
|
const data = this.parseFfmpegInputReport(report);
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
+
if (data) {
|
|
684
|
+
return { success: true, data };
|
|
685
|
+
}
|
|
686
|
+
// Keep ffmpeg's own words when it failed as well as found no duration:
|
|
687
|
+
// a file it cannot read at all is the common case, and the reason is the
|
|
688
|
+
// only thing the warning that follows can tell the reader.
|
|
689
|
+
return {
|
|
690
|
+
success: false,
|
|
691
|
+
error: exitFailure
|
|
692
|
+
? `ffmpeg reported no duration: ${exitFailure}`
|
|
693
|
+
: "ffmpeg reported no duration",
|
|
694
|
+
};
|
|
683
695
|
}
|
|
684
696
|
/**
|
|
685
697
|
* Parse the input section of an ffmpeg banner: `Duration:`, `bitrate:` and
|