@flue/sdk 0.3.11 → 0.4.0

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,75 +1,105 @@
1
- import { t as normalizeExecutor } from "../command-helpers-hTZKWK13.mjs";
2
- import { execFile } from "node:child_process";
1
+ import { t as abortErrorFor } from "../abort-Bg3qsAkU.mjs";
2
+ import * as path from "node:path";
3
+ import { exec } from "node:child_process";
3
4
  import { promisify } from "node:util";
5
+ import * as fs from "node:fs/promises";
4
6
 
5
- //#region src/node/define-command.ts
7
+ //#region src/node/local-env.ts
6
8
  /**
7
- * Node-specific `defineCommand`. Supports three forms:
9
+ * Pure-Node `SessionEnv` backed by the host filesystem and `child_process`.
8
10
  *
9
- * ```ts
10
- * defineCommand('agent-browser');
11
- * defineCommand('gh', { env: { GH_TOKEN: process.env.GH_TOKEN } });
12
- * defineCommand('gh', async (args) => ({ stdout: '...' }));
13
- * ```
11
+ * Powers `init({ sandbox: 'local' })` on the Node target. No sandboxing,
12
+ * no virtual filesystem, no just-bash — `exec` shells out via the user's
13
+ * default shell, file methods call `node:fs/promises` directly.
14
14
  *
15
- * Forms A and B shell out via `child_process.execFile`. Form C lets the user
16
- * implement the command however they like. All three forms benefit from
17
- * return-shape normalization and throw-catching no `try`/`catch` or
18
- * `return { stdout, stderr, exitCode: 0 }` boilerplate required.
15
+ * Use this when you're running flue inside an external sandbox (Daytona,
16
+ * E2B, a container, a CI runner, etc.) and want flue itself to operate on
17
+ * the host filesystem without an additional layer of isolation.
19
18
  */
20
- const execFileAsync = promisify(execFile);
21
- /**
22
- * Essential, non-sensitive environment variables automatically forwarded to
23
- * pass-through commands (forms A and B). Users can override any of these —
24
- * or add their own (e.g. `GH_TOKEN`) — via `options.env`. Anything not listed
25
- * here (API keys, tokens, secrets, etc.) stays on the host and is NEVER
26
- * exposed to the spawned process unless the caller opts in explicitly.
27
- *
28
- * If you need full control over the env, use the function form:
29
- * `defineCommand('gh', async (args) => { ... })`.
30
- */
31
- const DEFAULT_ENV = {
32
- PATH: process.env.PATH,
33
- HOME: process.env.HOME,
34
- USER: process.env.USER,
35
- LOGNAME: process.env.LOGNAME,
36
- HOSTNAME: process.env.HOSTNAME,
37
- SHELL: process.env.SHELL,
38
- LANG: process.env.LANG,
39
- LC_ALL: process.env.LC_ALL,
40
- LC_CTYPE: process.env.LC_CTYPE,
41
- TZ: process.env.TZ,
42
- TERM: process.env.TERM,
43
- TMPDIR: process.env.TMPDIR,
44
- TMP: process.env.TMP,
45
- TEMP: process.env.TEMP
46
- };
47
- function defineCommand(name, arg) {
48
- if (typeof arg === "function") return {
49
- name,
50
- execute: normalizeExecutor(arg)
51
- };
52
- const userOpts = arg ?? {};
53
- const mergedOpts = {
54
- maxBuffer: 50 * 1024 * 1024,
55
- ...userOpts,
56
- env: {
57
- ...DEFAULT_ENV,
58
- ...userOpts.env ?? {}
59
- }
60
- };
61
- const executor = async (args) => {
62
- const { stdout, stderr } = await execFileAsync(name, args, mergedOpts);
63
- return {
64
- stdout: String(stdout ?? ""),
65
- stderr: String(stderr ?? "")
66
- };
67
- };
19
+ const execAsync = promisify(exec);
20
+ function createLocalSessionEnv(options = {}) {
21
+ const cwd = path.resolve(options.cwd ?? process.cwd());
22
+ const resolvePath = (p) => path.isAbsolute(p) ? p : path.resolve(cwd, p);
68
23
  return {
69
- name,
70
- execute: normalizeExecutor(executor)
24
+ async exec(command, opts) {
25
+ const signal = opts?.signal;
26
+ if (signal?.aborted) throw abortErrorFor(signal);
27
+ const timeoutSignal = typeof opts?.timeout === "number" ? AbortSignal.timeout(opts.timeout * 1e3) : void 0;
28
+ const mergedSignal = signal && timeoutSignal ? AbortSignal.any([signal, timeoutSignal]) : signal ?? timeoutSignal;
29
+ try {
30
+ const { stdout, stderr } = await execAsync(command, {
31
+ cwd: opts?.cwd ? resolvePath(opts.cwd) : cwd,
32
+ env: opts?.env ? {
33
+ ...process.env,
34
+ ...opts.env
35
+ } : process.env,
36
+ signal: mergedSignal,
37
+ encoding: "utf8",
38
+ maxBuffer: 64 * 1024 * 1024
39
+ });
40
+ if (signal?.aborted) throw abortErrorFor(signal);
41
+ return {
42
+ stdout,
43
+ stderr,
44
+ exitCode: 0
45
+ };
46
+ } catch (err) {
47
+ if (signal?.aborted) throw abortErrorFor(signal);
48
+ if (err && typeof err === "object" && "code" in err) return {
49
+ stdout: typeof err.stdout === "string" ? err.stdout : "",
50
+ stderr: typeof err.stderr === "string" ? err.stderr : String(err.message ?? ""),
51
+ exitCode: typeof err.code === "number" ? err.code : 1
52
+ };
53
+ throw err;
54
+ }
55
+ },
56
+ async readFile(p) {
57
+ return fs.readFile(resolvePath(p), "utf8");
58
+ },
59
+ async readFileBuffer(p) {
60
+ const buf = await fs.readFile(resolvePath(p));
61
+ return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
62
+ },
63
+ async writeFile(p, content) {
64
+ const resolved = resolvePath(p);
65
+ const dir = path.dirname(resolved);
66
+ if (dir && dir !== resolved) await fs.mkdir(dir, { recursive: true });
67
+ await fs.writeFile(resolved, content);
68
+ },
69
+ async stat(p) {
70
+ const s = await fs.stat(resolvePath(p));
71
+ return {
72
+ isFile: s.isFile(),
73
+ isDirectory: s.isDirectory(),
74
+ isSymbolicLink: s.isSymbolicLink(),
75
+ size: s.size,
76
+ mtime: s.mtime
77
+ };
78
+ },
79
+ async readdir(p) {
80
+ return fs.readdir(resolvePath(p));
81
+ },
82
+ async exists(p) {
83
+ try {
84
+ await fs.access(resolvePath(p));
85
+ return true;
86
+ } catch {
87
+ return false;
88
+ }
89
+ },
90
+ async mkdir(p, opts) {
91
+ await fs.mkdir(resolvePath(p), { recursive: opts?.recursive ?? false });
92
+ },
93
+ async rm(p, opts) {
94
+ await fs.rm(resolvePath(p), {
95
+ recursive: opts?.recursive ?? false,
96
+ force: opts?.force ?? false
97
+ });
98
+ },
99
+ cwd,
100
+ resolvePath
71
101
  };
72
102
  }
73
103
 
74
104
  //#endregion
75
- export { defineCommand };
105
+ export { createLocalSessionEnv };
@@ -0,0 +1,158 @@
1
+ import { registerApiProvider } from "@mariozechner/pi-ai";
2
+
3
+ //#region src/cloudflare-model.ts
4
+ /** Pi-ai `Api` slug for the binding-backed Workers AI provider. */
5
+ const CLOUDFLARE_AI_BINDING_API = "cloudflare-ai-binding";
6
+ /** Provider name surfaced on AssistantMessage records and usage logs. */
7
+ const CLOUDFLARE_AI_BINDING_PROVIDER = "workers-ai";
8
+
9
+ //#endregion
10
+ //#region src/runtime/providers.ts
11
+ /** Runtime provider registries consumed by `resolveModel` and Session. */
12
+ /**
13
+ * pi-ai's open-ended `Api` type prevents direct discriminator narrowing.
14
+ */
15
+ function isCloudflareBindingRegistration(def) {
16
+ return def.api === CLOUDFLARE_AI_BINDING_API;
17
+ }
18
+ /**
19
+ * URL-prefix registry populated at module init by `app.ts` and generated
20
+ * server entries.
21
+ */
22
+ const userModels = /* @__PURE__ */ new Map();
23
+ /**
24
+ * Register a Flue-level model provider keyed by URL prefix.
25
+ *
26
+ * Last-write-wins. On Cloudflare, the generated entry reserves the
27
+ * `cloudflare` prefix for the built-in Workers AI binding integration.
28
+ */
29
+ function registerProvider(name, registration) {
30
+ userModels.set(name, registration);
31
+ }
32
+ /**
33
+ * Look up a registration apiKey by the resolved pi-ai provider slug.
34
+ */
35
+ function getRegisteredApiKey(provider) {
36
+ for (const [name, def] of userModels) {
37
+ if (effectiveProviderSlug(name, def) !== provider) continue;
38
+ if (!isCloudflareBindingRegistration(def)) return def.apiKey;
39
+ }
40
+ }
41
+ /**
42
+ * Re-export of pi-ai's `registerApiProvider`. Use to register a brand-new
43
+ * wire-protocol handler for an `api` slug pi-ai doesn't ship. Then call
44
+ * {@link registerProvider} to alias a URL prefix to that api.
45
+ *
46
+ * ```ts
47
+ * registerApiProvider({ api: 'my-novel-api', stream, streamSimple });
48
+ * registerProvider('thing', { api: 'my-novel-api', baseUrl: '...', apiKey: '...' });
49
+ * ```
50
+ *
51
+ * pi-ai's registry is also module-scoped and last-write-wins. Calling
52
+ * `registerApiProvider` repeatedly with the same `api` string overwrites,
53
+ * so generated code can register on every isolate boot without dedupe
54
+ * bookkeeping.
55
+ */
56
+ const registerApiProvider$1 = registerApiProvider;
57
+ const providerOverrides = /* @__PURE__ */ new Map();
58
+ /**
59
+ * Patch transport-level settings on an existing provider while preserving its
60
+ * resolved Model metadata (cost, context window, token limits, etc.).
61
+ *
62
+ * ```ts
63
+ * import { configureProvider } from '@flue/sdk/app';
64
+ *
65
+ * configureProvider('anthropic', {
66
+ * baseUrl: 'https://gateway.example.com/anthropic',
67
+ * apiKey: process.env.GATEWAY_KEY,
68
+ * });
69
+ * ```
70
+ *
71
+ * Keyed by the resolved `Model.provider` value, not necessarily the URL
72
+ * prefix. Last-write-wins.
73
+ */
74
+ function configureProvider(provider, settings) {
75
+ providerOverrides.set(provider, settings);
76
+ }
77
+ /**
78
+ * Internal read accessor for provider overrides.
79
+ */
80
+ function getProviderConfiguration(provider) {
81
+ return providerOverrides.get(provider);
82
+ }
83
+ /** Attach a Workers AI binding to a Model literal. */
84
+ function attachModelBinding(model, binding) {
85
+ return {
86
+ ...model,
87
+ binding
88
+ };
89
+ }
90
+ /**
91
+ * Read a Workers AI binding off a resolved Model, or `undefined` if no
92
+ * usable binding is attached.
93
+ */
94
+ function getModelBinding(model) {
95
+ const candidate = model.binding;
96
+ if (!candidate || typeof candidate.run !== "function") return;
97
+ return candidate;
98
+ }
99
+ /**
100
+ * Resolve `'name/modelId'` against the URL-prefix registry.
101
+ */
102
+ function resolveRegisteredModel(name, modelId) {
103
+ const def = userModels.get(name);
104
+ if (!def) return void 0;
105
+ return buildModelFromRegistration(name, def, modelId);
106
+ }
107
+ /**
108
+ * Construct a pi-ai Model from a registered provider template. User-defined
109
+ * providers do not have catalog metadata, so cost and context limits default
110
+ * to zero. apiKey flows through `getApiKey`; it is not part of pi-ai's Model.
111
+ */
112
+ function buildModelFromRegistration(name, def, modelId) {
113
+ if (isCloudflareBindingRegistration(def)) return attachModelBinding({
114
+ id: modelId,
115
+ name: modelId,
116
+ api: CLOUDFLARE_AI_BINDING_API,
117
+ provider: def.provider ?? CLOUDFLARE_AI_BINDING_PROVIDER,
118
+ baseUrl: "",
119
+ reasoning: false,
120
+ input: ["text"],
121
+ cost: {
122
+ input: 0,
123
+ output: 0,
124
+ cacheRead: 0,
125
+ cacheWrite: 0
126
+ },
127
+ contextWindow: 0,
128
+ maxTokens: 0
129
+ }, def.binding);
130
+ return {
131
+ id: modelId,
132
+ name: modelId,
133
+ api: def.api,
134
+ provider: def.provider ?? name,
135
+ baseUrl: def.baseUrl,
136
+ reasoning: false,
137
+ input: ["text"],
138
+ cost: {
139
+ input: 0,
140
+ output: 0,
141
+ cacheRead: 0,
142
+ cacheWrite: 0
143
+ },
144
+ contextWindow: 0,
145
+ maxTokens: 0,
146
+ headers: def.headers
147
+ };
148
+ }
149
+ /**
150
+ * Compute the provider slug emitted on the resolved Model.
151
+ */
152
+ function effectiveProviderSlug(name, def) {
153
+ if (isCloudflareBindingRegistration(def)) return def.provider ?? CLOUDFLARE_AI_BINDING_PROVIDER;
154
+ return def.provider ?? name;
155
+ }
156
+
157
+ //#endregion
158
+ export { registerApiProvider$1 as a, CLOUDFLARE_AI_BINDING_API as c, getRegisteredApiKey as i, getModelBinding as n, registerProvider as o, getProviderConfiguration as r, resolveRegisteredModel as s, configureProvider as t };