@flue/sdk 0.3.0 → 0.3.2

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.
@@ -0,0 +1,461 @@
1
+ import { Model, TSchema } from "@mariozechner/pi-ai";
2
+ import { AgentMessage } from "@mariozechner/pi-agent-core";
3
+ import * as v from "valibot";
4
+
5
+ //#region src/types.d.ts
6
+ interface Skill {
7
+ name: string;
8
+ description: string;
9
+ /** Markdown body of SKILL.md (below the frontmatter). */
10
+ instructions: string;
11
+ }
12
+ interface Role {
13
+ name: string;
14
+ description: string;
15
+ /** Markdown body of the role file (below the frontmatter). */
16
+ instructions: string;
17
+ model?: string;
18
+ }
19
+ /**
20
+ * An executable command that can be passed to prompt(), skill(), or shell().
21
+ * Registered into just-bash for the duration of the call.
22
+ */
23
+ interface Command {
24
+ name: string;
25
+ execute(args: string[]): Promise<{
26
+ stdout: string;
27
+ stderr: string;
28
+ exitCode: number;
29
+ }>;
30
+ }
31
+ /** @deprecated Use `Command` with `defineCommand()` instead. */
32
+ interface CommandDef {
33
+ name: string;
34
+ env?: Record<string, string>;
35
+ }
36
+ type ToolParameters = TSchema | Record<string, unknown>;
37
+ /**
38
+ * Custom tool passed to init(), prompt(), skill(), or task(). init() tools are
39
+ * available to every session call; prompt/skill/task tools are scoped to that call.
40
+ * Parameters are JSON Schema-compatible. Use `Type` from `@flue/sdk/client` for
41
+ * hand-written tools, or pass schemas discovered from adapters such as MCP.
42
+ */
43
+ interface ToolDef<TParams extends ToolParameters = ToolParameters> {
44
+ /** Must be unique across built-in and custom tools. */
45
+ name: string;
46
+ /** Tells the LLM when and how to use this tool. */
47
+ description: string;
48
+ /** JSON Schema-compatible parameter schema. */
49
+ parameters: TParams;
50
+ /** Returns a string result sent back to the LLM. Thrown errors become tool errors. */
51
+ execute: (args: Record<string, any>, signal?: AbortSignal) => Promise<string>;
52
+ }
53
+ interface FileStat {
54
+ isFile: boolean;
55
+ isDirectory: boolean;
56
+ isSymbolicLink: boolean;
57
+ size: number;
58
+ mtime: Date;
59
+ }
60
+ /**
61
+ * Universal session environment interface. All sandbox modes (isolate, local, remote)
62
+ * implement this — no mode-specific branching needed in core logic.
63
+ *
64
+ * File methods accept both absolute and relative paths (resolved against `cwd`).
65
+ */
66
+ interface SessionEnv {
67
+ exec(command: string, options?: {
68
+ cwd?: string;
69
+ env?: Record<string, string>;
70
+ }): Promise<ShellResult>;
71
+ /** Create an operation-scoped environment, usually backed by a fresh Bash runtime. */
72
+ scope?(options?: {
73
+ commands?: Command[];
74
+ }): Promise<SessionEnv>;
75
+ readFile(path: string): Promise<string>;
76
+ readFileBuffer(path: string): Promise<Uint8Array>;
77
+ writeFile(path: string, content: string | Uint8Array): Promise<void>;
78
+ stat(path: string): Promise<FileStat>;
79
+ readdir(path: string): Promise<string[]>;
80
+ exists(path: string): Promise<boolean>;
81
+ mkdir(path: string, options?: {
82
+ recursive?: boolean;
83
+ }): Promise<void>;
84
+ rm(path: string, options?: {
85
+ recursive?: boolean;
86
+ force?: boolean;
87
+ }): Promise<void>;
88
+ cwd: string;
89
+ /**
90
+ * Resolve a relative path against cwd. Absolute paths pass through.
91
+ * File methods resolve internally — only needed when you need the absolute path
92
+ * for your own logic (e.g., extracting the parent directory).
93
+ */
94
+ resolvePath(p: string): string;
95
+ cleanup(): Promise<void>;
96
+ }
97
+ interface CompactionConfig {
98
+ enabled?: boolean;
99
+ /** Token buffer to keep free in the context window. Default: 16384 */
100
+ reserveTokens?: number;
101
+ /** Recent tokens to preserve (not summarized). Default: 20000 */
102
+ keepRecentTokens?: number;
103
+ }
104
+ interface AgentConfig {
105
+ /** Discovered at runtime from AGENTS.md + .agents/skills/ in the session's cwd. */
106
+ systemPrompt: string;
107
+ /** Discovered at runtime from .agents/skills/ in the session's cwd. */
108
+ skills: Record<string, Skill>;
109
+ roles: Record<string, Role>;
110
+ /**
111
+ * Agent-wide default model. Undefined by default — the user must set it via
112
+ * `init({ model: "provider/model-id" })` or pass `{ model }` at each prompt/
113
+ * skill/task call site. Calls with no model resolved throw clearly at runtime.
114
+ */
115
+ model: Model<any> | undefined;
116
+ /** Agent-wide default role. Per-session and per-call roles override this. */
117
+ role?: string;
118
+ /** Resolve a "provider/modelId" string to a Model instance. Throws on invalid input. */
119
+ resolveModel?: (modelString: string) => Model<any>;
120
+ compaction?: CompactionConfig;
121
+ }
122
+ /** Request context passed to agent handler functions. */
123
+ interface FlueContext {
124
+ readonly id: string;
125
+ readonly payload: any;
126
+ /** Platform env bindings (process.env on Node, Worker env on Cloudflare). */
127
+ readonly env: Record<string, any>;
128
+ /** Initialize an agent runtime with sandbox + persistence. */
129
+ init(options?: AgentInit): Promise<FlueAgent>;
130
+ }
131
+ /** All fields are optional — omitting gives platform defaults (empty sandbox, platform store, build-time model). */
132
+ interface AgentInit {
133
+ /** Agent/sandbox scope id. Defaults to the route/context id. */
134
+ id?: string;
135
+ /** Working directory for context discovery, tools, and shell calls. Defaults to the sandbox cwd. */
136
+ cwd?: string;
137
+ /**
138
+ * - `'empty'` (default): In-memory sandbox, no files, no host access.
139
+ * - `'local'`: Mounts process.cwd() at /workspace. Node only.
140
+ * - `BashFactory`: User-configured just-bash factory. Must return a fresh Bash-like instance.
141
+ * - `SandboxFactory`: Connector-wrapped external sandbox (Daytona, CF Containers, etc.).
142
+ */
143
+ sandbox?: 'empty' | 'local' | SandboxFactory | BashFactory;
144
+ /** Defaults to platform store (in-memory on Node, DO SQLite on Cloudflare). */
145
+ persist?: SessionStore;
146
+ /**
147
+ * Override the default model for this agent. Applies to all prompt() and skill()
148
+ * calls unless overridden at the call site.
149
+ *
150
+ * Format: `'provider/modelId'` (e.g. `'anthropic/claude-opus-4-20250514'`).
151
+ *
152
+ * Precedence (highest wins): per-call `model` > role `model` > agent `model` > build-time default.
153
+ */
154
+ model?: string;
155
+ /** Agent-wide default role. Overridden by session-level or per-call roles. */
156
+ role?: string;
157
+ /**
158
+ * Agent-wide tools. Every prompt(), skill(), and task() call can use these.
159
+ * Per-call tools are added on top and must not reuse the same names.
160
+ */
161
+ tools?: ToolDef[];
162
+ /**
163
+ * Agent-wide commands. Every prompt(), skill(), and shell() call inherits
164
+ * this list. Per-call `commands` are merged on top — if a per-call command
165
+ * shares a name with an agent command, the per-call version wins for that
166
+ * call.
167
+ */
168
+ commands?: Command[];
169
+ }
170
+ interface FlueAgent {
171
+ readonly id: string;
172
+ /** Get or create a session in this agent. Defaults to the "default" session. */
173
+ session(id?: string, options?: SessionOptions): Promise<FlueSession>;
174
+ /** Explicit session management helpers. */
175
+ readonly sessions: FlueSessions;
176
+ /** Run a shell command in the agent sandbox without recording it in a conversation. */
177
+ shell(command: string, options?: ShellOptions): Promise<ShellResult>;
178
+ /** Destroy the agent runtime and clean up the sandbox resources it owns. */
179
+ destroy(): Promise<void>;
180
+ }
181
+ interface FlueSessions {
182
+ /** Load an existing session. Throws if it does not exist. */
183
+ get(id?: string, options?: SessionOptions): Promise<FlueSession>;
184
+ /** Create a new session. Throws if it already exists. */
185
+ create(id?: string, options?: SessionOptions): Promise<FlueSession>;
186
+ /** Delete a session's stored conversation state. No-op when missing. */
187
+ delete(id?: string): Promise<void>;
188
+ }
189
+ interface SessionOptions {
190
+ /** Session-wide default role. Per-call roles override this. */
191
+ role?: string;
192
+ }
193
+ interface FlueSession {
194
+ readonly id: string;
195
+ prompt<S extends v.GenericSchema>(text: string, options: PromptOptions<S> & {
196
+ result: S;
197
+ }): Promise<v.InferOutput<S>>;
198
+ prompt(text: string, options?: PromptOptions): Promise<PromptResponse>;
199
+ shell(command: string, options?: ShellOptions): Promise<ShellResult>;
200
+ skill<S extends v.GenericSchema>(name: string, options: SkillOptions<S> & {
201
+ result: S;
202
+ }): Promise<v.InferOutput<S>>;
203
+ skill(name: string, options?: SkillOptions): Promise<PromptResponse>;
204
+ task<S extends v.GenericSchema>(text: string, options: TaskOptions<S> & {
205
+ result: S;
206
+ }): Promise<v.InferOutput<S>>;
207
+ task(text: string, options?: TaskOptions): Promise<PromptResponse>;
208
+ delete(): Promise<void>;
209
+ }
210
+ interface PromptResponse {
211
+ text: string;
212
+ }
213
+ interface SessionData {
214
+ version: 2;
215
+ entries: SessionEntry[];
216
+ leafId: string | null;
217
+ metadata: Record<string, any>;
218
+ createdAt: string;
219
+ updatedAt: string;
220
+ }
221
+ type SessionEntry = MessageEntry | CompactionEntry | BranchSummaryEntry;
222
+ interface SessionEntryBase {
223
+ type: string;
224
+ id: string;
225
+ parentId: string | null;
226
+ timestamp: string;
227
+ }
228
+ interface MessageEntry extends SessionEntryBase {
229
+ type: 'message';
230
+ message: AgentMessage;
231
+ source?: 'prompt' | 'skill' | 'shell' | 'task' | 'retry';
232
+ }
233
+ interface CompactionEntry extends SessionEntryBase {
234
+ type: 'compaction';
235
+ summary: string;
236
+ firstKeptEntryId: string;
237
+ tokensBefore: number;
238
+ details?: {
239
+ readFiles: string[];
240
+ modifiedFiles: string[];
241
+ };
242
+ }
243
+ interface BranchSummaryEntry extends SessionEntryBase {
244
+ type: 'branch_summary';
245
+ fromId: string;
246
+ summary: string;
247
+ details?: unknown;
248
+ }
249
+ interface SessionStore {
250
+ save(id: string, data: SessionData): Promise<void>;
251
+ load(id: string): Promise<SessionData | null>;
252
+ delete(id: string): Promise<void>;
253
+ }
254
+ /** All option fields are scoped to the duration of the call. */
255
+ interface PromptOptions<S extends v.GenericSchema | undefined = undefined> {
256
+ result?: S;
257
+ timeout?: number;
258
+ commands?: Command[];
259
+ tools?: ToolDef[];
260
+ role?: string;
261
+ /** e.g., 'anthropic/claude-sonnet-4-20250514' */
262
+ model?: string;
263
+ }
264
+ interface SkillOptions<S extends v.GenericSchema | undefined = undefined> {
265
+ args?: Record<string, unknown>;
266
+ result?: S;
267
+ timeout?: number;
268
+ commands?: Command[];
269
+ tools?: ToolDef[];
270
+ role?: string;
271
+ model?: string;
272
+ }
273
+ interface TaskOptions<S extends v.GenericSchema | undefined = undefined> {
274
+ result?: S;
275
+ commands?: Command[];
276
+ tools?: ToolDef[];
277
+ role?: string;
278
+ model?: string;
279
+ /** Working directory for the detached task session. Defaults to the parent session cwd. */
280
+ cwd?: string;
281
+ }
282
+ interface ShellOptions {
283
+ env?: Record<string, string>;
284
+ cwd?: string;
285
+ timeout?: number;
286
+ commands?: Command[];
287
+ }
288
+ interface ShellResult {
289
+ stdout: string;
290
+ stderr: string;
291
+ exitCode: number;
292
+ }
293
+ /** Wraps external sandboxes (Daytona, CF Containers, etc.) into Flue's SessionEnv. */
294
+ interface SandboxFactory {
295
+ createSessionEnv(options: {
296
+ id: string;
297
+ cwd?: string;
298
+ }): Promise<SessionEnv>;
299
+ }
300
+ /**
301
+ * Structural type for duck-type detection of just-bash `Bash` instances in init().
302
+ * Purely structural — no just-bash import, so client.ts stays platform-agnostic.
303
+ */
304
+ interface BashLike {
305
+ exec(command: string, options?: {
306
+ cwd?: string;
307
+ env?: Record<string, string>;
308
+ }): Promise<ShellResult>;
309
+ getCwd(): string;
310
+ fs: {
311
+ readFile(path: string, options?: any): Promise<string>;
312
+ readFileBuffer(path: string): Promise<Uint8Array>;
313
+ writeFile(path: string, content: string | Uint8Array, options?: any): Promise<void>;
314
+ stat(path: string): Promise<any>;
315
+ readdir(path: string): Promise<string[]>;
316
+ exists(path: string): Promise<boolean>;
317
+ mkdir(path: string, options?: {
318
+ recursive?: boolean;
319
+ }): Promise<void>;
320
+ rm(path: string, options?: {
321
+ recursive?: boolean;
322
+ force?: boolean;
323
+ }): Promise<void>;
324
+ resolvePath(base: string, path: string): string;
325
+ };
326
+ registerCommand?(cmd: any): void;
327
+ }
328
+ /** Factory for a fresh Bash-like runtime. Share `fs` inside the closure to persist files. */
329
+ type BashFactory = () => BashLike | Promise<BashLike>;
330
+ type FlueEvent = ({
331
+ type: 'agent_start';
332
+ } | {
333
+ type: 'text_delta';
334
+ text: string;
335
+ } | {
336
+ type: 'tool_start';
337
+ toolName: string;
338
+ toolCallId: string;
339
+ args?: any;
340
+ } | {
341
+ type: 'tool_end';
342
+ toolName: string;
343
+ toolCallId: string;
344
+ isError: boolean;
345
+ result?: any;
346
+ } | {
347
+ type: 'turn_end';
348
+ } | {
349
+ type: 'command_start';
350
+ command: string;
351
+ args: string[];
352
+ } | {
353
+ type: 'command_end';
354
+ command: string;
355
+ exitCode: number;
356
+ } | {
357
+ type: 'task_start';
358
+ taskId: string;
359
+ prompt: string;
360
+ role?: string;
361
+ cwd?: string;
362
+ } | {
363
+ type: 'task_end';
364
+ taskId: string;
365
+ isError: boolean;
366
+ result?: any;
367
+ } | {
368
+ type: 'compaction_start';
369
+ reason: 'threshold' | 'overflow';
370
+ estimatedTokens: number;
371
+ } | {
372
+ type: 'compaction_end';
373
+ messagesBefore: number;
374
+ messagesAfter: number;
375
+ } | {
376
+ type: 'idle';
377
+ } | {
378
+ type: 'error';
379
+ error: string;
380
+ }) & {
381
+ sessionId?: string;
382
+ parentSessionId?: string;
383
+ taskId?: string;
384
+ };
385
+ type FlueEventCallback = (event: FlueEvent) => void;
386
+ interface AgentInfo {
387
+ name: string;
388
+ filePath: string;
389
+ triggers: {
390
+ webhook?: boolean;
391
+ cron?: string;
392
+ };
393
+ }
394
+ interface BuildContext {
395
+ agents: AgentInfo[];
396
+ roles: Record<string, Role>;
397
+ /** The workspace root: the directory directly containing agents/ and roles/. */
398
+ workspaceDir: string;
399
+ /** Where dist/ is written. Typically the project root, independent of workspaceDir. */
400
+ outputDir: string;
401
+ options: BuildOptions;
402
+ }
403
+ /**
404
+ * Controls the build output format for a target platform.
405
+ *
406
+ * A plugin can either ship a fully-bundled JavaScript artifact (Node target)
407
+ * or hand over a TypeScript/ESM entry source that some downstream tool will
408
+ * bundle (Cloudflare target — wrangler does the bundling). Pre-bundling on
409
+ * top of a tool that bundles for itself causes subtle resolution conflicts
410
+ * (we hit this with `tar`/`fs`/etc. via `nodejs_compat`), so the Cloudflare
411
+ * path explicitly opts out.
412
+ */
413
+ interface BuildPlugin {
414
+ name: string;
415
+ /**
416
+ * The source of the entry point (TS or JS). May be async — the Cloudflare
417
+ * plugin reads the user's wrangler config (via wrangler's reader) which is
418
+ * a sync call but lives behind a lazy `await import('wrangler')`.
419
+ */
420
+ generateEntryPoint(ctx: BuildContext): string | Promise<string>;
421
+ /**
422
+ * Bundling strategy:
423
+ * - `'esbuild'` (default): run the SDK's esbuild pass to produce a
424
+ * bundled `dist/server.mjs`. Use when the deploy target is "just run
425
+ * this file" with no further bundling step.
426
+ * - `'none'`: skip esbuild. The entry is written as-is to `dist/` and
427
+ * becomes the input for whatever tool will deploy it (e.g. wrangler).
428
+ * The plugin must also implement `entryFilename` to set the file name.
429
+ */
430
+ bundle?: 'esbuild' | 'none';
431
+ /**
432
+ * The filename to use for the entry, written under `dist/`. Required when
433
+ * `bundle === 'none'`. For `bundle === 'esbuild'` the output is always
434
+ * `server.mjs` and this field is ignored.
435
+ */
436
+ entryFilename?: string;
437
+ /** esbuild options. Only consulted when `bundle === 'esbuild'`. */
438
+ esbuildOptions?(ctx: BuildContext): Record<string, any>;
439
+ /** Additional files to write to dist/ (e.g., wrangler.jsonc, Dockerfile). May be async. */
440
+ additionalOutputs?(ctx: BuildContext): Record<string, string> | Promise<Record<string, string>>;
441
+ }
442
+ interface BuildOptions {
443
+ /**
444
+ * The workspace directory: the directory directly containing agents/ and
445
+ * roles/. Pass an explicit path — no .flue/ waterfall is performed here.
446
+ * Callers that want the waterfall behavior (e.g. the CLI when --workspace
447
+ * is omitted) should resolve it themselves with `resolveWorkspaceFromCwd`.
448
+ */
449
+ workspaceDir: string;
450
+ /**
451
+ * Where to write the dist/ directory. Independent of workspaceDir — typically
452
+ * the project root, so platform config like wrangler.jsonc ends up where the
453
+ * deploy tool expects it.
454
+ */
455
+ outputDir: string;
456
+ target?: 'node' | 'cloudflare';
457
+ /** Overrides `target` when provided. */
458
+ plugin?: BuildPlugin;
459
+ }
460
+ //#endregion
461
+ export { TaskOptions as A, SessionEnv as C, ShellResult as D, ShellOptions as E, ToolParameters as M, Skill as O, SessionData as S, SessionStore as T, FlueSessions as _, BashLike as a, Role as b, BuildPlugin as c, FileStat as d, FlueAgent as f, FlueSession as g, FlueEventCallback as h, BashFactory as i, ToolDef as j, SkillOptions as k, Command as l, FlueEvent as m, AgentInfo as n, BuildContext as o, FlueContext as p, AgentInit as r, BuildOptions as s, AgentConfig as t, CommandDef as u, PromptOptions as v, SessionOptions as w, SandboxFactory as x, PromptResponse as y };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flue/sdk",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "exports": {
@@ -43,14 +43,22 @@
43
43
  "@valibot/to-json-schema": "^1.0.0",
44
44
  "esbuild": "^0.25.0",
45
45
  "hono": "^4.7.0",
46
- "jsonc-parser": "^3.3.1",
47
46
  "just-bash": "^2.14.2",
48
47
  "package-up": "^5.0.0",
49
48
  "valibot": "^1.0.0"
50
49
  },
51
50
  "devDependencies": {
52
51
  "@cloudflare/workers-types": "^4.20250410.0",
53
- "tsdown": "^0.20.3"
52
+ "tsdown": "^0.20.3",
53
+ "wrangler": "^4.83.0"
54
+ },
55
+ "peerDependencies": {
56
+ "wrangler": "^4.0.0"
57
+ },
58
+ "peerDependenciesMeta": {
59
+ "wrangler": {
60
+ "optional": true
61
+ }
54
62
  },
55
63
  "scripts": {
56
64
  "build": "tsdown",