@flue/sdk 0.3.11 → 0.4.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/README.md +14 -23
- package/dist/abort-Bg3qsAkU.mjs +43 -0
- package/dist/app.d.mts +106 -0
- package/dist/app.mjs +4 -0
- package/dist/client.d.mts +9 -3
- package/dist/client.mjs +10 -24
- package/dist/cloudflare/index.d.mts +10 -6
- package/dist/cloudflare/index.mjs +388 -26
- package/dist/cloudflare-model-BeiZ1pLz.d.mts +6 -0
- package/dist/config.d.mts +133 -0
- package/dist/config.mjs +195 -0
- package/dist/flue-app-CG8i4wNG.d.mts +184 -0
- package/dist/flue-app-DeTOZjPs.mjs +730 -0
- package/dist/index.d.mts +41 -19
- package/dist/index.mjs +434 -594
- package/dist/internal.d.mts +9 -272
- package/dist/internal.mjs +16 -430
- package/dist/{mcp-CcRxAwXW.d.mts → mcp-C3UBXVkR.d.mts} +1 -1
- package/dist/{mcp-DmDTeVXW.mjs → mcp-DM6yv_Qc.mjs} +19 -33
- package/dist/node/index.d.mts +8 -12
- package/dist/node/index.mjs +94 -64
- package/dist/providers-DeFRIwp0.mjs +158 -0
- package/dist/result-K1IRhWKM.mjs +685 -0
- package/dist/sandbox.d.mts +25 -4
- package/dist/sandbox.mjs +44 -62
- package/dist/{session-DlwIt7wq.mjs → session-CFOByKnM.mjs} +488 -263
- package/dist/types-BAmV4f3Q.d.mts +727 -0
- package/package.json +12 -1
- package/dist/agent-Cahthgu3.mjs +0 -453
- package/dist/command-helpers-eVG1-Iru.d.mts +0 -21
- package/dist/command-helpers-hTZKWK13.mjs +0 -37
- package/dist/types-DGpyKMFm.d.mts +0 -508
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import "../
|
|
2
|
-
import "../
|
|
1
|
+
import "../result-K1IRhWKM.mjs";
|
|
2
|
+
import { c as CLOUDFLARE_AI_BINDING_API, n as getModelBinding } from "../providers-DeFRIwp0.mjs";
|
|
3
|
+
import { t as abortErrorFor } from "../abort-Bg3qsAkU.mjs";
|
|
4
|
+
import "../session-CFOByKnM.mjs";
|
|
3
5
|
import { createSandboxSessionEnv } from "../sandbox.mjs";
|
|
4
|
-
import {
|
|
6
|
+
import { createAssistantMessageEventStream, parseStreamingJson } from "@mariozechner/pi-ai";
|
|
5
7
|
import { Workspace, WorkspaceFileSystem } from "@cloudflare/shell";
|
|
6
8
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
9
|
+
import { convertMessages } from "@mariozechner/pi-ai/openai-completions";
|
|
7
10
|
|
|
8
11
|
//#region src/cloudflare/context.ts
|
|
9
12
|
/**
|
|
@@ -113,28 +116,6 @@ async function getVirtualSandbox(bucket, options) {
|
|
|
113
116
|
});
|
|
114
117
|
}
|
|
115
118
|
|
|
116
|
-
//#endregion
|
|
117
|
-
//#region src/cloudflare/define-command.ts
|
|
118
|
-
/**
|
|
119
|
-
* Cloudflare-specific `defineCommand`. Function form only — Workers cannot
|
|
120
|
-
* spawn host processes, so there is no pass-through sugar. The user supplies
|
|
121
|
-
* an executor (typically `fetch`-based or SDK-based) and benefits from
|
|
122
|
-
* return-shape normalization plus automatic throw-catching.
|
|
123
|
-
*
|
|
124
|
-
* ```ts
|
|
125
|
-
* const issues = defineCommand('issues', async (args) => {
|
|
126
|
-
* const res = await fetch(`https://api.github.com/...`);
|
|
127
|
-
* return { stdout: await res.text() };
|
|
128
|
-
* });
|
|
129
|
-
* ```
|
|
130
|
-
*/
|
|
131
|
-
function defineCommand(name, execute) {
|
|
132
|
-
return {
|
|
133
|
-
name,
|
|
134
|
-
execute: normalizeExecutor(execute)
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
|
|
138
119
|
//#endregion
|
|
139
120
|
//#region src/cloudflare/cf-sandbox.ts
|
|
140
121
|
/** Wraps a @cloudflare/sandbox instance (from getSandbox()) into SessionEnv. */
|
|
@@ -195,12 +176,15 @@ async function cfSandboxToSessionEnv(sandbox, cwd = "/workspace") {
|
|
|
195
176
|
} else await sandbox.deleteFile(path);
|
|
196
177
|
},
|
|
197
178
|
async exec(command, execOpts) {
|
|
179
|
+
const externalSignal = execOpts?.signal;
|
|
180
|
+
if (externalSignal?.aborted) throw abortErrorFor(externalSignal);
|
|
198
181
|
const timeoutMs = typeof execOpts?.timeout === "number" ? execOpts.timeout * 1e3 : void 0;
|
|
199
182
|
const result = await sandbox.exec(command, {
|
|
200
183
|
cwd: execOpts?.cwd,
|
|
201
184
|
env: execOpts?.env,
|
|
202
185
|
timeout: timeoutMs
|
|
203
186
|
});
|
|
187
|
+
if (externalSignal?.aborted) throw abortErrorFor(externalSignal);
|
|
204
188
|
return {
|
|
205
189
|
stdout: result.stdout ?? "",
|
|
206
190
|
stderr: result.stderr ?? "",
|
|
@@ -240,4 +224,382 @@ function store() {
|
|
|
240
224
|
}
|
|
241
225
|
|
|
242
226
|
//#endregion
|
|
243
|
-
|
|
227
|
+
//#region src/cloudflare/workers-ai-provider.ts
|
|
228
|
+
/**
|
|
229
|
+
* Mirrors pi-ai's `detectCompat('cloudflare-workers-ai')`. Hardcoded here
|
|
230
|
+
* because `convertMessages` requires a fully-resolved compat object and the
|
|
231
|
+
* binding's wire format matches `cloudflare-workers-ai` exactly. Re-mirror
|
|
232
|
+
* if pi-ai's detection logic changes upstream.
|
|
233
|
+
*/
|
|
234
|
+
const WORKERS_AI_COMPAT = {
|
|
235
|
+
supportsStore: false,
|
|
236
|
+
supportsDeveloperRole: false,
|
|
237
|
+
supportsReasoningEffort: true,
|
|
238
|
+
supportsUsageInStreaming: true,
|
|
239
|
+
maxTokensField: "max_completion_tokens",
|
|
240
|
+
requiresToolResultName: false,
|
|
241
|
+
requiresAssistantAfterToolResult: false,
|
|
242
|
+
requiresThinkingAsText: false,
|
|
243
|
+
requiresReasoningContentOnAssistantMessages: false,
|
|
244
|
+
thinkingFormat: "openai",
|
|
245
|
+
openRouterRouting: {},
|
|
246
|
+
vercelGatewayRouting: {},
|
|
247
|
+
zaiToolStream: false,
|
|
248
|
+
supportsStrictMode: true,
|
|
249
|
+
cacheControlFormat: void 0,
|
|
250
|
+
sendSessionAffinityHeaders: true,
|
|
251
|
+
supportsLongCacheRetention: false
|
|
252
|
+
};
|
|
253
|
+
function convertTools(tools) {
|
|
254
|
+
return tools.map((tool) => ({
|
|
255
|
+
type: "function",
|
|
256
|
+
function: {
|
|
257
|
+
name: tool.name,
|
|
258
|
+
description: tool.description,
|
|
259
|
+
parameters: tool.parameters,
|
|
260
|
+
...WORKERS_AI_COMPAT.supportsStrictMode !== false && { strict: false }
|
|
261
|
+
}
|
|
262
|
+
}));
|
|
263
|
+
}
|
|
264
|
+
function emptyUsage() {
|
|
265
|
+
return {
|
|
266
|
+
input: 0,
|
|
267
|
+
output: 0,
|
|
268
|
+
cacheRead: 0,
|
|
269
|
+
cacheWrite: 0,
|
|
270
|
+
totalTokens: 0,
|
|
271
|
+
cost: {
|
|
272
|
+
input: 0,
|
|
273
|
+
output: 0,
|
|
274
|
+
cacheRead: 0,
|
|
275
|
+
cacheWrite: 0,
|
|
276
|
+
total: 0
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function parseChunkUsage(raw) {
|
|
281
|
+
const cacheRead = raw.prompt_tokens_details?.cached_tokens ?? 0;
|
|
282
|
+
const promptTokens = raw.prompt_tokens ?? 0;
|
|
283
|
+
const completionTokens = raw.completion_tokens ?? 0;
|
|
284
|
+
return {
|
|
285
|
+
input: Math.max(0, promptTokens - cacheRead),
|
|
286
|
+
output: completionTokens,
|
|
287
|
+
cacheRead,
|
|
288
|
+
cacheWrite: 0,
|
|
289
|
+
totalTokens: raw.total_tokens ?? promptTokens + completionTokens,
|
|
290
|
+
cost: {
|
|
291
|
+
input: 0,
|
|
292
|
+
output: 0,
|
|
293
|
+
cacheRead: 0,
|
|
294
|
+
cacheWrite: 0,
|
|
295
|
+
total: 0
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
function mapStopReason(reason) {
|
|
300
|
+
switch (reason) {
|
|
301
|
+
case "stop":
|
|
302
|
+
case "eos": return { stopReason: "stop" };
|
|
303
|
+
case "length": return { stopReason: "length" };
|
|
304
|
+
case "tool_calls":
|
|
305
|
+
case "function_call": return { stopReason: "toolUse" };
|
|
306
|
+
case "content_filter": return {
|
|
307
|
+
stopReason: "error",
|
|
308
|
+
errorMessage: "Provider stopped generation: content filter"
|
|
309
|
+
};
|
|
310
|
+
default: return {
|
|
311
|
+
stopReason: "error",
|
|
312
|
+
errorMessage: `Provider finish_reason: ${reason}`
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
async function* iterateSseChunks(body) {
|
|
317
|
+
const reader = body.getReader();
|
|
318
|
+
const decoder = new TextDecoder();
|
|
319
|
+
let buffer = "";
|
|
320
|
+
try {
|
|
321
|
+
while (true) {
|
|
322
|
+
const { done, value } = await reader.read();
|
|
323
|
+
if (done) {
|
|
324
|
+
if (buffer.trim().length > 0) yield* parseSseEvents(buffer);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
buffer += decoder.decode(value, { stream: true });
|
|
328
|
+
let separatorIndex = buffer.indexOf("\n\n");
|
|
329
|
+
while (separatorIndex !== -1) {
|
|
330
|
+
const block = buffer.slice(0, separatorIndex);
|
|
331
|
+
buffer = buffer.slice(separatorIndex + 2);
|
|
332
|
+
yield* parseSseEvents(block);
|
|
333
|
+
separatorIndex = buffer.indexOf("\n\n");
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
} finally {
|
|
337
|
+
try {
|
|
338
|
+
reader.releaseLock();
|
|
339
|
+
} catch {}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
function* parseSseEvents(block) {
|
|
343
|
+
for (const rawLine of block.split("\n")) {
|
|
344
|
+
const line = rawLine.replace(/\r$/, "");
|
|
345
|
+
if (!line.startsWith("data:")) continue;
|
|
346
|
+
const data = line.slice(5).trimStart();
|
|
347
|
+
if (data === "" || data === "[DONE]") continue;
|
|
348
|
+
try {
|
|
349
|
+
yield JSON.parse(data);
|
|
350
|
+
} catch {}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
function isAbortError(error) {
|
|
354
|
+
return error instanceof Error && (error.name === "AbortError" || /aborted/i.test(error.message));
|
|
355
|
+
}
|
|
356
|
+
const streamCloudflareWorkersAi = (model, context, options) => {
|
|
357
|
+
const stream = createAssistantMessageEventStream();
|
|
358
|
+
(async () => {
|
|
359
|
+
const output = {
|
|
360
|
+
role: "assistant",
|
|
361
|
+
content: [],
|
|
362
|
+
api: model.api,
|
|
363
|
+
provider: model.provider,
|
|
364
|
+
model: model.id,
|
|
365
|
+
usage: emptyUsage(),
|
|
366
|
+
stopReason: "stop",
|
|
367
|
+
timestamp: Date.now()
|
|
368
|
+
};
|
|
369
|
+
try {
|
|
370
|
+
const ai = resolveBinding(model);
|
|
371
|
+
const payload = {
|
|
372
|
+
messages: convertMessages(model, context, WORKERS_AI_COMPAT),
|
|
373
|
+
stream: true,
|
|
374
|
+
stream_options: { include_usage: true }
|
|
375
|
+
};
|
|
376
|
+
if (context.tools && context.tools.length > 0) payload.tools = convertTools(context.tools);
|
|
377
|
+
if (options?.maxTokens) payload.max_completion_tokens = options.maxTokens;
|
|
378
|
+
if (options?.temperature !== void 0) payload.temperature = options.temperature;
|
|
379
|
+
const overridden = await options?.onPayload?.(payload, model);
|
|
380
|
+
const finalPayload = overridden === void 0 ? payload : overridden;
|
|
381
|
+
const extraHeaders = {};
|
|
382
|
+
if (options?.sessionId) extraHeaders["x-session-affinity"] = options.sessionId;
|
|
383
|
+
if (options?.headers) Object.assign(extraHeaders, options.headers);
|
|
384
|
+
const response = await ai.run(model.id, finalPayload, {
|
|
385
|
+
returnRawResponse: true,
|
|
386
|
+
...options?.signal ? { signal: options.signal } : {},
|
|
387
|
+
...Object.keys(extraHeaders).length > 0 ? { extraHeaders } : {}
|
|
388
|
+
});
|
|
389
|
+
await options?.onResponse?.({
|
|
390
|
+
status: response.status,
|
|
391
|
+
headers: headersToRecord(response.headers)
|
|
392
|
+
}, model);
|
|
393
|
+
if (!response.ok) {
|
|
394
|
+
const errorBody = await safeReadText(response);
|
|
395
|
+
throw new Error(`Cloudflare AI binding returned ${response.status} ${response.statusText}` + (errorBody ? `: ${errorBody}` : ""));
|
|
396
|
+
}
|
|
397
|
+
if (!response.body) throw new Error("Cloudflare AI binding returned empty response body.");
|
|
398
|
+
stream.push({
|
|
399
|
+
type: "start",
|
|
400
|
+
partial: output
|
|
401
|
+
});
|
|
402
|
+
let currentBlock = null;
|
|
403
|
+
const blocks = output.content;
|
|
404
|
+
const indexOf = (block) => block ? blocks.indexOf(block) : -1;
|
|
405
|
+
const finishCurrentBlock = (block) => {
|
|
406
|
+
if (!block) return;
|
|
407
|
+
const contentIndex = indexOf(block);
|
|
408
|
+
if (contentIndex === -1) return;
|
|
409
|
+
if (block.type === "text") stream.push({
|
|
410
|
+
type: "text_end",
|
|
411
|
+
contentIndex,
|
|
412
|
+
content: block.text,
|
|
413
|
+
partial: output
|
|
414
|
+
});
|
|
415
|
+
else if (block.type === "thinking") stream.push({
|
|
416
|
+
type: "thinking_end",
|
|
417
|
+
contentIndex,
|
|
418
|
+
content: block.thinking,
|
|
419
|
+
partial: output
|
|
420
|
+
});
|
|
421
|
+
else if (block.type === "toolCall") {
|
|
422
|
+
block.arguments = parseStreamingJson(block.partialArgs ?? "");
|
|
423
|
+
delete block.partialArgs;
|
|
424
|
+
delete block.streamIndex;
|
|
425
|
+
stream.push({
|
|
426
|
+
type: "toolcall_end",
|
|
427
|
+
contentIndex,
|
|
428
|
+
toolCall: block,
|
|
429
|
+
partial: output
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
for await (const rawChunk of iterateSseChunks(response.body)) {
|
|
434
|
+
const chunk = rawChunk;
|
|
435
|
+
if (!chunk || typeof chunk !== "object") continue;
|
|
436
|
+
output.responseId ||= chunk.id;
|
|
437
|
+
if (typeof chunk.model === "string" && chunk.model.length > 0 && chunk.model !== model.id) output.responseModel ||= chunk.model;
|
|
438
|
+
if (chunk.usage) output.usage = parseChunkUsage(chunk.usage);
|
|
439
|
+
const choice = Array.isArray(chunk.choices) ? chunk.choices[0] : void 0;
|
|
440
|
+
if (!choice) continue;
|
|
441
|
+
if (!chunk.usage && choice.usage) output.usage = parseChunkUsage(choice.usage);
|
|
442
|
+
if (choice.finish_reason) {
|
|
443
|
+
const mapped = mapStopReason(choice.finish_reason);
|
|
444
|
+
output.stopReason = mapped.stopReason;
|
|
445
|
+
if (mapped.errorMessage) output.errorMessage = mapped.errorMessage;
|
|
446
|
+
}
|
|
447
|
+
const delta = choice.delta;
|
|
448
|
+
if (!delta) continue;
|
|
449
|
+
if (delta.content !== null && delta.content !== void 0 && delta.content.length > 0) {
|
|
450
|
+
if (!currentBlock || currentBlock.type !== "text") {
|
|
451
|
+
finishCurrentBlock(currentBlock);
|
|
452
|
+
currentBlock = {
|
|
453
|
+
type: "text",
|
|
454
|
+
text: ""
|
|
455
|
+
};
|
|
456
|
+
blocks.push(currentBlock);
|
|
457
|
+
stream.push({
|
|
458
|
+
type: "text_start",
|
|
459
|
+
contentIndex: indexOf(currentBlock),
|
|
460
|
+
partial: output
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
currentBlock.text += delta.content;
|
|
464
|
+
stream.push({
|
|
465
|
+
type: "text_delta",
|
|
466
|
+
contentIndex: indexOf(currentBlock),
|
|
467
|
+
delta: delta.content,
|
|
468
|
+
partial: output
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
const reasoningDelta = pickReasoning(delta);
|
|
472
|
+
if (reasoningDelta) {
|
|
473
|
+
if (!currentBlock || currentBlock.type !== "thinking") {
|
|
474
|
+
finishCurrentBlock(currentBlock);
|
|
475
|
+
currentBlock = {
|
|
476
|
+
type: "thinking",
|
|
477
|
+
thinking: "",
|
|
478
|
+
thinkingSignature: reasoningDelta.field
|
|
479
|
+
};
|
|
480
|
+
blocks.push(currentBlock);
|
|
481
|
+
stream.push({
|
|
482
|
+
type: "thinking_start",
|
|
483
|
+
contentIndex: indexOf(currentBlock),
|
|
484
|
+
partial: output
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
currentBlock.thinking += reasoningDelta.text;
|
|
488
|
+
stream.push({
|
|
489
|
+
type: "thinking_delta",
|
|
490
|
+
contentIndex: indexOf(currentBlock),
|
|
491
|
+
delta: reasoningDelta.text,
|
|
492
|
+
partial: output
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
if (delta.tool_calls) for (const toolCall of delta.tool_calls) {
|
|
496
|
+
const streamIndex = typeof toolCall.index === "number" ? toolCall.index : void 0;
|
|
497
|
+
if (!(currentBlock?.type === "toolCall" && (streamIndex !== void 0 && currentBlock.streamIndex === streamIndex || streamIndex === void 0 && !!toolCall.id && currentBlock.id === toolCall.id))) {
|
|
498
|
+
finishCurrentBlock(currentBlock);
|
|
499
|
+
currentBlock = {
|
|
500
|
+
type: "toolCall",
|
|
501
|
+
id: toolCall.id ?? "",
|
|
502
|
+
name: toolCall.function?.name ?? "",
|
|
503
|
+
arguments: {},
|
|
504
|
+
partialArgs: "",
|
|
505
|
+
streamIndex
|
|
506
|
+
};
|
|
507
|
+
blocks.push(currentBlock);
|
|
508
|
+
stream.push({
|
|
509
|
+
type: "toolcall_start",
|
|
510
|
+
contentIndex: indexOf(currentBlock),
|
|
511
|
+
partial: output
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
const block = currentBlock?.type === "toolCall" ? currentBlock : null;
|
|
515
|
+
if (block) {
|
|
516
|
+
if (!block.id && toolCall.id) block.id = toolCall.id;
|
|
517
|
+
if (!block.name && toolCall.function?.name) block.name = toolCall.function.name;
|
|
518
|
+
if (block.streamIndex === void 0 && streamIndex !== void 0) block.streamIndex = streamIndex;
|
|
519
|
+
let toolDelta = "";
|
|
520
|
+
if (toolCall.function?.arguments) {
|
|
521
|
+
toolDelta = toolCall.function.arguments;
|
|
522
|
+
block.partialArgs = (block.partialArgs ?? "") + toolDelta;
|
|
523
|
+
block.arguments = parseStreamingJson(block.partialArgs);
|
|
524
|
+
}
|
|
525
|
+
stream.push({
|
|
526
|
+
type: "toolcall_delta",
|
|
527
|
+
contentIndex: indexOf(block),
|
|
528
|
+
delta: toolDelta,
|
|
529
|
+
partial: output
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
finishCurrentBlock(currentBlock);
|
|
535
|
+
if (options?.signal?.aborted) throw new Error("Request was aborted");
|
|
536
|
+
if (output.stopReason === "aborted") throw new Error("Request was aborted");
|
|
537
|
+
if (output.stopReason === "error") throw new Error(output.errorMessage ?? "Provider returned an error stop reason");
|
|
538
|
+
stream.push({
|
|
539
|
+
type: "done",
|
|
540
|
+
reason: output.stopReason,
|
|
541
|
+
message: output
|
|
542
|
+
});
|
|
543
|
+
stream.end();
|
|
544
|
+
} catch (error) {
|
|
545
|
+
for (const block of output.content) if (block.type === "toolCall") {
|
|
546
|
+
delete block.partialArgs;
|
|
547
|
+
delete block.streamIndex;
|
|
548
|
+
}
|
|
549
|
+
output.stopReason = options?.signal?.aborted || isAbortError(error) ? "aborted" : "error";
|
|
550
|
+
output.errorMessage = error instanceof Error ? error.message : JSON.stringify(error);
|
|
551
|
+
stream.push({
|
|
552
|
+
type: "error",
|
|
553
|
+
reason: output.stopReason,
|
|
554
|
+
error: output
|
|
555
|
+
});
|
|
556
|
+
stream.end();
|
|
557
|
+
}
|
|
558
|
+
})();
|
|
559
|
+
return stream;
|
|
560
|
+
};
|
|
561
|
+
/**
|
|
562
|
+
* Read the binding extension carried on the resolved Model.
|
|
563
|
+
*/
|
|
564
|
+
function resolveBinding(model) {
|
|
565
|
+
const ai = getModelBinding(model);
|
|
566
|
+
if (!ai) throw new Error("[flue] Cloudflare AI binding not available. Models prefixed with \"cloudflare/\" require running on the Cloudflare target with `\"ai\": { \"binding\": \"AI\" }` declared in wrangler.jsonc. For URL-based access without the binding, use pi-ai's `cloudflare-workers-ai/...` or `cloudflare-ai-gateway/...` providers (both require Cloudflare API credentials in env vars).");
|
|
567
|
+
return ai;
|
|
568
|
+
}
|
|
569
|
+
function pickReasoning(delta) {
|
|
570
|
+
for (const field of ["reasoning_content", "reasoning"]) {
|
|
571
|
+
const value = delta[field];
|
|
572
|
+
if (typeof value === "string" && value.length > 0) return {
|
|
573
|
+
field,
|
|
574
|
+
text: value
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
return null;
|
|
578
|
+
}
|
|
579
|
+
function headersToRecord(headers) {
|
|
580
|
+
const out = {};
|
|
581
|
+
headers.forEach((value, key) => {
|
|
582
|
+
out[key] = value;
|
|
583
|
+
});
|
|
584
|
+
return out;
|
|
585
|
+
}
|
|
586
|
+
async function safeReadText(response) {
|
|
587
|
+
try {
|
|
588
|
+
return await response.text();
|
|
589
|
+
} catch {
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Return the pi-ai `ApiProvider` definition for the Cloudflare AI binding.
|
|
595
|
+
*/
|
|
596
|
+
function getCloudflareAIBindingApiProvider() {
|
|
597
|
+
return {
|
|
598
|
+
api: CLOUDFLARE_AI_BINDING_API,
|
|
599
|
+
stream: streamCloudflareWorkersAi,
|
|
600
|
+
streamSimple: streamCloudflareWorkersAi
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
//#endregion
|
|
605
|
+
export { cfSandboxToSessionEnv, getCloudflareAIBindingApiProvider, getCloudflareContext, getVirtualSandbox, runWithCloudflareContext, store };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
//#region src/cloudflare-model.d.ts
|
|
2
|
+
/** Pi-ai `Api` slug for the binding-backed Workers AI provider. */
|
|
3
|
+
declare const CLOUDFLARE_AI_BINDING_API: "cloudflare-ai-binding";
|
|
4
|
+
type CloudflareAIBindingApi = typeof CLOUDFLARE_AI_BINDING_API;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { CloudflareAIBindingApi as n, CLOUDFLARE_AI_BINDING_API as t };
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
//#region src/config.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Flue config file support — `flue.config.{ts,mts,mjs,js,cjs,cts}`.
|
|
4
|
+
*
|
|
5
|
+
* Modeled on Vite/Astro:
|
|
6
|
+
*
|
|
7
|
+
* - The config file lives at the project root. Its directory IS the root for
|
|
8
|
+
* the purposes of resolving any relative paths it sets (`root`, `output`).
|
|
9
|
+
* - Discovery: `--config <path>` (resolved vs. cwd) wins; otherwise we search
|
|
10
|
+
* a starting directory (`--root` if given, else cwd) for any of the
|
|
11
|
+
* supported extensions, in order.
|
|
12
|
+
* - Loading: plain Node dynamic `import()`. We rely on Node's native
|
|
13
|
+
* TypeScript type-stripping (Node ≥ 22.18 / ≥ 23.6 by default) to handle
|
|
14
|
+
* `.ts` configs. We deliberately do NOT bundle the config — `flue.config`
|
|
15
|
+
* is a flat declarative surface, and "what valid TS works" should match
|
|
16
|
+
* the same rules the user already absorbed for the rest of the runtime.
|
|
17
|
+
* The CLI bin pre-checks the Node version before we ever get here, so
|
|
18
|
+
* `ERR_UNKNOWN_FILE_EXTENSION` shouldn't surface in practice.
|
|
19
|
+
* - Validation: valibot schema on the user-facing shape.
|
|
20
|
+
* - Resolution: CLI inline > config file > built-in defaults. CLI flags
|
|
21
|
+
* always win on a per-field basis — only the fields the user actually
|
|
22
|
+
* passed get to override the file.
|
|
23
|
+
*
|
|
24
|
+
* The two public types mirror Astro's `AstroUserConfig` / `AstroConfig`
|
|
25
|
+
* split: `UserFlueConfig` is what users author (everything optional);
|
|
26
|
+
* `FlueConfig` is the resolved shape with required defaults filled in.
|
|
27
|
+
*
|
|
28
|
+
* Provider/model configuration lives in `app.ts`, where runtime env is
|
|
29
|
+
* available.
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* User-facing config shape — everything optional so `defineConfig({})` is
|
|
33
|
+
* valid. Defaults are filled in at resolution time. Modeled on Astro's
|
|
34
|
+
* `AstroUserConfig`.
|
|
35
|
+
*/
|
|
36
|
+
interface UserFlueConfig {
|
|
37
|
+
/**
|
|
38
|
+
* Build target. Required somewhere — either here or via `--target`.
|
|
39
|
+
*/
|
|
40
|
+
target?: 'node' | 'cloudflare';
|
|
41
|
+
/**
|
|
42
|
+
* Project root. Source files (`agents/`, `roles/`) live here directly,
|
|
43
|
+
* or under `<root>/.flue/`. Relative paths are resolved vs. the
|
|
44
|
+
* directory containing the config file (Vite-style: the config file's
|
|
45
|
+
* dir IS the root by default). Defaults to that directory if unset.
|
|
46
|
+
*/
|
|
47
|
+
root?: string;
|
|
48
|
+
/**
|
|
49
|
+
* Build output dir. Relative paths are resolved vs. the directory
|
|
50
|
+
* containing the config file. Defaults to `<root>/dist`.
|
|
51
|
+
*/
|
|
52
|
+
output?: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Resolved config — what the rest of the SDK consumes. All paths are
|
|
56
|
+
* absolute; all required fields are present.
|
|
57
|
+
*/
|
|
58
|
+
interface FlueConfig {
|
|
59
|
+
target: 'node' | 'cloudflare';
|
|
60
|
+
/** Absolute path. */
|
|
61
|
+
root: string;
|
|
62
|
+
/** Absolute path. */
|
|
63
|
+
output: string;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Identity helper for type inference and editor intellisense, à la Vite's
|
|
67
|
+
* `defineConfig`. Returns its argument unchanged.
|
|
68
|
+
*
|
|
69
|
+
* ```ts
|
|
70
|
+
* import { defineConfig } from '@flue/sdk/config';
|
|
71
|
+
* export default defineConfig({ target: 'node' });
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
declare function defineConfig(config: UserFlueConfig): UserFlueConfig;
|
|
75
|
+
interface ResolveConfigPathOptions {
|
|
76
|
+
/** Where to start searching when `configFile` is not set. */
|
|
77
|
+
cwd: string;
|
|
78
|
+
/**
|
|
79
|
+
* Explicit config-file path (relative to `cwd`, or absolute), or `false`
|
|
80
|
+
* to disable config loading entirely. Mirrors Astro's
|
|
81
|
+
* `AstroInlineOnlyConfig.configFile`.
|
|
82
|
+
*/
|
|
83
|
+
configFile?: string | false;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Resolve the absolute path of the user's `flue.config.*` file, or
|
|
87
|
+
* `undefined` if none is found and the user didn't ask for one.
|
|
88
|
+
*
|
|
89
|
+
* Throws if `configFile` is an explicit path that doesn't exist on disk —
|
|
90
|
+
* that's a typo, not a "config not configured" situation.
|
|
91
|
+
*/
|
|
92
|
+
declare function resolveConfigPath(opts: ResolveConfigPathOptions): string | undefined;
|
|
93
|
+
interface ResolveConfigOptions {
|
|
94
|
+
/** Working directory of the CLI invocation; default search base. */
|
|
95
|
+
cwd: string;
|
|
96
|
+
/**
|
|
97
|
+
* Optional starting directory to search for the config. If unset, falls
|
|
98
|
+
* back to `cwd`. Used when the CLI received `--root` and we want to look
|
|
99
|
+
* for a config inside that directory rather than cwd. Vite has the same
|
|
100
|
+
* behavior with `--root`.
|
|
101
|
+
*/
|
|
102
|
+
searchFrom?: string;
|
|
103
|
+
/** Explicit `--config` value, or `false` to skip loading. */
|
|
104
|
+
configFile?: string | false;
|
|
105
|
+
/**
|
|
106
|
+
* Inline overrides from the CLI. Only fields the user actually passed
|
|
107
|
+
* should be present — `undefined` means "fall through to the config file
|
|
108
|
+
* value or the default".
|
|
109
|
+
*/
|
|
110
|
+
inline?: UserFlueConfig;
|
|
111
|
+
}
|
|
112
|
+
interface ResolvedConfigResult {
|
|
113
|
+
/** Absolute path of the loaded config file, or undefined if none. */
|
|
114
|
+
configPath: string | undefined;
|
|
115
|
+
/** The merged-but-unresolved user config (config file + inline). */
|
|
116
|
+
userConfig: UserFlueConfig;
|
|
117
|
+
/** The fully-resolved config consumed by the rest of the SDK. */
|
|
118
|
+
flueConfig: FlueConfig;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Discover, load, validate, merge, and resolve a Flue config. The single
|
|
122
|
+
* entry point CLIs and embedders call.
|
|
123
|
+
*
|
|
124
|
+
* Precedence (highest first):
|
|
125
|
+
* 1. CLI inline values (`opts.inline.*`)
|
|
126
|
+
* 2. `flue.config.ts`
|
|
127
|
+
* 3. Built-in defaults
|
|
128
|
+
*
|
|
129
|
+
* Throws if validation fails or if no `target` is supplied anywhere.
|
|
130
|
+
*/
|
|
131
|
+
declare function resolveConfig(opts: ResolveConfigOptions): Promise<ResolvedConfigResult>;
|
|
132
|
+
//#endregion
|
|
133
|
+
export { FlueConfig, ResolveConfigOptions, ResolveConfigPathOptions, ResolvedConfigResult, UserFlueConfig, defineConfig, resolveConfig, resolveConfigPath };
|