@synapseia-network/node 0.8.5
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/LICENSE +105 -0
- package/README.md +232 -0
- package/dist/bid-responder-Q725ZIUC.js +86 -0
- package/dist/bootstrap.js +22 -0
- package/dist/chain-info-lightweight-2UWAQZBF.js +303 -0
- package/dist/chat-stream-handler-BSHSGMFF.js +127 -0
- package/dist/chunk-2X7MSWD4.js +270 -0
- package/dist/chunk-3BHRQWSM.js +531 -0
- package/dist/chunk-5QFTU52A.js +442 -0
- package/dist/chunk-5ZAJBIAV.js +25 -0
- package/dist/chunk-7FLDR5NT.js +186 -0
- package/dist/chunk-C5XRYLYP.js +137 -0
- package/dist/chunk-D7ADMHK2.js +36 -0
- package/dist/chunk-DXUYWRO7.js +23 -0
- package/dist/chunk-F5UDK56Z.js +289 -0
- package/dist/chunk-NEHR6XY7.js +111 -0
- package/dist/chunk-NMJVODKH.js +453 -0
- package/dist/chunk-PRVT22SM.js +324 -0
- package/dist/chunk-T2ZRG5CX.js +1380 -0
- package/dist/chunk-V2L5SXTL.js +88 -0
- package/dist/chunk-XL2NJWFY.js +702 -0
- package/dist/embedding-C6GE3WVM.js +16 -0
- package/dist/hardware-ITQQJ5YI.js +37 -0
- package/dist/index.js +16836 -0
- package/dist/inference-server-CIGRJ36H.js +25 -0
- package/dist/local-cors-J6RWNMMD.js +44 -0
- package/dist/model-catalog-C53SDFMG.js +15 -0
- package/dist/model-discovery-LA6YMT3I.js +10 -0
- package/dist/ollama-XVXA3A37.js +9 -0
- package/dist/rewards-vault-cli-HW7H4EMD.js +147 -0
- package/dist/scripts/create_nodes.sh +6 -0
- package/dist/scripts/diloco_train.py +319 -0
- package/dist/scripts/train_lora.py +237 -0
- package/dist/scripts/train_micro.py +586 -0
- package/dist/trainer-HQMV2ZAR.js +21 -0
- package/package.json +128 -0
- package/scripts/create_nodes.sh +6 -0
- package/scripts/diloco_train.py +319 -0
- package/scripts/train_lora.py +237 -0
- package/scripts/train_micro.py +586 -0
|
@@ -0,0 +1,1380 @@
|
|
|
1
|
+
import { fileURLToPath as __synFup } from "url";import { dirname as __synDn } from "path";const __filename = __synFup(import.meta.url);const __dirname = __synDn(__filename);
|
|
2
|
+
import {
|
|
3
|
+
OllamaHelper
|
|
4
|
+
} from "./chunk-2X7MSWD4.js";
|
|
5
|
+
import {
|
|
6
|
+
init_logger,
|
|
7
|
+
logger_default
|
|
8
|
+
} from "./chunk-V2L5SXTL.js";
|
|
9
|
+
import {
|
|
10
|
+
__name
|
|
11
|
+
} from "./chunk-D7ADMHK2.js";
|
|
12
|
+
|
|
13
|
+
// src/modules/llm/providers.ts
|
|
14
|
+
var CLOUD_PROVIDERS = [
|
|
15
|
+
{
|
|
16
|
+
id: "openai",
|
|
17
|
+
label: "OpenAI",
|
|
18
|
+
endpoint: "https://api.openai.com/v1/chat/completions",
|
|
19
|
+
apiKeyEnvVar: "OPENAI_API_KEY",
|
|
20
|
+
models: {
|
|
21
|
+
top: {
|
|
22
|
+
modelId: "gpt-5",
|
|
23
|
+
latencyMs: 600,
|
|
24
|
+
maxTokens: 4e5,
|
|
25
|
+
costPerCall: 5e-3
|
|
26
|
+
},
|
|
27
|
+
mid: {
|
|
28
|
+
modelId: "gpt-4o",
|
|
29
|
+
latencyMs: 400,
|
|
30
|
+
maxTokens: 128e3,
|
|
31
|
+
costPerCall: 25e-4
|
|
32
|
+
},
|
|
33
|
+
budget: {
|
|
34
|
+
modelId: "gpt-4o-mini",
|
|
35
|
+
latencyMs: 250,
|
|
36
|
+
maxTokens: 128e3,
|
|
37
|
+
costPerCall: 6e-4
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: "anthropic",
|
|
43
|
+
label: "Anthropic",
|
|
44
|
+
endpoint: "https://api.anthropic.com/v1/messages",
|
|
45
|
+
apiKeyEnvVar: "ANTHROPIC_API_KEY",
|
|
46
|
+
models: {
|
|
47
|
+
top: {
|
|
48
|
+
modelId: "claude-opus-4-7",
|
|
49
|
+
latencyMs: 700,
|
|
50
|
+
maxTokens: 2e5,
|
|
51
|
+
costPerCall: 0.015
|
|
52
|
+
},
|
|
53
|
+
mid: {
|
|
54
|
+
modelId: "claude-sonnet-4-6",
|
|
55
|
+
latencyMs: 300,
|
|
56
|
+
maxTokens: 2e5,
|
|
57
|
+
costPerCall: 3e-3
|
|
58
|
+
},
|
|
59
|
+
budget: {
|
|
60
|
+
modelId: "claude-haiku-4-5",
|
|
61
|
+
latencyMs: 200,
|
|
62
|
+
maxTokens: 2e5,
|
|
63
|
+
costPerCall: 8e-4
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
id: "google",
|
|
69
|
+
label: "Google (Gemini)",
|
|
70
|
+
endpoint: "https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent",
|
|
71
|
+
apiKeyEnvVar: "GEMINI_API_KEY",
|
|
72
|
+
models: {
|
|
73
|
+
top: {
|
|
74
|
+
modelId: "gemini-2.5-pro",
|
|
75
|
+
latencyMs: 600,
|
|
76
|
+
maxTokens: 1048576,
|
|
77
|
+
costPerCall: 35e-4
|
|
78
|
+
},
|
|
79
|
+
mid: {
|
|
80
|
+
modelId: "gemini-2.5-flash",
|
|
81
|
+
latencyMs: 300,
|
|
82
|
+
maxTokens: 1048576,
|
|
83
|
+
costPerCall: 8e-4
|
|
84
|
+
},
|
|
85
|
+
budget: {
|
|
86
|
+
modelId: "gemini-2.5-flash-lite",
|
|
87
|
+
latencyMs: 200,
|
|
88
|
+
maxTokens: 1048576,
|
|
89
|
+
costPerCall: 15e-5
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: "moonshot",
|
|
95
|
+
label: "Kimi (Moonshot)",
|
|
96
|
+
endpoint: "https://api.moonshot.ai/v1/chat/completions",
|
|
97
|
+
apiKeyEnvVar: "MOONSHOT_API_KEY",
|
|
98
|
+
models: {
|
|
99
|
+
top: {
|
|
100
|
+
modelId: "kimi-k2.6",
|
|
101
|
+
latencyMs: 500,
|
|
102
|
+
maxTokens: 256e3,
|
|
103
|
+
costPerCall: 2e-3
|
|
104
|
+
},
|
|
105
|
+
mid: {
|
|
106
|
+
modelId: "kimi-k2-0711-preview",
|
|
107
|
+
latencyMs: 350,
|
|
108
|
+
maxTokens: 131072,
|
|
109
|
+
costPerCall: 1e-3
|
|
110
|
+
},
|
|
111
|
+
budget: {
|
|
112
|
+
modelId: "moonshot-v1-32k",
|
|
113
|
+
latencyMs: 300,
|
|
114
|
+
maxTokens: 32768,
|
|
115
|
+
costPerCall: 5e-4
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: "minimax",
|
|
121
|
+
label: "MiniMax",
|
|
122
|
+
endpoint: "https://api.minimax.io/v1/chat/completions",
|
|
123
|
+
apiKeyEnvVar: "MINIMAX_API_KEY",
|
|
124
|
+
models: {
|
|
125
|
+
top: {
|
|
126
|
+
modelId: "MiniMax-M2.7",
|
|
127
|
+
latencyMs: 400,
|
|
128
|
+
maxTokens: 245760,
|
|
129
|
+
costPerCall: 15e-4
|
|
130
|
+
},
|
|
131
|
+
mid: {
|
|
132
|
+
modelId: "abab7-chat-preview",
|
|
133
|
+
latencyMs: 350,
|
|
134
|
+
maxTokens: 245760,
|
|
135
|
+
costPerCall: 8e-4
|
|
136
|
+
},
|
|
137
|
+
budget: {
|
|
138
|
+
modelId: "abab6.5s-chat",
|
|
139
|
+
latencyMs: 300,
|
|
140
|
+
maxTokens: 245760,
|
|
141
|
+
costPerCall: 3e-4
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: "zhipu",
|
|
147
|
+
label: "Zhipu (GLM)",
|
|
148
|
+
endpoint: "https://open.bigmodel.cn/api/paas/v4/chat/completions",
|
|
149
|
+
apiKeyEnvVar: "ZHIPU_API_KEY",
|
|
150
|
+
models: {
|
|
151
|
+
top: {
|
|
152
|
+
modelId: "glm-4.6",
|
|
153
|
+
latencyMs: 500,
|
|
154
|
+
maxTokens: 2e5,
|
|
155
|
+
costPerCall: 15e-4
|
|
156
|
+
},
|
|
157
|
+
mid: {
|
|
158
|
+
modelId: "glm-4-plus",
|
|
159
|
+
latencyMs: 400,
|
|
160
|
+
maxTokens: 128e3,
|
|
161
|
+
costPerCall: 8e-4
|
|
162
|
+
},
|
|
163
|
+
budget: {
|
|
164
|
+
modelId: "glm-4-flash",
|
|
165
|
+
latencyMs: 250,
|
|
166
|
+
maxTokens: 128e3,
|
|
167
|
+
costPerCall: 1e-4
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
];
|
|
172
|
+
var CLOUD_PROVIDERS_BY_ID = new Map(CLOUD_PROVIDERS.map((p) => [
|
|
173
|
+
p.id,
|
|
174
|
+
p
|
|
175
|
+
]));
|
|
176
|
+
var OLLAMA_DEFAULT_MODELS = [
|
|
177
|
+
{
|
|
178
|
+
modelId: "qwen2.5:0.5b",
|
|
179
|
+
latencyMs: 300,
|
|
180
|
+
maxTokens: 4096
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
modelId: "qwen2.5:3b",
|
|
184
|
+
latencyMs: 800,
|
|
185
|
+
maxTokens: 8192
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
modelId: "gemma3:4b",
|
|
189
|
+
latencyMs: 1200,
|
|
190
|
+
maxTokens: 8192
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
modelId: "llama3.2:3b",
|
|
194
|
+
latencyMs: 900,
|
|
195
|
+
maxTokens: 8192
|
|
196
|
+
}
|
|
197
|
+
];
|
|
198
|
+
function resolveSlug(slug) {
|
|
199
|
+
const slash = slug.indexOf("/");
|
|
200
|
+
if (slash <= 0) return null;
|
|
201
|
+
const provider = slug.slice(0, slash);
|
|
202
|
+
const modelId = slug.slice(slash + 1);
|
|
203
|
+
if (!modelId) return null;
|
|
204
|
+
if (provider === "ollama") {
|
|
205
|
+
const descriptor2 = OLLAMA_DEFAULT_MODELS.find((m) => m.modelId === modelId);
|
|
206
|
+
return {
|
|
207
|
+
provider: "ollama",
|
|
208
|
+
modelId,
|
|
209
|
+
endpoint: "",
|
|
210
|
+
descriptor: descriptor2
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
if (provider === "synapseia") {
|
|
214
|
+
return {
|
|
215
|
+
provider: "synapseia",
|
|
216
|
+
modelId,
|
|
217
|
+
endpoint: ""
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
const entry = CLOUD_PROVIDERS_BY_ID.get(provider);
|
|
221
|
+
if (!entry) return null;
|
|
222
|
+
const descriptor = Object.values(entry.models).find((m) => m.modelId === modelId);
|
|
223
|
+
if (!descriptor) return null;
|
|
224
|
+
return {
|
|
225
|
+
provider: entry.id,
|
|
226
|
+
modelId,
|
|
227
|
+
endpoint: entry.endpoint,
|
|
228
|
+
descriptor
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
__name(resolveSlug, "resolveSlug");
|
|
232
|
+
var FALLBACK_MODEL_SLUG = "anthropic/claude-sonnet-4-6";
|
|
233
|
+
function topSlugFor(provider) {
|
|
234
|
+
const entry = CLOUD_PROVIDERS_BY_ID.get(provider);
|
|
235
|
+
if (!entry) return FALLBACK_MODEL_SLUG;
|
|
236
|
+
return `${entry.id}/${entry.models.top.modelId}`;
|
|
237
|
+
}
|
|
238
|
+
__name(topSlugFor, "topSlugFor");
|
|
239
|
+
|
|
240
|
+
// src/modules/llm/llm-provider.ts
|
|
241
|
+
import { Injectable as Injectable2, Optional } from "@nestjs/common";
|
|
242
|
+
|
|
243
|
+
// src/shared/sanitize-llm-output.ts
|
|
244
|
+
var REASONING_PATTERNS = [
|
|
245
|
+
/<(think|thinking|reasoning|scratchpad)\b[^>]*>[\s\S]*?<\/\1>/gi,
|
|
246
|
+
/<\|(?:start_of_thinking|reasoning|scratchpad|thought)\|>[\s\S]*?<\|end_of_(?:thinking|reasoning|scratchpad|thought)\|>/gi
|
|
247
|
+
];
|
|
248
|
+
var UNCLOSED_REASONING_PATTERNS = [
|
|
249
|
+
/<(think|thinking|reasoning|scratchpad)\b[^>]*>[\s\S]*$/i,
|
|
250
|
+
/<\|(?:start_of_thinking|reasoning|scratchpad|thought)\|>[\s\S]*$/i
|
|
251
|
+
];
|
|
252
|
+
function stripReasoning(input) {
|
|
253
|
+
if (input == null) return "";
|
|
254
|
+
const text = typeof input === "string" ? input : String(input);
|
|
255
|
+
let out = text;
|
|
256
|
+
for (const re of REASONING_PATTERNS) out = out.replace(re, "");
|
|
257
|
+
for (const re of UNCLOSED_REASONING_PATTERNS) out = out.replace(re, "");
|
|
258
|
+
return out.trim();
|
|
259
|
+
}
|
|
260
|
+
__name(stripReasoning, "stripReasoning");
|
|
261
|
+
|
|
262
|
+
// src/modules/llm/synapseia-serving-client.ts
|
|
263
|
+
init_logger();
|
|
264
|
+
import { Injectable } from "@nestjs/common";
|
|
265
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
266
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
267
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
268
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
269
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
270
|
+
}
|
|
271
|
+
__name(_ts_decorate, "_ts_decorate");
|
|
272
|
+
function _ts_metadata(k, v) {
|
|
273
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
274
|
+
}
|
|
275
|
+
__name(_ts_metadata, "_ts_metadata");
|
|
276
|
+
var DEFAULT_URL = "http://127.0.0.1:8080";
|
|
277
|
+
var DEFAULT_TIMEOUT_MS = 6e4;
|
|
278
|
+
var SynapseiaServingClient = class {
|
|
279
|
+
static {
|
|
280
|
+
__name(this, "SynapseiaServingClient");
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* The serving URL + the Synapseia version currently loaded. Set by
|
|
284
|
+
* `ActiveModelSubscriber` at boot and after every hot-reload.
|
|
285
|
+
*/
|
|
286
|
+
servingUrl;
|
|
287
|
+
activeVersion = null;
|
|
288
|
+
constructor() {
|
|
289
|
+
this.servingUrl = process.env.SYNAPSEIA_SERVING_URL ?? DEFAULT_URL;
|
|
290
|
+
}
|
|
291
|
+
setActiveVersion(version, url) {
|
|
292
|
+
this.activeVersion = version;
|
|
293
|
+
if (url) this.servingUrl = url;
|
|
294
|
+
logger_default.info(`[SynapseiaServing] now serving ${version} at ${this.servingUrl}`);
|
|
295
|
+
}
|
|
296
|
+
getActiveVersion() {
|
|
297
|
+
return this.activeVersion;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Returns `true` when the local runtime answers `GET /health` (or
|
|
301
|
+
* `GET /v1/models`) within 2s. Used by the bidder to decide whether
|
|
302
|
+
* to advertise a Synapseia bid this auction.
|
|
303
|
+
*/
|
|
304
|
+
async isAvailable() {
|
|
305
|
+
try {
|
|
306
|
+
const res = await fetch(`${this.servingUrl}/v1/models`, {
|
|
307
|
+
signal: AbortSignal.timeout(2e3)
|
|
308
|
+
});
|
|
309
|
+
return res.ok;
|
|
310
|
+
} catch {
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
async generate(opts) {
|
|
315
|
+
if (!this.activeVersion) {
|
|
316
|
+
throw new Error("Synapseia serving not initialized (no active version)");
|
|
317
|
+
}
|
|
318
|
+
const started = Date.now();
|
|
319
|
+
const res = await fetch(`${this.servingUrl}/v1/chat/completions`, {
|
|
320
|
+
method: "POST",
|
|
321
|
+
headers: {
|
|
322
|
+
"content-type": "application/json"
|
|
323
|
+
},
|
|
324
|
+
body: JSON.stringify({
|
|
325
|
+
model: this.activeVersion,
|
|
326
|
+
messages: opts.messages,
|
|
327
|
+
temperature: opts.temperature ?? 0.2,
|
|
328
|
+
max_tokens: opts.maxTokens ?? 2048
|
|
329
|
+
}),
|
|
330
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? DEFAULT_TIMEOUT_MS)
|
|
331
|
+
});
|
|
332
|
+
if (!res.ok) {
|
|
333
|
+
const body = await res.text();
|
|
334
|
+
throw new Error(`Synapseia serving HTTP ${res.status}: ${body.slice(0, 200)}`);
|
|
335
|
+
}
|
|
336
|
+
const data = await res.json();
|
|
337
|
+
const content = data.choices?.[0]?.message?.content ?? "";
|
|
338
|
+
if (!content) {
|
|
339
|
+
throw new Error("Synapseia serving returned empty content");
|
|
340
|
+
}
|
|
341
|
+
return {
|
|
342
|
+
content,
|
|
343
|
+
modelVersion: this.activeVersion,
|
|
344
|
+
latencyMs: Date.now() - started
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
SynapseiaServingClient = _ts_decorate([
|
|
349
|
+
Injectable(),
|
|
350
|
+
_ts_metadata("design:type", Function),
|
|
351
|
+
_ts_metadata("design:paramtypes", [])
|
|
352
|
+
], SynapseiaServingClient);
|
|
353
|
+
|
|
354
|
+
// src/modules/llm/llm-provider.ts
|
|
355
|
+
init_logger();
|
|
356
|
+
|
|
357
|
+
// src/modules/llm/adapters/llm-response-adapter.ts
|
|
358
|
+
function readString(obj, ...path) {
|
|
359
|
+
let cur = obj;
|
|
360
|
+
for (const k of path) {
|
|
361
|
+
if (cur == null || typeof cur !== "object") return void 0;
|
|
362
|
+
cur = cur[k];
|
|
363
|
+
}
|
|
364
|
+
return typeof cur === "string" ? cur : void 0;
|
|
365
|
+
}
|
|
366
|
+
__name(readString, "readString");
|
|
367
|
+
function readNumber(obj, ...path) {
|
|
368
|
+
let cur = obj;
|
|
369
|
+
for (const k of path) {
|
|
370
|
+
if (cur == null || typeof cur !== "object") return void 0;
|
|
371
|
+
cur = cur[k];
|
|
372
|
+
}
|
|
373
|
+
return typeof cur === "number" ? cur : void 0;
|
|
374
|
+
}
|
|
375
|
+
__name(readNumber, "readNumber");
|
|
376
|
+
function readArray(obj, ...path) {
|
|
377
|
+
let cur = obj;
|
|
378
|
+
for (const k of path) {
|
|
379
|
+
if (cur == null || typeof cur !== "object") return [];
|
|
380
|
+
cur = cur[k];
|
|
381
|
+
}
|
|
382
|
+
return Array.isArray(cur) ? cur : [];
|
|
383
|
+
}
|
|
384
|
+
__name(readArray, "readArray");
|
|
385
|
+
function defaultErrorMessage(httpStatus, body, fallbackText) {
|
|
386
|
+
const err = readString(body, "error", "message") ?? readString(body, "message") ?? readString(body, "error");
|
|
387
|
+
if (err) return `HTTP ${httpStatus}: ${err}`;
|
|
388
|
+
if (fallbackText) {
|
|
389
|
+
const snippet = fallbackText.replace(/\s+/g, " ").trim().slice(0, 200);
|
|
390
|
+
return `HTTP ${httpStatus}: ${snippet}`;
|
|
391
|
+
}
|
|
392
|
+
return `HTTP ${httpStatus}`;
|
|
393
|
+
}
|
|
394
|
+
__name(defaultErrorMessage, "defaultErrorMessage");
|
|
395
|
+
|
|
396
|
+
// src/modules/llm/adapters/openai.adapter.ts
|
|
397
|
+
var FINISH_REASON_MAP = {
|
|
398
|
+
stop: "stop",
|
|
399
|
+
length: "length",
|
|
400
|
+
content_filter: "content_filter",
|
|
401
|
+
tool_calls: "tool_calls",
|
|
402
|
+
function_call: "tool_calls"
|
|
403
|
+
};
|
|
404
|
+
var OpenAIAdapter = class {
|
|
405
|
+
static {
|
|
406
|
+
__name(this, "OpenAIAdapter");
|
|
407
|
+
}
|
|
408
|
+
providerId = "openai";
|
|
409
|
+
buildRequest(req) {
|
|
410
|
+
const entry = CLOUD_PROVIDERS_BY_ID.get("openai");
|
|
411
|
+
if (!entry) throw new Error("openai provider not registered");
|
|
412
|
+
const body = {
|
|
413
|
+
model: req.model,
|
|
414
|
+
messages: [
|
|
415
|
+
{
|
|
416
|
+
role: "user",
|
|
417
|
+
content: req.prompt
|
|
418
|
+
}
|
|
419
|
+
]
|
|
420
|
+
};
|
|
421
|
+
if (req.hyperparams?.temperature !== void 0) body.temperature = req.hyperparams.temperature;
|
|
422
|
+
if (req.hyperparams?.maxTokens !== void 0) body.max_tokens = req.hyperparams.maxTokens;
|
|
423
|
+
if (req.hyperparams?.forceJson) body.response_format = {
|
|
424
|
+
type: "json_object"
|
|
425
|
+
};
|
|
426
|
+
return {
|
|
427
|
+
url: entry.endpoint,
|
|
428
|
+
init: {
|
|
429
|
+
method: "POST",
|
|
430
|
+
headers: {
|
|
431
|
+
"Content-Type": "application/json",
|
|
432
|
+
"Authorization": `Bearer ${req.apiKey}`
|
|
433
|
+
},
|
|
434
|
+
body: JSON.stringify(body)
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
parseResponse(_httpStatus, body) {
|
|
439
|
+
const choices = readArray(body, "choices");
|
|
440
|
+
if (choices.length === 0) {
|
|
441
|
+
throw new Error("OpenAI response had no choices[]");
|
|
442
|
+
}
|
|
443
|
+
const choice = choices[0];
|
|
444
|
+
const refusal = readString(choice, "message", "refusal");
|
|
445
|
+
if (refusal) {
|
|
446
|
+
throw new Error(`OpenAI refused to answer: ${refusal}`);
|
|
447
|
+
}
|
|
448
|
+
const content = readString(choice, "message", "content");
|
|
449
|
+
if (typeof content !== "string" || content.length === 0) {
|
|
450
|
+
const finish = readString(choice, "finish_reason") ?? "unknown";
|
|
451
|
+
throw new Error(`OpenAI returned no message.content (finish_reason=${finish}); tool_calls/multimodal responses are not supported by this node`);
|
|
452
|
+
}
|
|
453
|
+
const finishStr = readString(choice, "finish_reason") ?? "stop";
|
|
454
|
+
return {
|
|
455
|
+
text: content,
|
|
456
|
+
finishReason: FINISH_REASON_MAP[finishStr] ?? "unknown",
|
|
457
|
+
usage: {
|
|
458
|
+
promptTokens: readNumber(body, "usage", "prompt_tokens"),
|
|
459
|
+
completionTokens: readNumber(body, "usage", "completion_tokens"),
|
|
460
|
+
totalTokens: readNumber(body, "usage", "total_tokens")
|
|
461
|
+
},
|
|
462
|
+
raw: body
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
parseError(httpStatus, body, fallbackText) {
|
|
466
|
+
return new Error(defaultErrorMessage(httpStatus, body, fallbackText));
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
// src/modules/llm/adapters/anthropic.adapter.ts
|
|
471
|
+
var STOP_REASON_MAP = {
|
|
472
|
+
end_turn: "stop",
|
|
473
|
+
stop_sequence: "stop",
|
|
474
|
+
max_tokens: "length",
|
|
475
|
+
tool_use: "tool_calls",
|
|
476
|
+
refusal: "content_filter"
|
|
477
|
+
};
|
|
478
|
+
var ANTHROPIC_VERSION = "2023-06-01";
|
|
479
|
+
var AnthropicAdapter = class {
|
|
480
|
+
static {
|
|
481
|
+
__name(this, "AnthropicAdapter");
|
|
482
|
+
}
|
|
483
|
+
providerId = "anthropic";
|
|
484
|
+
buildRequest(req) {
|
|
485
|
+
const entry = CLOUD_PROVIDERS_BY_ID.get("anthropic");
|
|
486
|
+
if (!entry) throw new Error("anthropic provider not registered");
|
|
487
|
+
const body = {
|
|
488
|
+
model: req.model,
|
|
489
|
+
max_tokens: req.hyperparams?.maxTokens ?? 4096,
|
|
490
|
+
messages: [
|
|
491
|
+
{
|
|
492
|
+
role: "user",
|
|
493
|
+
content: req.prompt
|
|
494
|
+
}
|
|
495
|
+
]
|
|
496
|
+
};
|
|
497
|
+
if (req.hyperparams?.temperature !== void 0) body.temperature = req.hyperparams.temperature;
|
|
498
|
+
return {
|
|
499
|
+
url: entry.endpoint,
|
|
500
|
+
init: {
|
|
501
|
+
method: "POST",
|
|
502
|
+
headers: {
|
|
503
|
+
"Content-Type": "application/json",
|
|
504
|
+
"x-api-key": req.apiKey,
|
|
505
|
+
"anthropic-version": ANTHROPIC_VERSION,
|
|
506
|
+
// Required when the request runs from a browser-like environment;
|
|
507
|
+
// Tauri / Electron embedded fetch occasionally tags itself as such.
|
|
508
|
+
"anthropic-dangerous-direct-browser-access": "true"
|
|
509
|
+
},
|
|
510
|
+
body: JSON.stringify(body)
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
parseResponse(_httpStatus, body) {
|
|
515
|
+
const blocks = readArray(body, "content");
|
|
516
|
+
if (blocks.length === 0) {
|
|
517
|
+
throw new Error("Anthropic response had no content[] blocks");
|
|
518
|
+
}
|
|
519
|
+
const textParts = [];
|
|
520
|
+
for (const block of blocks) {
|
|
521
|
+
const type = readString(block, "type");
|
|
522
|
+
if (type === "text") {
|
|
523
|
+
const t = readString(block, "text");
|
|
524
|
+
if (t) textParts.push(t);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
if (textParts.length === 0) {
|
|
528
|
+
const stop = readString(body, "stop_reason") ?? "unknown";
|
|
529
|
+
throw new Error(`Anthropic returned no text blocks (stop_reason=${stop}); tool_use / thinking-only responses are not consumable by this node`);
|
|
530
|
+
}
|
|
531
|
+
const stopStr = readString(body, "stop_reason") ?? "end_turn";
|
|
532
|
+
return {
|
|
533
|
+
text: textParts.join(""),
|
|
534
|
+
finishReason: STOP_REASON_MAP[stopStr] ?? "unknown",
|
|
535
|
+
usage: {
|
|
536
|
+
promptTokens: readNumber(body, "usage", "input_tokens"),
|
|
537
|
+
completionTokens: readNumber(body, "usage", "output_tokens"),
|
|
538
|
+
// Anthropic doesn't publish a total — it's input + output and we
|
|
539
|
+
// expose it pre-computed for parity with OpenAI usage telemetry.
|
|
540
|
+
totalTokens: (readNumber(body, "usage", "input_tokens") ?? 0) + (readNumber(body, "usage", "output_tokens") ?? 0) || void 0
|
|
541
|
+
},
|
|
542
|
+
raw: body
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
parseError(httpStatus, body, fallbackText) {
|
|
546
|
+
const msg = readString(body, "error", "message") ?? readString(body, "message");
|
|
547
|
+
if (msg) return new Error(`HTTP ${httpStatus}: ${msg}`);
|
|
548
|
+
return new Error(defaultErrorMessage(httpStatus, body, fallbackText));
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
// src/modules/llm/adapters/google.adapter.ts
|
|
553
|
+
var FINISH_REASON_MAP2 = {
|
|
554
|
+
STOP: "stop",
|
|
555
|
+
MAX_TOKENS: "length",
|
|
556
|
+
SAFETY: "content_filter",
|
|
557
|
+
RECITATION: "content_filter",
|
|
558
|
+
PROHIBITED_CONTENT: "content_filter",
|
|
559
|
+
SPII: "content_filter",
|
|
560
|
+
BLOCKLIST: "content_filter",
|
|
561
|
+
LANGUAGE: "content_filter",
|
|
562
|
+
OTHER: "unknown",
|
|
563
|
+
TOOL_CODE_FAILURE: "tool_calls"
|
|
564
|
+
};
|
|
565
|
+
var GoogleAdapter = class {
|
|
566
|
+
static {
|
|
567
|
+
__name(this, "GoogleAdapter");
|
|
568
|
+
}
|
|
569
|
+
providerId = "google";
|
|
570
|
+
buildRequest(req) {
|
|
571
|
+
const entry = CLOUD_PROVIDERS_BY_ID.get("google");
|
|
572
|
+
if (!entry) throw new Error("google provider not registered");
|
|
573
|
+
const url = entry.endpoint.replace("{model}", encodeURIComponent(req.model));
|
|
574
|
+
const generationConfig = {};
|
|
575
|
+
if (req.hyperparams?.temperature !== void 0) {
|
|
576
|
+
generationConfig.temperature = req.hyperparams.temperature;
|
|
577
|
+
}
|
|
578
|
+
if (req.hyperparams?.maxTokens !== void 0) {
|
|
579
|
+
generationConfig.maxOutputTokens = req.hyperparams.maxTokens;
|
|
580
|
+
}
|
|
581
|
+
if (req.hyperparams?.forceJson) {
|
|
582
|
+
generationConfig.responseMimeType = "application/json";
|
|
583
|
+
}
|
|
584
|
+
const body = {
|
|
585
|
+
contents: [
|
|
586
|
+
{
|
|
587
|
+
role: "user",
|
|
588
|
+
parts: [
|
|
589
|
+
{
|
|
590
|
+
text: req.prompt
|
|
591
|
+
}
|
|
592
|
+
]
|
|
593
|
+
}
|
|
594
|
+
]
|
|
595
|
+
};
|
|
596
|
+
if (Object.keys(generationConfig).length > 0) body.generationConfig = generationConfig;
|
|
597
|
+
return {
|
|
598
|
+
url,
|
|
599
|
+
init: {
|
|
600
|
+
method: "POST",
|
|
601
|
+
headers: {
|
|
602
|
+
"Content-Type": "application/json",
|
|
603
|
+
"x-goog-api-key": req.apiKey
|
|
604
|
+
},
|
|
605
|
+
body: JSON.stringify(body)
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
parseResponse(_httpStatus, body) {
|
|
610
|
+
const blockReason = readString(body, "promptFeedback", "blockReason");
|
|
611
|
+
if (blockReason) {
|
|
612
|
+
throw new Error(`Google blocked the prompt: blockReason=${blockReason}`);
|
|
613
|
+
}
|
|
614
|
+
const candidates = readArray(body, "candidates");
|
|
615
|
+
if (candidates.length === 0) {
|
|
616
|
+
throw new Error("Google response had no candidates[]");
|
|
617
|
+
}
|
|
618
|
+
const candidate = candidates[0];
|
|
619
|
+
const finishStr = readString(candidate, "finishReason") ?? "STOP";
|
|
620
|
+
const parts = readArray(candidate, "content", "parts");
|
|
621
|
+
const textParts = [];
|
|
622
|
+
for (const part of parts) {
|
|
623
|
+
if (readString(part, "functionCall", "name")) continue;
|
|
624
|
+
const isThought = part?.thought === true;
|
|
625
|
+
if (isThought) continue;
|
|
626
|
+
const t = readString(part, "text");
|
|
627
|
+
if (t) textParts.push(t);
|
|
628
|
+
}
|
|
629
|
+
if (textParts.length === 0) {
|
|
630
|
+
throw new Error(`Google returned no text parts (finishReason=${finishStr}); function-call / thought-only responses are not consumable by this node`);
|
|
631
|
+
}
|
|
632
|
+
return {
|
|
633
|
+
text: textParts.join(""),
|
|
634
|
+
finishReason: FINISH_REASON_MAP2[finishStr] ?? "unknown",
|
|
635
|
+
usage: {
|
|
636
|
+
promptTokens: readNumber(body, "usageMetadata", "promptTokenCount"),
|
|
637
|
+
completionTokens: readNumber(body, "usageMetadata", "candidatesTokenCount"),
|
|
638
|
+
totalTokens: readNumber(body, "usageMetadata", "totalTokenCount")
|
|
639
|
+
},
|
|
640
|
+
raw: body
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
parseError(httpStatus, body, fallbackText) {
|
|
644
|
+
const msg = readString(body, "error", "message");
|
|
645
|
+
if (msg) return new Error(`HTTP ${httpStatus}: ${msg}`);
|
|
646
|
+
return new Error(defaultErrorMessage(httpStatus, body, fallbackText));
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
|
|
650
|
+
// src/modules/llm/adapters/openai-compat.adapter.ts
|
|
651
|
+
var FINISH_REASON_MAP3 = {
|
|
652
|
+
stop: "stop",
|
|
653
|
+
length: "length",
|
|
654
|
+
content_filter: "content_filter",
|
|
655
|
+
tool_calls: "tool_calls",
|
|
656
|
+
function_call: "tool_calls"
|
|
657
|
+
};
|
|
658
|
+
var OpenAICompatAdapter = class {
|
|
659
|
+
static {
|
|
660
|
+
__name(this, "OpenAICompatAdapter");
|
|
661
|
+
}
|
|
662
|
+
/** Subclasses can extend if they need extra Authorization shapes. */
|
|
663
|
+
buildHeaders(req) {
|
|
664
|
+
return {
|
|
665
|
+
"Content-Type": "application/json",
|
|
666
|
+
"Authorization": `Bearer ${req.apiKey}`
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
buildRequest(req) {
|
|
670
|
+
const body = {
|
|
671
|
+
model: req.model,
|
|
672
|
+
messages: [
|
|
673
|
+
{
|
|
674
|
+
role: "user",
|
|
675
|
+
content: req.prompt
|
|
676
|
+
}
|
|
677
|
+
]
|
|
678
|
+
};
|
|
679
|
+
if (req.hyperparams?.temperature !== void 0) body.temperature = req.hyperparams.temperature;
|
|
680
|
+
if (req.hyperparams?.maxTokens !== void 0) body.max_tokens = req.hyperparams.maxTokens;
|
|
681
|
+
if (req.hyperparams?.forceJson) body.response_format = {
|
|
682
|
+
type: "json_object"
|
|
683
|
+
};
|
|
684
|
+
return {
|
|
685
|
+
url: this.endpoint,
|
|
686
|
+
init: {
|
|
687
|
+
method: "POST",
|
|
688
|
+
headers: this.buildHeaders(req),
|
|
689
|
+
body: JSON.stringify(body)
|
|
690
|
+
}
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
parseResponse(_httpStatus, body) {
|
|
694
|
+
if (this.validateResponseBody) this.validateResponseBody(body);
|
|
695
|
+
const choices = readArray(body, "choices");
|
|
696
|
+
if (choices.length === 0) {
|
|
697
|
+
throw new Error(`${this.providerId} response had no choices[]`);
|
|
698
|
+
}
|
|
699
|
+
const choice = choices[0];
|
|
700
|
+
const content = readString(choice, "message", "content");
|
|
701
|
+
if (typeof content !== "string" || content.length === 0) {
|
|
702
|
+
const finish = readString(choice, "finish_reason") ?? "unknown";
|
|
703
|
+
throw new Error(`${this.providerId} returned no message.content (finish_reason=${finish})`);
|
|
704
|
+
}
|
|
705
|
+
const finishStr = readString(choice, "finish_reason") ?? "stop";
|
|
706
|
+
return {
|
|
707
|
+
text: content,
|
|
708
|
+
finishReason: FINISH_REASON_MAP3[finishStr] ?? "unknown",
|
|
709
|
+
usage: {
|
|
710
|
+
promptTokens: readNumber(body, "usage", "prompt_tokens"),
|
|
711
|
+
completionTokens: readNumber(body, "usage", "completion_tokens"),
|
|
712
|
+
totalTokens: readNumber(body, "usage", "total_tokens")
|
|
713
|
+
},
|
|
714
|
+
raw: body
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
parseError(httpStatus, body, fallbackText) {
|
|
718
|
+
return new Error(defaultErrorMessage(httpStatus, body, fallbackText));
|
|
719
|
+
}
|
|
720
|
+
};
|
|
721
|
+
|
|
722
|
+
// src/modules/llm/adapters/moonshot.adapter.ts
|
|
723
|
+
var MoonshotAdapter = class extends OpenAICompatAdapter {
|
|
724
|
+
static {
|
|
725
|
+
__name(this, "MoonshotAdapter");
|
|
726
|
+
}
|
|
727
|
+
providerId = "moonshot";
|
|
728
|
+
endpoint = (() => {
|
|
729
|
+
const e = CLOUD_PROVIDERS_BY_ID.get("moonshot")?.endpoint;
|
|
730
|
+
if (!e) throw new Error("moonshot provider missing from CLOUD_PROVIDERS table");
|
|
731
|
+
return e;
|
|
732
|
+
})();
|
|
733
|
+
};
|
|
734
|
+
|
|
735
|
+
// src/modules/llm/adapters/minimax.adapter.ts
|
|
736
|
+
var MINIMAX_TRANSIENT_CODES = /* @__PURE__ */ new Set([
|
|
737
|
+
1002,
|
|
738
|
+
1004,
|
|
739
|
+
1027,
|
|
740
|
+
2013,
|
|
741
|
+
2064
|
|
742
|
+
]);
|
|
743
|
+
var MinimaxAdapter = class extends OpenAICompatAdapter {
|
|
744
|
+
static {
|
|
745
|
+
__name(this, "MinimaxAdapter");
|
|
746
|
+
}
|
|
747
|
+
providerId = "minimax";
|
|
748
|
+
endpoint = (() => {
|
|
749
|
+
const e = CLOUD_PROVIDERS_BY_ID.get("minimax")?.endpoint;
|
|
750
|
+
if (!e) throw new Error("minimax provider missing from CLOUD_PROVIDERS table");
|
|
751
|
+
return e;
|
|
752
|
+
})();
|
|
753
|
+
validateResponseBody(body) {
|
|
754
|
+
const code = readNumber(body, "base_resp", "status_code");
|
|
755
|
+
if (code !== void 0 && code !== 0) {
|
|
756
|
+
const msg = readString(body, "base_resp", "status_msg") ?? "unknown";
|
|
757
|
+
const transient = MINIMAX_TRANSIENT_CODES.has(code);
|
|
758
|
+
throw new Error(`MiniMax base_resp ${code}: ${msg}` + (transient ? " (transient)" : ""));
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
isTransientError(err) {
|
|
762
|
+
const msg = String(err?.message ?? err ?? "");
|
|
763
|
+
if (msg.includes("(transient)")) return true;
|
|
764
|
+
for (const code of MINIMAX_TRANSIENT_CODES) {
|
|
765
|
+
if (msg.includes(`base_resp ${code}`)) return true;
|
|
766
|
+
}
|
|
767
|
+
return false;
|
|
768
|
+
}
|
|
769
|
+
};
|
|
770
|
+
|
|
771
|
+
// src/modules/llm/adapters/zhipu.adapter.ts
|
|
772
|
+
var ZhipuAdapter = class extends OpenAICompatAdapter {
|
|
773
|
+
static {
|
|
774
|
+
__name(this, "ZhipuAdapter");
|
|
775
|
+
}
|
|
776
|
+
providerId = "zhipu";
|
|
777
|
+
endpoint = (() => {
|
|
778
|
+
const e = CLOUD_PROVIDERS_BY_ID.get("zhipu")?.endpoint;
|
|
779
|
+
if (!e) throw new Error("zhipu provider missing from CLOUD_PROVIDERS table");
|
|
780
|
+
return e;
|
|
781
|
+
})();
|
|
782
|
+
};
|
|
783
|
+
|
|
784
|
+
// src/modules/llm/adapters/index.ts
|
|
785
|
+
var ADAPTERS = /* @__PURE__ */ new Map([
|
|
786
|
+
[
|
|
787
|
+
"openai",
|
|
788
|
+
new OpenAIAdapter()
|
|
789
|
+
],
|
|
790
|
+
[
|
|
791
|
+
"anthropic",
|
|
792
|
+
new AnthropicAdapter()
|
|
793
|
+
],
|
|
794
|
+
[
|
|
795
|
+
"google",
|
|
796
|
+
new GoogleAdapter()
|
|
797
|
+
],
|
|
798
|
+
[
|
|
799
|
+
"moonshot",
|
|
800
|
+
new MoonshotAdapter()
|
|
801
|
+
],
|
|
802
|
+
[
|
|
803
|
+
"minimax",
|
|
804
|
+
new MinimaxAdapter()
|
|
805
|
+
],
|
|
806
|
+
[
|
|
807
|
+
"zhipu",
|
|
808
|
+
new ZhipuAdapter()
|
|
809
|
+
]
|
|
810
|
+
]);
|
|
811
|
+
function getAdapter(providerId) {
|
|
812
|
+
const adapter = ADAPTERS.get(providerId);
|
|
813
|
+
if (!adapter) {
|
|
814
|
+
throw new Error(`No LLM response adapter registered for provider '${providerId}'`);
|
|
815
|
+
}
|
|
816
|
+
return adapter;
|
|
817
|
+
}
|
|
818
|
+
__name(getAdapter, "getAdapter");
|
|
819
|
+
|
|
820
|
+
// src/modules/llm/llm-provider.ts
|
|
821
|
+
function _ts_decorate2(decorators, target, key, desc) {
|
|
822
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
823
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
824
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
825
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
826
|
+
}
|
|
827
|
+
__name(_ts_decorate2, "_ts_decorate");
|
|
828
|
+
function _ts_metadata2(k, v) {
|
|
829
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
830
|
+
}
|
|
831
|
+
__name(_ts_metadata2, "_ts_metadata");
|
|
832
|
+
function _ts_param(paramIndex, decorator) {
|
|
833
|
+
return function(target, key) {
|
|
834
|
+
decorator(target, key, paramIndex);
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
__name(_ts_param, "_ts_param");
|
|
838
|
+
function buildSupportedModels() {
|
|
839
|
+
const out = {};
|
|
840
|
+
for (const entry of CLOUD_PROVIDERS) {
|
|
841
|
+
for (const tier of [
|
|
842
|
+
"top",
|
|
843
|
+
"mid",
|
|
844
|
+
"budget"
|
|
845
|
+
]) {
|
|
846
|
+
const desc = entry.models[tier];
|
|
847
|
+
out[`${entry.id}/${desc.modelId}`] = {
|
|
848
|
+
provider: "cloud",
|
|
849
|
+
providerId: entry.id,
|
|
850
|
+
modelId: desc.modelId
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
for (const m of OLLAMA_DEFAULT_MODELS) {
|
|
855
|
+
out[`ollama/${m.modelId}`] = {
|
|
856
|
+
provider: "ollama",
|
|
857
|
+
providerId: "",
|
|
858
|
+
modelId: m.modelId
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
return out;
|
|
862
|
+
}
|
|
863
|
+
__name(buildSupportedModels, "buildSupportedModels");
|
|
864
|
+
function buildModelMetadata() {
|
|
865
|
+
const out = {};
|
|
866
|
+
for (const entry of CLOUD_PROVIDERS) {
|
|
867
|
+
for (const tier of [
|
|
868
|
+
"top",
|
|
869
|
+
"mid",
|
|
870
|
+
"budget"
|
|
871
|
+
]) {
|
|
872
|
+
const d = entry.models[tier];
|
|
873
|
+
out[d.modelId] = d;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
for (const m of OLLAMA_DEFAULT_MODELS) {
|
|
877
|
+
out[m.modelId] = m;
|
|
878
|
+
}
|
|
879
|
+
return out;
|
|
880
|
+
}
|
|
881
|
+
__name(buildModelMetadata, "buildModelMetadata");
|
|
882
|
+
var SUPPORTED_MODELS = buildSupportedModels();
|
|
883
|
+
var MODEL_METADATA = buildModelMetadata();
|
|
884
|
+
function isTransientLlmError(err) {
|
|
885
|
+
const msg = String(err?.message ?? err ?? "").toLowerCase();
|
|
886
|
+
return msg.includes("2064") || msg.includes("high load") || msg.includes("overloaded") || msg.includes("rate limit") || msg.includes("rate-limit") || msg.includes("too many requests") || msg.includes("429") || msg.includes("503") || msg.includes("502") || msg.includes("504") || msg.includes("timeout") || msg.includes("econn") || msg.includes("etimedout") || msg.includes("enotfound") || msg.includes("socket hang up") || msg.includes("runner process") || msg.includes("%!w") || msg.includes("unexpected eof") || msg.includes("try again") || msg.includes("(transient)");
|
|
887
|
+
}
|
|
888
|
+
__name(isTransientLlmError, "isTransientLlmError");
|
|
889
|
+
var RETRY_SCHEDULE_MS = [
|
|
890
|
+
1e3,
|
|
891
|
+
3e3,
|
|
892
|
+
8e3
|
|
893
|
+
];
|
|
894
|
+
var LlmProviderHelper = class {
|
|
895
|
+
static {
|
|
896
|
+
__name(this, "LlmProviderHelper");
|
|
897
|
+
}
|
|
898
|
+
synapseia;
|
|
899
|
+
ollamaHelper = new OllamaHelper();
|
|
900
|
+
// F3-C7 — optional Synapseia client. When Nest wires it, any model
|
|
901
|
+
// with `provider: 'synapseia'` is dispatched through it; otherwise we
|
|
902
|
+
// fall back to cloud/ollama so the node keeps serving even pre-F3.
|
|
903
|
+
constructor(synapseia) {
|
|
904
|
+
this.synapseia = synapseia;
|
|
905
|
+
}
|
|
906
|
+
// ── Public methods ────────────────────────────────────────────────────────
|
|
907
|
+
toErrorMessage(error) {
|
|
908
|
+
try {
|
|
909
|
+
return String(error?.message ?? "Unknown error");
|
|
910
|
+
} catch {
|
|
911
|
+
return "Unknown error";
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
/**
|
|
915
|
+
* Resolve a slug against the whitelist. Returns null for anything we
|
|
916
|
+
* don't recognise so callers (CLI, config validation) can decide
|
|
917
|
+
* whether to migrate or hard-fail.
|
|
918
|
+
*/
|
|
919
|
+
parseModel(modelStr) {
|
|
920
|
+
const known = SUPPORTED_MODELS[modelStr];
|
|
921
|
+
if (known) return known;
|
|
922
|
+
const slash = modelStr.indexOf("/");
|
|
923
|
+
if (slash <= 0) return null;
|
|
924
|
+
const provider = modelStr.slice(0, slash);
|
|
925
|
+
const modelId = modelStr.slice(slash + 1);
|
|
926
|
+
if (!modelId) return null;
|
|
927
|
+
if (provider === "ollama") {
|
|
928
|
+
return {
|
|
929
|
+
provider: "ollama",
|
|
930
|
+
providerId: "",
|
|
931
|
+
modelId
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
if (provider === "synapseia") {
|
|
935
|
+
return {
|
|
936
|
+
provider: "synapseia",
|
|
937
|
+
providerId: "",
|
|
938
|
+
modelId
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
if (CLOUD_PROVIDERS_BY_ID.has(provider)) {
|
|
942
|
+
return {
|
|
943
|
+
provider: "cloud",
|
|
944
|
+
providerId: provider,
|
|
945
|
+
modelId
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
return null;
|
|
949
|
+
}
|
|
950
|
+
async checkLLM(model, config) {
|
|
951
|
+
if (model.provider === "ollama") return this.checkOllamaLLM(model);
|
|
952
|
+
if (model.provider === "cloud") return this.checkCloudLLM(model, config);
|
|
953
|
+
if (model.provider === "synapseia") return this.checkSynapseiaLLM(model);
|
|
954
|
+
return {
|
|
955
|
+
available: false,
|
|
956
|
+
model,
|
|
957
|
+
estimatedLatencyMs: 0,
|
|
958
|
+
error: "Unknown provider"
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
async generateLLM(model, prompt, config, hyperparams) {
|
|
962
|
+
let raw;
|
|
963
|
+
let lastErr;
|
|
964
|
+
for (let attempt = 0; attempt <= RETRY_SCHEDULE_MS.length; attempt++) {
|
|
965
|
+
try {
|
|
966
|
+
if (model.provider === "ollama") {
|
|
967
|
+
raw = await this.generateOllamaLLM(model, prompt, hyperparams);
|
|
968
|
+
} else if (model.provider === "cloud") {
|
|
969
|
+
raw = await this.generateCloudLLM(model, prompt, config, hyperparams);
|
|
970
|
+
} else if (model.provider === "synapseia") {
|
|
971
|
+
raw = await this.generateSynapseiaLLM(model, prompt, hyperparams);
|
|
972
|
+
} else {
|
|
973
|
+
throw new Error("Unknown provider");
|
|
974
|
+
}
|
|
975
|
+
break;
|
|
976
|
+
} catch (err) {
|
|
977
|
+
lastErr = err;
|
|
978
|
+
const adapterTransient = model.provider === "cloud" && model.providerId ? this.adapterIsTransient(model.providerId, err) : false;
|
|
979
|
+
if (attempt >= RETRY_SCHEDULE_MS.length || !isTransientLlmError(err) && !adapterTransient) {
|
|
980
|
+
throw err;
|
|
981
|
+
}
|
|
982
|
+
const wait = RETRY_SCHEDULE_MS[attempt];
|
|
983
|
+
logger_default.warn(`[LLM] transient error on attempt ${attempt + 1}/${RETRY_SCHEDULE_MS.length + 1} (${this.toErrorMessage(err)}) \u2014 retrying in ${wait}ms`);
|
|
984
|
+
await new Promise((resolve) => setTimeout(resolve, wait));
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
if (raw === void 0) throw lastErr ?? new Error("LLM generation failed");
|
|
988
|
+
return stripReasoning(raw);
|
|
989
|
+
}
|
|
990
|
+
checkOllama() {
|
|
991
|
+
return this.ollamaHelper.checkOllama();
|
|
992
|
+
}
|
|
993
|
+
generateOllama(prompt, modelId) {
|
|
994
|
+
return this.ollamaHelper.generate(prompt, modelId);
|
|
995
|
+
}
|
|
996
|
+
get supportedModels() {
|
|
997
|
+
return SUPPORTED_MODELS;
|
|
998
|
+
}
|
|
999
|
+
get modelMetadata() {
|
|
1000
|
+
return MODEL_METADATA;
|
|
1001
|
+
}
|
|
1002
|
+
// ── Private: Ollama ───────────────────────────────────────────────────────
|
|
1003
|
+
async checkOllamaLLM(model) {
|
|
1004
|
+
try {
|
|
1005
|
+
const status = await this.ollamaHelper.checkOllama();
|
|
1006
|
+
if (!status.available) {
|
|
1007
|
+
return {
|
|
1008
|
+
available: false,
|
|
1009
|
+
model,
|
|
1010
|
+
estimatedLatencyMs: 0,
|
|
1011
|
+
error: status.error || "Ollama not available"
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
1014
|
+
const meta = MODEL_METADATA[model.modelId];
|
|
1015
|
+
const modelAvailable = status.models.includes(model.modelId);
|
|
1016
|
+
if (!modelAvailable) {
|
|
1017
|
+
return {
|
|
1018
|
+
available: false,
|
|
1019
|
+
model,
|
|
1020
|
+
estimatedLatencyMs: meta?.latencyMs ?? 500,
|
|
1021
|
+
error: `Model ${model.modelId} not found. Pull with: ollama pull ${model.modelId}`
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
return {
|
|
1025
|
+
available: true,
|
|
1026
|
+
model,
|
|
1027
|
+
estimatedLatencyMs: meta?.latencyMs ?? 500,
|
|
1028
|
+
maxTokens: meta?.maxTokens
|
|
1029
|
+
};
|
|
1030
|
+
} catch (error) {
|
|
1031
|
+
return {
|
|
1032
|
+
available: false,
|
|
1033
|
+
model,
|
|
1034
|
+
estimatedLatencyMs: 0,
|
|
1035
|
+
error: this.toErrorMessage(error)
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
async generateOllamaLLM(model, prompt, hyperparams) {
|
|
1040
|
+
return this.ollamaHelper.generate(prompt, model.modelId, void 0, hyperparams);
|
|
1041
|
+
}
|
|
1042
|
+
// ── Private: Cloud routing ────────────────────────────────────────────────
|
|
1043
|
+
async checkCloudLLM(model, config) {
|
|
1044
|
+
if (!config?.apiKey) {
|
|
1045
|
+
return {
|
|
1046
|
+
available: false,
|
|
1047
|
+
model,
|
|
1048
|
+
estimatedLatencyMs: 0,
|
|
1049
|
+
error: "API key required for cloud provider"
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
if (config.baseUrl) {
|
|
1053
|
+
const safeUrl = config.baseUrl.split("?")[0];
|
|
1054
|
+
logger_default.warn(`[LLM] config.baseUrl is set ('${safeUrl}') but is ignored \u2014 endpoints are hardcoded per provider`);
|
|
1055
|
+
}
|
|
1056
|
+
if (!model.providerId || !CLOUD_PROVIDERS_BY_ID.has(model.providerId)) {
|
|
1057
|
+
return {
|
|
1058
|
+
available: false,
|
|
1059
|
+
model,
|
|
1060
|
+
estimatedLatencyMs: 0,
|
|
1061
|
+
error: `Unknown cloud provider '${model.providerId}'`
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
const meta = MODEL_METADATA[model.modelId];
|
|
1065
|
+
try {
|
|
1066
|
+
await this.runAdapterRequest(model, "Hi", config.apiKey, {
|
|
1067
|
+
maxTokens: 16
|
|
1068
|
+
});
|
|
1069
|
+
return {
|
|
1070
|
+
available: true,
|
|
1071
|
+
model,
|
|
1072
|
+
estimatedLatencyMs: meta?.latencyMs ?? 400,
|
|
1073
|
+
estimatedCostPerCall: meta?.costPerCall,
|
|
1074
|
+
maxTokens: meta?.maxTokens
|
|
1075
|
+
};
|
|
1076
|
+
} catch (error) {
|
|
1077
|
+
return {
|
|
1078
|
+
available: false,
|
|
1079
|
+
model,
|
|
1080
|
+
estimatedLatencyMs: 0,
|
|
1081
|
+
error: this.toErrorMessage(error)
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
async generateCloudLLM(model, prompt, config, hyperparams) {
|
|
1086
|
+
if (!config?.apiKey) throw new Error("API key required for cloud provider");
|
|
1087
|
+
if (!model.providerId || !CLOUD_PROVIDERS_BY_ID.has(model.providerId)) {
|
|
1088
|
+
throw new Error(`Unknown cloud provider '${model.providerId}'`);
|
|
1089
|
+
}
|
|
1090
|
+
if (config.baseUrl) {
|
|
1091
|
+
const safeUrl = config.baseUrl.split("?")[0];
|
|
1092
|
+
logger_default.warn(`[LLM] config.baseUrl is set ('${safeUrl}') but is ignored \u2014 endpoints are hardcoded per provider`);
|
|
1093
|
+
}
|
|
1094
|
+
return this.runAdapterRequest(model, prompt, config.apiKey, hyperparams);
|
|
1095
|
+
}
|
|
1096
|
+
async runAdapterRequest(model, prompt, apiKey, hyperparams) {
|
|
1097
|
+
const adapter = getAdapter(model.providerId);
|
|
1098
|
+
const chatReq = {
|
|
1099
|
+
model: model.modelId,
|
|
1100
|
+
prompt,
|
|
1101
|
+
apiKey,
|
|
1102
|
+
hyperparams: hyperparams ? {
|
|
1103
|
+
temperature: hyperparams.temperature,
|
|
1104
|
+
maxTokens: hyperparams.maxTokens,
|
|
1105
|
+
forceJson: hyperparams.forceJson
|
|
1106
|
+
} : void 0
|
|
1107
|
+
};
|
|
1108
|
+
const { url, init } = adapter.buildRequest(chatReq);
|
|
1109
|
+
const response = await fetch(url, init);
|
|
1110
|
+
const text = await response.text().catch(() => "");
|
|
1111
|
+
let body = null;
|
|
1112
|
+
if (text) {
|
|
1113
|
+
try {
|
|
1114
|
+
body = JSON.parse(text);
|
|
1115
|
+
} catch {
|
|
1116
|
+
body = null;
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
if (!response.ok) {
|
|
1120
|
+
throw adapter.parseError(response.status, body, text);
|
|
1121
|
+
}
|
|
1122
|
+
const normalized = adapter.parseResponse(response.status, body);
|
|
1123
|
+
return normalized.text;
|
|
1124
|
+
}
|
|
1125
|
+
adapterIsTransient(providerId, err) {
|
|
1126
|
+
try {
|
|
1127
|
+
const adapter = getAdapter(providerId);
|
|
1128
|
+
return Boolean(adapter.isTransientError?.(err));
|
|
1129
|
+
} catch {
|
|
1130
|
+
return false;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
// ── Private: Synapseia (F3-C7) ────────────────────────────────────────────
|
|
1134
|
+
async checkSynapseiaLLM(model) {
|
|
1135
|
+
if (!this.synapseia) {
|
|
1136
|
+
return {
|
|
1137
|
+
available: false,
|
|
1138
|
+
model,
|
|
1139
|
+
estimatedLatencyMs: 0,
|
|
1140
|
+
error: "Synapseia client not wired"
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
const ok = await this.synapseia.isAvailable();
|
|
1144
|
+
return {
|
|
1145
|
+
available: ok,
|
|
1146
|
+
model,
|
|
1147
|
+
estimatedLatencyMs: 600,
|
|
1148
|
+
error: ok ? void 0 : "local serving runtime not reachable"
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
async generateSynapseiaLLM(model, prompt, hyperparams) {
|
|
1152
|
+
if (!this.synapseia) {
|
|
1153
|
+
throw new Error("Synapseia client not wired \u2014 operator must launch llama.cpp + register swap hook");
|
|
1154
|
+
}
|
|
1155
|
+
const expected = model.synapseiaVersion;
|
|
1156
|
+
const active = this.synapseia.getActiveVersion();
|
|
1157
|
+
if (expected && active && expected !== active) {
|
|
1158
|
+
throw new Error(`Synapseia version mismatch: caller asked ${expected}, node is serving ${active}`);
|
|
1159
|
+
}
|
|
1160
|
+
const result = await this.synapseia.generate({
|
|
1161
|
+
messages: [
|
|
1162
|
+
{
|
|
1163
|
+
role: "user",
|
|
1164
|
+
content: prompt
|
|
1165
|
+
}
|
|
1166
|
+
],
|
|
1167
|
+
temperature: hyperparams?.temperature,
|
|
1168
|
+
maxTokens: hyperparams?.maxTokens
|
|
1169
|
+
});
|
|
1170
|
+
return result.content;
|
|
1171
|
+
}
|
|
1172
|
+
};
|
|
1173
|
+
LlmProviderHelper = _ts_decorate2([
|
|
1174
|
+
Injectable2(),
|
|
1175
|
+
_ts_param(0, Optional()),
|
|
1176
|
+
_ts_metadata2("design:type", Function),
|
|
1177
|
+
_ts_metadata2("design:paramtypes", [
|
|
1178
|
+
typeof SynapseiaServingClient === "undefined" ? Object : SynapseiaServingClient
|
|
1179
|
+
])
|
|
1180
|
+
], LlmProviderHelper);
|
|
1181
|
+
|
|
1182
|
+
// src/modules/p2p/stream-codec.ts
|
|
1183
|
+
var MAX_FRAME_BYTES = 1 << 20;
|
|
1184
|
+
async function sendJsonOverStream(stream, obj) {
|
|
1185
|
+
const json = new TextEncoder().encode(JSON.stringify(obj));
|
|
1186
|
+
if (json.byteLength > MAX_FRAME_BYTES) {
|
|
1187
|
+
throw new Error(`stream-codec: payload ${json.byteLength} > ${MAX_FRAME_BYTES}`);
|
|
1188
|
+
}
|
|
1189
|
+
const full = new Uint8Array(4 + json.byteLength);
|
|
1190
|
+
new DataView(full.buffer, full.byteOffset, 4).setUint32(0, json.byteLength, true);
|
|
1191
|
+
full.set(json, 4);
|
|
1192
|
+
const ok = stream.send(full);
|
|
1193
|
+
if (!ok) {
|
|
1194
|
+
await new Promise((resolve, reject) => {
|
|
1195
|
+
const onDrain = /* @__PURE__ */ __name(() => {
|
|
1196
|
+
cleanup();
|
|
1197
|
+
resolve();
|
|
1198
|
+
}, "onDrain");
|
|
1199
|
+
const onClose = /* @__PURE__ */ __name(() => {
|
|
1200
|
+
cleanup();
|
|
1201
|
+
reject(new Error("stream closed before drain"));
|
|
1202
|
+
}, "onClose");
|
|
1203
|
+
const cleanup = /* @__PURE__ */ __name(() => {
|
|
1204
|
+
stream.removeEventListener?.("drain", onDrain);
|
|
1205
|
+
stream.removeEventListener?.("close", onClose);
|
|
1206
|
+
stream.removeEventListener?.("remoteCloseRead", onClose);
|
|
1207
|
+
}, "cleanup");
|
|
1208
|
+
stream.addEventListener?.("drain", onDrain, {
|
|
1209
|
+
once: true
|
|
1210
|
+
});
|
|
1211
|
+
stream.addEventListener?.("close", onClose, {
|
|
1212
|
+
once: true
|
|
1213
|
+
});
|
|
1214
|
+
stream.addEventListener?.("remoteCloseRead", onClose, {
|
|
1215
|
+
once: true
|
|
1216
|
+
});
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
1219
|
+
try {
|
|
1220
|
+
await stream.closeWrite?.();
|
|
1221
|
+
} catch {
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
__name(sendJsonOverStream, "sendJsonOverStream");
|
|
1225
|
+
async function readJsonFromStream(stream) {
|
|
1226
|
+
const buffers = [];
|
|
1227
|
+
let total = 0;
|
|
1228
|
+
let expected = null;
|
|
1229
|
+
for await (const chunk of stream) {
|
|
1230
|
+
const bytes = chunk instanceof Uint8Array ? chunk : chunk.subarray();
|
|
1231
|
+
buffers.push(bytes);
|
|
1232
|
+
total += bytes.byteLength;
|
|
1233
|
+
if (expected === null && total >= 4) {
|
|
1234
|
+
const head = coalesceFirstN(buffers, 4);
|
|
1235
|
+
expected = new DataView(head.buffer, head.byteOffset, 4).getUint32(0, true);
|
|
1236
|
+
if (expected > MAX_FRAME_BYTES) {
|
|
1237
|
+
throw new Error(`stream-codec: frame length ${expected} > ${MAX_FRAME_BYTES}`);
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
if (expected !== null && total >= 4 + expected) break;
|
|
1241
|
+
}
|
|
1242
|
+
if (expected === null) throw new Error("stream-codec: stream ended before header");
|
|
1243
|
+
const all = coalesce(buffers);
|
|
1244
|
+
if (all.byteLength < 4 + expected) {
|
|
1245
|
+
throw new Error(`stream-codec: stream ended mid-frame (got ${all.byteLength - 4} of ${expected})`);
|
|
1246
|
+
}
|
|
1247
|
+
const payload = all.subarray(4, 4 + expected);
|
|
1248
|
+
return JSON.parse(new TextDecoder().decode(payload));
|
|
1249
|
+
}
|
|
1250
|
+
__name(readJsonFromStream, "readJsonFromStream");
|
|
1251
|
+
async function sendJsonFrame(stream, obj) {
|
|
1252
|
+
const json = new TextEncoder().encode(JSON.stringify(obj));
|
|
1253
|
+
if (json.byteLength > MAX_FRAME_BYTES) {
|
|
1254
|
+
throw new Error(`stream-codec: payload ${json.byteLength} > ${MAX_FRAME_BYTES}`);
|
|
1255
|
+
}
|
|
1256
|
+
const full = new Uint8Array(4 + json.byteLength);
|
|
1257
|
+
new DataView(full.buffer, full.byteOffset, 4).setUint32(0, json.byteLength, true);
|
|
1258
|
+
full.set(json, 4);
|
|
1259
|
+
const ok = stream.send(full);
|
|
1260
|
+
if (!ok) {
|
|
1261
|
+
await new Promise((resolve, reject) => {
|
|
1262
|
+
const onDrain = /* @__PURE__ */ __name(() => {
|
|
1263
|
+
cleanup();
|
|
1264
|
+
resolve();
|
|
1265
|
+
}, "onDrain");
|
|
1266
|
+
const onClose = /* @__PURE__ */ __name(() => {
|
|
1267
|
+
cleanup();
|
|
1268
|
+
reject(new Error("stream closed before drain"));
|
|
1269
|
+
}, "onClose");
|
|
1270
|
+
const cleanup = /* @__PURE__ */ __name(() => {
|
|
1271
|
+
stream.removeEventListener?.("drain", onDrain);
|
|
1272
|
+
stream.removeEventListener?.("close", onClose);
|
|
1273
|
+
stream.removeEventListener?.("remoteCloseRead", onClose);
|
|
1274
|
+
}, "cleanup");
|
|
1275
|
+
stream.addEventListener?.("drain", onDrain, {
|
|
1276
|
+
once: true
|
|
1277
|
+
});
|
|
1278
|
+
stream.addEventListener?.("close", onClose, {
|
|
1279
|
+
once: true
|
|
1280
|
+
});
|
|
1281
|
+
stream.addEventListener?.("remoteCloseRead", onClose, {
|
|
1282
|
+
once: true
|
|
1283
|
+
});
|
|
1284
|
+
});
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
__name(sendJsonFrame, "sendJsonFrame");
|
|
1288
|
+
async function endJsonStream(stream) {
|
|
1289
|
+
try {
|
|
1290
|
+
await stream.closeWrite?.();
|
|
1291
|
+
} catch {
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
__name(endJsonStream, "endJsonStream");
|
|
1295
|
+
async function readJsonFramesUntilDone(stream, onFrame, isDone, hardCapFrames = 5e6) {
|
|
1296
|
+
const buffers = [];
|
|
1297
|
+
let total = 0;
|
|
1298
|
+
let expected = null;
|
|
1299
|
+
let frames = 0;
|
|
1300
|
+
let lastDone = null;
|
|
1301
|
+
for await (const chunk of stream) {
|
|
1302
|
+
const bytes = chunk instanceof Uint8Array ? chunk : chunk.subarray();
|
|
1303
|
+
buffers.push(bytes);
|
|
1304
|
+
total += bytes.byteLength;
|
|
1305
|
+
while (true) {
|
|
1306
|
+
if (expected === null) {
|
|
1307
|
+
if (total < 4) break;
|
|
1308
|
+
const head = coalesceFirstN(buffers, 4);
|
|
1309
|
+
expected = new DataView(head.buffer, head.byteOffset, 4).getUint32(0, true);
|
|
1310
|
+
if (expected > MAX_FRAME_BYTES) {
|
|
1311
|
+
throw new Error(`stream-codec: frame length ${expected} > ${MAX_FRAME_BYTES}`);
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
if (total < 4 + expected) break;
|
|
1315
|
+
const all = coalesce(buffers);
|
|
1316
|
+
const payload = all.subarray(4, 4 + expected);
|
|
1317
|
+
const frame = JSON.parse(new TextDecoder().decode(payload));
|
|
1318
|
+
const remainder = all.subarray(4 + expected);
|
|
1319
|
+
buffers.length = 0;
|
|
1320
|
+
total = 0;
|
|
1321
|
+
if (remainder.byteLength > 0) {
|
|
1322
|
+
buffers.push(remainder);
|
|
1323
|
+
total = remainder.byteLength;
|
|
1324
|
+
}
|
|
1325
|
+
expected = null;
|
|
1326
|
+
frames++;
|
|
1327
|
+
if (frames > hardCapFrames) {
|
|
1328
|
+
throw new Error(`stream-codec: exceeded ${hardCapFrames} frames`);
|
|
1329
|
+
}
|
|
1330
|
+
if (isDone(frame)) {
|
|
1331
|
+
lastDone = frame;
|
|
1332
|
+
return lastDone;
|
|
1333
|
+
}
|
|
1334
|
+
await onFrame(frame);
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
if (lastDone) return lastDone;
|
|
1338
|
+
throw new Error("stream-codec: stream ended before done frame");
|
|
1339
|
+
}
|
|
1340
|
+
__name(readJsonFramesUntilDone, "readJsonFramesUntilDone");
|
|
1341
|
+
function coalesce(chunks) {
|
|
1342
|
+
let total = 0;
|
|
1343
|
+
for (const c of chunks) total += c.byteLength;
|
|
1344
|
+
const out = new Uint8Array(total);
|
|
1345
|
+
let off = 0;
|
|
1346
|
+
for (const c of chunks) {
|
|
1347
|
+
out.set(c, off);
|
|
1348
|
+
off += c.byteLength;
|
|
1349
|
+
}
|
|
1350
|
+
return out;
|
|
1351
|
+
}
|
|
1352
|
+
__name(coalesce, "coalesce");
|
|
1353
|
+
function coalesceFirstN(chunks, n) {
|
|
1354
|
+
const out = new Uint8Array(n);
|
|
1355
|
+
let off = 0;
|
|
1356
|
+
for (const c of chunks) {
|
|
1357
|
+
if (off >= n) break;
|
|
1358
|
+
const take = Math.min(c.byteLength, n - off);
|
|
1359
|
+
out.set(c.subarray(0, take), off);
|
|
1360
|
+
off += take;
|
|
1361
|
+
}
|
|
1362
|
+
return out;
|
|
1363
|
+
}
|
|
1364
|
+
__name(coalesceFirstN, "coalesceFirstN");
|
|
1365
|
+
|
|
1366
|
+
export {
|
|
1367
|
+
CLOUD_PROVIDERS,
|
|
1368
|
+
CLOUD_PROVIDERS_BY_ID,
|
|
1369
|
+
resolveSlug,
|
|
1370
|
+
FALLBACK_MODEL_SLUG,
|
|
1371
|
+
topSlugFor,
|
|
1372
|
+
stripReasoning,
|
|
1373
|
+
SynapseiaServingClient,
|
|
1374
|
+
LlmProviderHelper,
|
|
1375
|
+
sendJsonOverStream,
|
|
1376
|
+
readJsonFromStream,
|
|
1377
|
+
sendJsonFrame,
|
|
1378
|
+
endJsonStream,
|
|
1379
|
+
readJsonFramesUntilDone
|
|
1380
|
+
};
|