@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.
@@ -1,9 +1,12 @@
1
- import "../agent-Cahthgu3.mjs";
2
- import "../session-DlwIt7wq.mjs";
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 { t as normalizeExecutor } from "../command-helpers-hTZKWK13.mjs";
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
- export { cfSandboxToSessionEnv, defineCommand, getCloudflareContext, getVirtualSandbox, runWithCloudflareContext, store };
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 };