@flue/sdk 0.1.1 → 0.1.3
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 +4 -9
- package/dist/agent-BYG0nVbQ.mjs +432 -0
- package/dist/client.d.mts +2 -7
- package/dist/client.mjs +4 -9
- package/dist/cloudflare/index.d.mts +6 -2
- package/dist/cloudflare/index.mjs +26 -2
- package/dist/command-helpers-BPcSV93o.d.mts +21 -0
- package/dist/command-helpers-CxRhK1my.mjs +37 -0
- package/dist/index.d.mts +2 -13
- package/dist/index.mjs +48 -60
- package/dist/internal.d.mts +15 -0
- package/dist/internal.mjs +6 -0
- package/dist/node/index.d.mts +14 -0
- package/dist/node/index.mjs +75 -0
- package/dist/sandbox.d.mts +1 -1
- package/dist/sandbox.mjs +2 -1
- package/dist/{session-0gnaB_aY.mjs → session-CiAMTsLZ.mjs} +28 -438
- package/dist/{types-C97_qJ21.d.mts → types-BZPltYah.d.mts} +8 -2
- package/package.json +9 -1
|
@@ -1,437 +1,9 @@
|
|
|
1
|
+
import { i as loadSkillByPath, n as createTools, r as discoverSessionContext, t as BUILTIN_TOOL_NAMES } from "./agent-BYG0nVbQ.mjs";
|
|
2
|
+
import { completeSimple, isContextOverflow } from "@mariozechner/pi-ai";
|
|
1
3
|
import { Agent } from "@mariozechner/pi-agent-core";
|
|
2
|
-
import { Type, completeSimple, isContextOverflow } from "@mariozechner/pi-ai";
|
|
3
4
|
import { toJsonSchema } from "@valibot/to-json-schema";
|
|
4
5
|
import * as v from "valibot";
|
|
5
6
|
|
|
6
|
-
//#region src/context.ts
|
|
7
|
-
/** Parse optional YAML frontmatter (--- delimited). Basic `key: value` only. */
|
|
8
|
-
function parseFrontmatterFile(content, defaultName) {
|
|
9
|
-
const frontmatterMatch = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/);
|
|
10
|
-
if (!frontmatterMatch) return {
|
|
11
|
-
name: defaultName,
|
|
12
|
-
description: "",
|
|
13
|
-
body: content.trim(),
|
|
14
|
-
frontmatter: {}
|
|
15
|
-
};
|
|
16
|
-
const rawFrontmatter = frontmatterMatch[1] ?? "";
|
|
17
|
-
const body = frontmatterMatch[2] ?? "";
|
|
18
|
-
const frontmatter = {};
|
|
19
|
-
for (const line of rawFrontmatter.split("\n")) {
|
|
20
|
-
const match = line.match(/^(\w+):\s*(.+)$/);
|
|
21
|
-
if (match?.[1] && match[2]) frontmatter[match[1]] = match[2].trim();
|
|
22
|
-
}
|
|
23
|
-
return {
|
|
24
|
-
name: frontmatter.name || defaultName,
|
|
25
|
-
description: frontmatter.description || "",
|
|
26
|
-
body: body.trim(),
|
|
27
|
-
frontmatter
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
/** Read AGENTS.md (and CLAUDE.md if present) from a directory. Returns concatenated contents. */
|
|
31
|
-
async function readAgentsMd(env, basePath) {
|
|
32
|
-
const parts = [];
|
|
33
|
-
for (const filename of ["AGENTS.md", "CLAUDE.md"]) {
|
|
34
|
-
const filePath = basePath.endsWith("/") ? basePath + filename : `${basePath}/${filename}`;
|
|
35
|
-
if (await env.exists(filePath)) {
|
|
36
|
-
const content = await env.readFile(filePath);
|
|
37
|
-
parts.push(content.trim());
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
return parts.join("\n\n");
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Load a skill directly by relative path under `.agents/skills/`.
|
|
44
|
-
*
|
|
45
|
-
* The path is taken as-is — no extension is auto-appended. Callers reference
|
|
46
|
-
* the full filename, e.g. `'triage/reproduce.md'`. Returns `null` if the file
|
|
47
|
-
* doesn't exist.
|
|
48
|
-
*
|
|
49
|
-
* Used as a fallback by `session.skill()` when the requested name doesn't match
|
|
50
|
-
* a discovered skill's frontmatter `name:` field. Lets users organise skills as
|
|
51
|
-
* a pack of sibling markdown files under one directory (orchestration SKILL.md
|
|
52
|
-
* + stage files) without forcing each stage into its own `SKILL.md` subdirectory.
|
|
53
|
-
*/
|
|
54
|
-
async function loadSkillByPath(env, basePath, relPath) {
|
|
55
|
-
const filePath = `${basePath.endsWith("/") ? `${basePath}.agents/skills` : `${basePath}/.agents/skills`}/${relPath}`;
|
|
56
|
-
if (!await env.exists(filePath)) return null;
|
|
57
|
-
const parsed = parseFrontmatterFile(await env.readFile(filePath), relPath.replace(/\.(md|markdown)$/i, ""));
|
|
58
|
-
return {
|
|
59
|
-
name: parsed.name,
|
|
60
|
-
description: parsed.description,
|
|
61
|
-
instructions: parsed.body
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
/** Discover skills from .agents/skills/<name>/SKILL.md under basePath. */
|
|
65
|
-
async function discoverLocalSkills(env, basePath) {
|
|
66
|
-
const skillsDir = basePath.endsWith("/") ? `${basePath}.agents/skills` : `${basePath}/.agents/skills`;
|
|
67
|
-
if (!await env.exists(skillsDir)) return {};
|
|
68
|
-
const skills = {};
|
|
69
|
-
const entries = await env.readdir(skillsDir);
|
|
70
|
-
for (const entry of entries) {
|
|
71
|
-
const skillDir = `${skillsDir}/${entry}`;
|
|
72
|
-
try {
|
|
73
|
-
if (!(await env.stat(skillDir)).isDirectory) continue;
|
|
74
|
-
} catch {
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
const skillMdPath = `${skillDir}/SKILL.md`;
|
|
78
|
-
if (!await env.exists(skillMdPath)) continue;
|
|
79
|
-
const parsed = parseFrontmatterFile(await env.readFile(skillMdPath), entry);
|
|
80
|
-
skills[parsed.name] = {
|
|
81
|
-
name: parsed.name,
|
|
82
|
-
description: parsed.description,
|
|
83
|
-
instructions: parsed.body
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
return skills;
|
|
87
|
-
}
|
|
88
|
-
function composeSystemPrompt(agentsMd, skills, env) {
|
|
89
|
-
const parts = [];
|
|
90
|
-
if (agentsMd) parts.push(agentsMd);
|
|
91
|
-
const skillEntries = Object.values(skills);
|
|
92
|
-
if (skillEntries.length > 0) {
|
|
93
|
-
parts.push("", "## Available Skills", "");
|
|
94
|
-
for (const skill of skillEntries) {
|
|
95
|
-
const desc = skill.description ? ` - ${skill.description}` : "";
|
|
96
|
-
parts.push(`- **${skill.name}**${desc}`);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
if (env) {
|
|
100
|
-
const date = (/* @__PURE__ */ new Date()).toLocaleDateString("en-US", {
|
|
101
|
-
weekday: "short",
|
|
102
|
-
year: "numeric",
|
|
103
|
-
month: "short",
|
|
104
|
-
day: "numeric"
|
|
105
|
-
});
|
|
106
|
-
parts.push("", `Date: ${date}`);
|
|
107
|
-
parts.push(`Working directory: ${env.cwd}`);
|
|
108
|
-
if (env.directoryListing && env.directoryListing.length > 0) parts.push("", "Directory structure:", env.directoryListing.join("\n"));
|
|
109
|
-
}
|
|
110
|
-
return parts.join("\n");
|
|
111
|
-
}
|
|
112
|
-
/** Discover AGENTS.md, local skills, and directory listing from the session's cwd. */
|
|
113
|
-
async function discoverSessionContext(env) {
|
|
114
|
-
const cwd = env.cwd;
|
|
115
|
-
const agentsMd = await readAgentsMd(env, cwd);
|
|
116
|
-
const skills = await discoverLocalSkills(env, cwd);
|
|
117
|
-
let directoryListing;
|
|
118
|
-
try {
|
|
119
|
-
directoryListing = await env.readdir(cwd);
|
|
120
|
-
} catch {}
|
|
121
|
-
return {
|
|
122
|
-
systemPrompt: composeSystemPrompt(agentsMd, skills, {
|
|
123
|
-
cwd,
|
|
124
|
-
directoryListing
|
|
125
|
-
}),
|
|
126
|
-
skills
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
//#endregion
|
|
131
|
-
//#region src/agent.ts
|
|
132
|
-
const MAX_READ_LINES = 2e3;
|
|
133
|
-
const MAX_READ_BYTES = 50 * 1024;
|
|
134
|
-
const MAX_GREP_MATCHES = 100;
|
|
135
|
-
const MAX_GREP_LINE_LENGTH = 500;
|
|
136
|
-
const MAX_GLOB_RESULTS = 1e3;
|
|
137
|
-
const BUILTIN_TOOL_NAMES = new Set([
|
|
138
|
-
"read",
|
|
139
|
-
"write",
|
|
140
|
-
"edit",
|
|
141
|
-
"bash",
|
|
142
|
-
"grep",
|
|
143
|
-
"glob"
|
|
144
|
-
]);
|
|
145
|
-
function createTools(env) {
|
|
146
|
-
return [
|
|
147
|
-
createReadTool(env),
|
|
148
|
-
createWriteTool(env),
|
|
149
|
-
createEditTool(env),
|
|
150
|
-
createBashTool(env),
|
|
151
|
-
createGrepTool(env),
|
|
152
|
-
createGlobTool(env)
|
|
153
|
-
];
|
|
154
|
-
}
|
|
155
|
-
function createReadTool(env) {
|
|
156
|
-
return {
|
|
157
|
-
name: "read",
|
|
158
|
-
label: "Read File",
|
|
159
|
-
description: "Read a file or list a directory. For files, output is truncated to 2000 lines or 50KB — use offset/limit for large files. For directories, returns the list of entries.",
|
|
160
|
-
parameters: Type.Object({
|
|
161
|
-
path: Type.String({ description: "Path to the file to read" }),
|
|
162
|
-
offset: Type.Optional(Type.Number({ description: "Line number to start from (1-indexed)" })),
|
|
163
|
-
limit: Type.Optional(Type.Number({ description: "Maximum number of lines to read" }))
|
|
164
|
-
}),
|
|
165
|
-
async execute(_toolCallId, params, signal) {
|
|
166
|
-
throwIfAborted(signal);
|
|
167
|
-
try {
|
|
168
|
-
if ((await env.stat(params.path)).isDirectory) {
|
|
169
|
-
const entries = await env.readdir(params.path);
|
|
170
|
-
return {
|
|
171
|
-
content: [{
|
|
172
|
-
type: "text",
|
|
173
|
-
text: entries.join("\n") || "(empty directory)"
|
|
174
|
-
}],
|
|
175
|
-
details: {
|
|
176
|
-
path: params.path,
|
|
177
|
-
isDirectory: true,
|
|
178
|
-
entries: entries.length
|
|
179
|
-
}
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
} catch {}
|
|
183
|
-
const allLines = (await env.readFile(params.path)).split("\n");
|
|
184
|
-
const startLine = params.offset ? Math.max(0, params.offset - 1) : 0;
|
|
185
|
-
if (startLine >= allLines.length) throw new Error(`Offset ${params.offset} is beyond end of file (${allLines.length} lines total)`);
|
|
186
|
-
const endLine = params.limit ? startLine + params.limit : allLines.length;
|
|
187
|
-
const { text: truncatedText, wasTruncated } = truncateHead(allLines.slice(startLine, endLine), MAX_READ_LINES, MAX_READ_BYTES);
|
|
188
|
-
let output = truncatedText;
|
|
189
|
-
if (wasTruncated) {
|
|
190
|
-
const shownEnd = startLine + truncatedText.split("\n").length;
|
|
191
|
-
output += `\n\n[Showing lines ${startLine + 1}-${shownEnd} of ${allLines.length}. Use offset=${shownEnd + 1} to continue.]`;
|
|
192
|
-
}
|
|
193
|
-
return {
|
|
194
|
-
content: [{
|
|
195
|
-
type: "text",
|
|
196
|
-
text: output
|
|
197
|
-
}],
|
|
198
|
-
details: {
|
|
199
|
-
path: params.path,
|
|
200
|
-
lines: allLines.length
|
|
201
|
-
}
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
function createWriteTool(env) {
|
|
207
|
-
return {
|
|
208
|
-
name: "write",
|
|
209
|
-
label: "Write File",
|
|
210
|
-
description: "Write content to a file. Creates the file and parent directories if they do not exist.",
|
|
211
|
-
parameters: Type.Object({
|
|
212
|
-
path: Type.String({ description: "Path to the file to write" }),
|
|
213
|
-
content: Type.String({ description: "Content to write to the file" })
|
|
214
|
-
}),
|
|
215
|
-
async execute(_toolCallId, params, signal) {
|
|
216
|
-
throwIfAborted(signal);
|
|
217
|
-
const resolved = env.resolvePath(params.path);
|
|
218
|
-
const dir = resolved.replace(/\/[^/]*$/, "");
|
|
219
|
-
if (dir && dir !== resolved) await env.mkdir(dir, { recursive: true });
|
|
220
|
-
await env.writeFile(resolved, params.content);
|
|
221
|
-
return {
|
|
222
|
-
content: [{
|
|
223
|
-
type: "text",
|
|
224
|
-
text: `Successfully wrote ${params.content.length} bytes to ${params.path}`
|
|
225
|
-
}],
|
|
226
|
-
details: {
|
|
227
|
-
path: params.path,
|
|
228
|
-
size: params.content.length
|
|
229
|
-
}
|
|
230
|
-
};
|
|
231
|
-
}
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
function createEditTool(env) {
|
|
235
|
-
return {
|
|
236
|
-
name: "edit",
|
|
237
|
-
label: "Edit File",
|
|
238
|
-
description: "Edit a file using exact text replacement. The oldText must match a unique region of the file. Use replaceAll to replace all occurrences.",
|
|
239
|
-
parameters: Type.Object({
|
|
240
|
-
path: Type.String({ description: "Path to the file to edit" }),
|
|
241
|
-
oldText: Type.String({ description: "Exact text to find (must be unique)" }),
|
|
242
|
-
newText: Type.String({ description: "Replacement text" }),
|
|
243
|
-
replaceAll: Type.Optional(Type.Boolean({ description: "Replace all occurrences" }))
|
|
244
|
-
}),
|
|
245
|
-
async execute(_toolCallId, params, signal) {
|
|
246
|
-
throwIfAborted(signal);
|
|
247
|
-
const content = await env.readFile(params.path);
|
|
248
|
-
if (params.replaceAll) {
|
|
249
|
-
const newContent = content.replaceAll(params.oldText, params.newText);
|
|
250
|
-
if (newContent === content) throw new Error(`Could not find the text in ${params.path}. No changes made.`);
|
|
251
|
-
await env.writeFile(params.path, newContent);
|
|
252
|
-
const count = content.split(params.oldText).length - 1;
|
|
253
|
-
return {
|
|
254
|
-
content: [{
|
|
255
|
-
type: "text",
|
|
256
|
-
text: `Replaced ${count} occurrences in ${params.path}`
|
|
257
|
-
}],
|
|
258
|
-
details: {
|
|
259
|
-
path: params.path,
|
|
260
|
-
replacements: count
|
|
261
|
-
}
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
|
-
const occurrences = countOccurrences(content, params.oldText);
|
|
265
|
-
if (occurrences === 0) throw new Error(`Could not find the exact text in ${params.path}. Make sure your oldText matches exactly, including whitespace and indentation.`);
|
|
266
|
-
if (occurrences > 1) throw new Error(`Found ${occurrences} occurrences of the text in ${params.path}. Provide more surrounding context to make the match unique, or use replaceAll.`);
|
|
267
|
-
const newContent = content.replace(params.oldText, params.newText);
|
|
268
|
-
await env.writeFile(params.path, newContent);
|
|
269
|
-
return {
|
|
270
|
-
content: [{
|
|
271
|
-
type: "text",
|
|
272
|
-
text: `Successfully edited ${params.path}`
|
|
273
|
-
}],
|
|
274
|
-
details: { path: params.path }
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
function createBashTool(env) {
|
|
280
|
-
return {
|
|
281
|
-
name: "bash",
|
|
282
|
-
label: "Run Command",
|
|
283
|
-
description: "Execute a bash command. Returns stdout and stderr. Output is truncated to the last 2000 lines or 50KB.",
|
|
284
|
-
parameters: Type.Object({
|
|
285
|
-
command: Type.String({ description: "Bash command to execute" }),
|
|
286
|
-
timeout: Type.Optional(Type.Number({ description: "Timeout in seconds" }))
|
|
287
|
-
}),
|
|
288
|
-
async execute(_toolCallId, params, signal) {
|
|
289
|
-
throwIfAborted(signal);
|
|
290
|
-
return formatBashResult(await env.exec(params.command), params.command);
|
|
291
|
-
}
|
|
292
|
-
};
|
|
293
|
-
}
|
|
294
|
-
function formatBashResult(result, command) {
|
|
295
|
-
const { text: output } = truncateTail((result.stdout + (result.stderr ? "\n" + result.stderr : "")).trim(), MAX_READ_LINES, MAX_READ_BYTES);
|
|
296
|
-
if (result.exitCode !== 0) throw new Error(`${output}\n\nCommand exited with code ${result.exitCode}`);
|
|
297
|
-
return {
|
|
298
|
-
content: [{
|
|
299
|
-
type: "text",
|
|
300
|
-
text: output || "(no output)"
|
|
301
|
-
}],
|
|
302
|
-
details: {
|
|
303
|
-
command,
|
|
304
|
-
exitCode: result.exitCode
|
|
305
|
-
}
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
|
-
function createGrepTool(env) {
|
|
309
|
-
return {
|
|
310
|
-
name: "grep",
|
|
311
|
-
label: "Search Files",
|
|
312
|
-
description: "Search file contents for a regex pattern. Returns matching lines with file paths and line numbers.",
|
|
313
|
-
parameters: Type.Object({
|
|
314
|
-
pattern: Type.String({ description: "Search pattern (regex)" }),
|
|
315
|
-
path: Type.Optional(Type.String({ description: "Directory or file to search (default: .)" })),
|
|
316
|
-
include: Type.Optional(Type.String({ description: "Glob filter, e.g. \"*.ts\"" }))
|
|
317
|
-
}),
|
|
318
|
-
async execute(_toolCallId, params, signal) {
|
|
319
|
-
throwIfAborted(signal);
|
|
320
|
-
const searchPath = params.path || ".";
|
|
321
|
-
let cmd = `grep -rn "${escapeShellArg(params.pattern)}" ${escapeShellArg(searchPath)}`;
|
|
322
|
-
if (params.include) cmd = `grep -rn --include="${escapeShellArg(params.include)}" "${escapeShellArg(params.pattern)}" ${escapeShellArg(searchPath)}`;
|
|
323
|
-
const result = await env.exec(cmd);
|
|
324
|
-
if (result.exitCode === 1 && !result.stdout.trim()) return {
|
|
325
|
-
content: [{
|
|
326
|
-
type: "text",
|
|
327
|
-
text: "No matches found."
|
|
328
|
-
}],
|
|
329
|
-
details: { matchCount: 0 }
|
|
330
|
-
};
|
|
331
|
-
if (result.exitCode > 1) throw new Error(`grep failed: ${result.stderr}`);
|
|
332
|
-
const lines = result.stdout.trim().split("\n");
|
|
333
|
-
let finalOutput = lines.slice(0, MAX_GREP_MATCHES).map((line) => line.length > MAX_GREP_LINE_LENGTH ? line.slice(0, MAX_GREP_LINE_LENGTH) + "..." : line).join("\n");
|
|
334
|
-
if (lines.length > MAX_GREP_MATCHES) finalOutput += `\n\n[Showing ${MAX_GREP_MATCHES} of ${lines.length} matches. Narrow your search.]`;
|
|
335
|
-
return {
|
|
336
|
-
content: [{
|
|
337
|
-
type: "text",
|
|
338
|
-
text: finalOutput
|
|
339
|
-
}],
|
|
340
|
-
details: { matchCount: Math.min(lines.length, MAX_GREP_MATCHES) }
|
|
341
|
-
};
|
|
342
|
-
}
|
|
343
|
-
};
|
|
344
|
-
}
|
|
345
|
-
function createGlobTool(env) {
|
|
346
|
-
return {
|
|
347
|
-
name: "glob",
|
|
348
|
-
label: "Find Files",
|
|
349
|
-
description: "Find files by glob pattern. Returns matching file paths.",
|
|
350
|
-
parameters: Type.Object({
|
|
351
|
-
pattern: Type.String({ description: "Glob pattern, e.g. \"**/*.ts\"" }),
|
|
352
|
-
path: Type.Optional(Type.String({ description: "Directory to search in (default: .)" }))
|
|
353
|
-
}),
|
|
354
|
-
async execute(_toolCallId, params, signal) {
|
|
355
|
-
throwIfAborted(signal);
|
|
356
|
-
const cmd = `find ${escapeShellArg(params.path || ".")} -type f -name "${escapeShellArg(params.pattern)}" 2>/dev/null | head -${MAX_GLOB_RESULTS}`;
|
|
357
|
-
const result = await env.exec(cmd);
|
|
358
|
-
if (result.exitCode !== 0 && !result.stdout.trim()) return {
|
|
359
|
-
content: [{
|
|
360
|
-
type: "text",
|
|
361
|
-
text: "No files found matching pattern."
|
|
362
|
-
}],
|
|
363
|
-
details: { matchCount: 0 }
|
|
364
|
-
};
|
|
365
|
-
const paths = result.stdout.trim().split("\n").filter(Boolean);
|
|
366
|
-
if (paths.length === 0) return {
|
|
367
|
-
content: [{
|
|
368
|
-
type: "text",
|
|
369
|
-
text: "No files found matching pattern."
|
|
370
|
-
}],
|
|
371
|
-
details: { matchCount: 0 }
|
|
372
|
-
};
|
|
373
|
-
return {
|
|
374
|
-
content: [{
|
|
375
|
-
type: "text",
|
|
376
|
-
text: paths.join("\n")
|
|
377
|
-
}],
|
|
378
|
-
details: { matchCount: paths.length }
|
|
379
|
-
};
|
|
380
|
-
}
|
|
381
|
-
};
|
|
382
|
-
}
|
|
383
|
-
function throwIfAborted(signal) {
|
|
384
|
-
if (signal?.aborted) throw new Error("Operation aborted");
|
|
385
|
-
}
|
|
386
|
-
function countOccurrences(str, substr) {
|
|
387
|
-
let count = 0;
|
|
388
|
-
let pos = str.indexOf(substr, 0);
|
|
389
|
-
while (pos !== -1) {
|
|
390
|
-
count++;
|
|
391
|
-
pos = str.indexOf(substr, pos + substr.length);
|
|
392
|
-
}
|
|
393
|
-
return count;
|
|
394
|
-
}
|
|
395
|
-
function escapeShellArg(arg) {
|
|
396
|
-
return arg.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\$/g, "\\$").replace(/`/g, "\\`");
|
|
397
|
-
}
|
|
398
|
-
function truncateHead(lines, maxLines, maxBytes) {
|
|
399
|
-
let result = "";
|
|
400
|
-
let lineCount = 0;
|
|
401
|
-
let wasTruncated = false;
|
|
402
|
-
for (const line of lines) {
|
|
403
|
-
if (lineCount >= maxLines) {
|
|
404
|
-
wasTruncated = true;
|
|
405
|
-
break;
|
|
406
|
-
}
|
|
407
|
-
const next = lineCount === 0 ? line : "\n" + line;
|
|
408
|
-
if (result.length + next.length > maxBytes) {
|
|
409
|
-
wasTruncated = true;
|
|
410
|
-
break;
|
|
411
|
-
}
|
|
412
|
-
result += next;
|
|
413
|
-
lineCount++;
|
|
414
|
-
}
|
|
415
|
-
return {
|
|
416
|
-
text: result,
|
|
417
|
-
wasTruncated
|
|
418
|
-
};
|
|
419
|
-
}
|
|
420
|
-
function truncateTail(text, maxLines, maxBytes) {
|
|
421
|
-
const lines = text.split("\n");
|
|
422
|
-
if (lines.length <= maxLines && text.length <= maxBytes) return {
|
|
423
|
-
text,
|
|
424
|
-
wasTruncated: false
|
|
425
|
-
};
|
|
426
|
-
let result = lines.slice(-maxLines).join("\n");
|
|
427
|
-
if (result.length > maxBytes) result = result.slice(-maxBytes);
|
|
428
|
-
return {
|
|
429
|
-
text: result,
|
|
430
|
-
wasTruncated: true
|
|
431
|
-
};
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
//#endregion
|
|
435
7
|
//#region src/compaction.ts
|
|
436
8
|
const DEFAULT_COMPACTION_SETTINGS = {
|
|
437
9
|
enabled: true,
|
|
@@ -936,11 +508,13 @@ var Session = class Session {
|
|
|
936
508
|
compactionAbortController;
|
|
937
509
|
eventCallback;
|
|
938
510
|
builtinTools;
|
|
939
|
-
|
|
511
|
+
sessionCommands;
|
|
512
|
+
constructor(id, config, env, store, existingData, onAgentEvent, sessionCommands) {
|
|
940
513
|
this.id = id;
|
|
941
514
|
this.config = config;
|
|
942
515
|
this.env = env;
|
|
943
516
|
this.store = store;
|
|
517
|
+
this.sessionCommands = sessionCommands ?? [];
|
|
944
518
|
this.metadata = existingData?.metadata ?? {};
|
|
945
519
|
this.createdAt = existingData?.createdAt;
|
|
946
520
|
this.lastCompaction = existingData?.lastCompaction;
|
|
@@ -1014,8 +588,9 @@ var Session = class Session {
|
|
|
1014
588
|
const promptWithRole = this.injectRoleInstructions(text, options?.role);
|
|
1015
589
|
const schema = options?.result;
|
|
1016
590
|
const fullPrompt = buildPromptText(promptWithRole, schema);
|
|
1017
|
-
|
|
1018
|
-
|
|
591
|
+
const effectiveCommands = this.mergeCommands(options?.commands);
|
|
592
|
+
if (effectiveCommands.length > 0) this.assertCommandSupport(effectiveCommands);
|
|
593
|
+
const registeredCommandNames = this.registerCommands(effectiveCommands);
|
|
1019
594
|
const registeredToolNames = options?.tools ? this.registerCustomTools(options.tools) : [];
|
|
1020
595
|
try {
|
|
1021
596
|
await this.agent.prompt(fullPrompt);
|
|
@@ -1044,8 +619,9 @@ var Session = class Session {
|
|
|
1044
619
|
const schema = options?.result;
|
|
1045
620
|
const skillPrompt = buildSkillPrompt(registeredSkill.instructions, options?.args, schema);
|
|
1046
621
|
const promptWithRole = this.injectRoleInstructions(skillPrompt, options?.role);
|
|
1047
|
-
|
|
1048
|
-
|
|
622
|
+
const effectiveCommands = this.mergeCommands(options?.commands);
|
|
623
|
+
if (effectiveCommands.length > 0) this.assertCommandSupport(effectiveCommands);
|
|
624
|
+
const registeredCommandNames = this.registerCommands(effectiveCommands);
|
|
1049
625
|
const registeredToolNames = options?.tools ? this.registerCustomTools(options.tools) : [];
|
|
1050
626
|
try {
|
|
1051
627
|
await this.agent.prompt(promptWithRole);
|
|
@@ -1060,8 +636,9 @@ var Session = class Session {
|
|
|
1060
636
|
}
|
|
1061
637
|
}
|
|
1062
638
|
async shell(command, options) {
|
|
1063
|
-
|
|
1064
|
-
|
|
639
|
+
const effectiveCommands = this.mergeCommands(options?.commands);
|
|
640
|
+
if (effectiveCommands.length > 0) this.assertCommandSupport(effectiveCommands);
|
|
641
|
+
const registeredNames = this.registerCommands(effectiveCommands);
|
|
1065
642
|
try {
|
|
1066
643
|
const result = await this.env.exec(command, {
|
|
1067
644
|
env: options?.env,
|
|
@@ -1178,6 +755,19 @@ var Session = class Session {
|
|
|
1178
755
|
if (commands.length === 0) return;
|
|
1179
756
|
if (!this.env.commandSupport) throw new Error("[flue] Cannot use commands: this environment does not support command registration. Commands are only available in isolate sandbox mode. Remote sandboxes handle command execution at the platform level.");
|
|
1180
757
|
}
|
|
758
|
+
/**
|
|
759
|
+
* Merge session-wide `commands` (from init()) with per-call commands. When
|
|
760
|
+
* both define a command with the same name, the per-call entry wins for
|
|
761
|
+
* that call.
|
|
762
|
+
*/
|
|
763
|
+
mergeCommands(perCall) {
|
|
764
|
+
if (!perCall || perCall.length === 0) return this.sessionCommands;
|
|
765
|
+
if (this.sessionCommands.length === 0) return perCall;
|
|
766
|
+
const byName = /* @__PURE__ */ new Map();
|
|
767
|
+
for (const cmd of this.sessionCommands) byName.set(cmd.name, cmd);
|
|
768
|
+
for (const cmd of perCall) byName.set(cmd.name, cmd);
|
|
769
|
+
return Array.from(byName.values());
|
|
770
|
+
}
|
|
1181
771
|
registerCommands(commands) {
|
|
1182
772
|
if (!this.env.commandSupport || commands.length === 0) return [];
|
|
1183
773
|
const names = [];
|
|
@@ -1350,4 +940,4 @@ function normalizePath(p) {
|
|
|
1350
940
|
}
|
|
1351
941
|
|
|
1352
942
|
//#endregion
|
|
1353
|
-
export {
|
|
943
|
+
export { Session as n, normalizePath as r, InMemorySessionStore as t };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { AgentMessage } from "@mariozechner/pi-agent-core";
|
|
2
1
|
import { Model, TSchema } from "@mariozechner/pi-ai";
|
|
2
|
+
import { AgentMessage } from "@mariozechner/pi-agent-core";
|
|
3
3
|
import * as v from "valibot";
|
|
4
4
|
|
|
5
5
|
//#region src/types.d.ts
|
|
@@ -146,6 +146,13 @@ interface SessionInit {
|
|
|
146
146
|
* Precedence (highest wins): per-call `model` > role `model` > session `model` > build-time default.
|
|
147
147
|
*/
|
|
148
148
|
model?: string;
|
|
149
|
+
/**
|
|
150
|
+
* Session-wide commands. Every prompt(), skill(), and shell() call inherits
|
|
151
|
+
* this list. Per-call `commands` are merged on top — if a per-call command
|
|
152
|
+
* shares a name with a session command, the per-call version wins for that
|
|
153
|
+
* call.
|
|
154
|
+
*/
|
|
155
|
+
commands?: Command[];
|
|
149
156
|
}
|
|
150
157
|
interface FlueSession {
|
|
151
158
|
prompt<S extends v.GenericSchema>(text: string, options: PromptOptions<S> & {
|
|
@@ -317,7 +324,6 @@ interface BuildContext {
|
|
|
317
324
|
roles: Record<string, Role>;
|
|
318
325
|
agentDir: string;
|
|
319
326
|
options: BuildOptions;
|
|
320
|
-
resolveSDKImport: (module: string) => string;
|
|
321
327
|
}
|
|
322
328
|
/** Controls the build output format for a target platform. */
|
|
323
329
|
interface BuildPlugin {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flue/sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"exports": {
|
|
@@ -16,9 +16,17 @@
|
|
|
16
16
|
"types": "./dist/sandbox.d.mts",
|
|
17
17
|
"import": "./dist/sandbox.mjs"
|
|
18
18
|
},
|
|
19
|
+
"./internal": {
|
|
20
|
+
"types": "./dist/internal.d.mts",
|
|
21
|
+
"import": "./dist/internal.mjs"
|
|
22
|
+
},
|
|
19
23
|
"./cloudflare": {
|
|
20
24
|
"types": "./dist/cloudflare/index.d.mts",
|
|
21
25
|
"import": "./dist/cloudflare/index.mjs"
|
|
26
|
+
},
|
|
27
|
+
"./node": {
|
|
28
|
+
"types": "./dist/node/index.d.mts",
|
|
29
|
+
"import": "./dist/node/index.mjs"
|
|
22
30
|
}
|
|
23
31
|
},
|
|
24
32
|
"main": "./dist/index.mjs",
|