@kevin5251984/guild 0.2.18 → 0.2.20
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/package.json +2 -2
- package/src/catalog/subagents.ts +2 -1
- package/src/chat-parts.ts +6 -1
- package/src/cli.ts +2 -5
- package/src/compact.ts +27 -0
- package/src/db.ts +236 -68
- package/src/generate.ts +59 -28
- package/src/handlers.ts +355 -84
- package/src/harness.ts +39 -10
- package/src/host-browse.ts +149 -4
- package/src/image-gen.ts +2 -1
- package/src/llm.ts +435 -71
- package/src/mcp.ts +32 -5
- package/src/memory.ts +69 -0
- package/src/mention.ts +189 -14
- package/src/oauth.ts +204 -27
- package/src/opencode-free.ts +248 -0
- package/src/public/buddy.js +432 -0
- package/src/public/chat.css +174 -72
- package/src/public/chat.html +354 -69
- package/src/public/i18n.js +44 -6
- package/src/public/index.html +1 -46
- package/src/public/mobile.css +597 -0
- package/src/public/mobile.html +954 -0
- package/src/public/settings.html +239 -35
- package/src/public/skills-add.html +8 -0
- package/src/public/studio.html +171 -117
- package/src/public/style.css +220 -26
- package/src/public/subagents-add.html +8 -0
- package/src/reasoning-catalog.ts +346 -0
- package/src/router.ts +184 -13
- package/src/store.ts +170 -5
- package/src/subagent.ts +239 -7
- package/src/tools.ts +132 -21
- package/src/trajectory.ts +72 -9
- package/src/usage.ts +10 -0
- package/src/version.ts +25 -0
- package/vendor/protocol/src/index.ts +27 -2
package/src/tools.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { Type, type Tool } from "@earendil-works/pi-ai";
|
|
|
7
7
|
import { listHostSkills } from "./host-skills.ts";
|
|
8
8
|
import type { McpToolRef } from "./mcp.ts";
|
|
9
9
|
import {
|
|
10
|
+
defaultWorkspace,
|
|
10
11
|
gateTool,
|
|
11
12
|
parseSandbox,
|
|
12
13
|
resolveToolPath,
|
|
@@ -75,8 +76,25 @@ export type ToolContext = {
|
|
|
75
76
|
args: Record<string, unknown>,
|
|
76
77
|
ctx: ToolContext,
|
|
77
78
|
) => Promise<ToolOutcome>;
|
|
79
|
+
/** Devin-style background spawn handles for this turn. Same Map across dispatch clones. */
|
|
80
|
+
spawnHandles?: Map<string, SpawnHandle>;
|
|
78
81
|
};
|
|
79
82
|
|
|
83
|
+
export type SpawnHandle = {
|
|
84
|
+
id: string;
|
|
85
|
+
title: string;
|
|
86
|
+
profile: string;
|
|
87
|
+
done: Promise<ToolOutcome>;
|
|
88
|
+
outcome?: ToolOutcome;
|
|
89
|
+
abort?: AbortController;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/** Pin the turn's handle Map before dispatch spreads a rest clone. */
|
|
93
|
+
export function attachSpawnHandles(ctx: ToolContext): Map<string, SpawnHandle> {
|
|
94
|
+
if (!ctx.spawnHandles) ctx.spawnHandles = new Map();
|
|
95
|
+
return ctx.spawnHandles;
|
|
96
|
+
}
|
|
97
|
+
|
|
80
98
|
const BASE_TOOLS: Tool[] = [
|
|
81
99
|
{
|
|
82
100
|
name: "run",
|
|
@@ -169,7 +187,9 @@ export function guildTools(
|
|
|
169
187
|
(tool) => tool.name === "read" || tool.name === "list",
|
|
170
188
|
);
|
|
171
189
|
} else if (sandbox === "workspace_write") {
|
|
172
|
-
tools = tools.filter(
|
|
190
|
+
tools = tools.filter(
|
|
191
|
+
(tool) => tool.name !== "image_gen" && tool.name !== "browser",
|
|
192
|
+
);
|
|
173
193
|
}
|
|
174
194
|
tools.push({
|
|
175
195
|
name: "skill",
|
|
@@ -178,7 +198,7 @@ export function guildTools(
|
|
|
178
198
|
name: Type.String({ description: "Skill name or slug" }),
|
|
179
199
|
}),
|
|
180
200
|
});
|
|
181
|
-
if ((ctx.spawnDepth ?? 0) < 1
|
|
201
|
+
if ((ctx.spawnDepth ?? 0) < 1) {
|
|
182
202
|
const agents = ctx.subagents ?? [];
|
|
183
203
|
const listed = agents
|
|
184
204
|
.slice(0, 40)
|
|
@@ -190,14 +210,28 @@ export function guildTools(
|
|
|
190
210
|
const catalog = listed ? ` Available: ${listed}.` : "";
|
|
191
211
|
tools.push({
|
|
192
212
|
name: "spawn",
|
|
193
|
-
description: `
|
|
213
|
+
description: `Delegate to a specialist (Devin run_subagent / Pi subagent / Codex spawn_agent). Fresh context; returns a summary, not a transcript. You stay coordinator. Single: title + task + profile (aliases: description/prompt, name/agent). Profiles: explorer (read-only survey), reviewer (read-only critique), worker (bounded patch). luna-explore maps to explorer, luna-general to worker. Independent surveys: background=true (is_background), then read_spawn with the agent_id before the final reply. Parallel: several spawn calls this round, or tasks: [{title, task, profile}, ...] (max 8, 4 at a time). Do not spawn for one known file or a one-line change.${catalog} A read_only parent still spawns; the child stays read_only. Subagents cannot spawn children.`,
|
|
194
214
|
parameters: Type.Object({
|
|
195
|
-
prompt: Type.
|
|
196
|
-
|
|
197
|
-
|
|
215
|
+
prompt: Type.Optional(
|
|
216
|
+
Type.String({
|
|
217
|
+
description: "Self-contained task. Same as task.",
|
|
218
|
+
}),
|
|
219
|
+
),
|
|
220
|
+
task: Type.Optional(
|
|
221
|
+
Type.String({ description: "Alias of prompt (Devin/Pi)" }),
|
|
222
|
+
),
|
|
198
223
|
name: Type.Optional(
|
|
199
224
|
Type.String({
|
|
200
|
-
description: "Subagent name or slug
|
|
225
|
+
description: "Subagent name or slug. Default worker.",
|
|
226
|
+
}),
|
|
227
|
+
),
|
|
228
|
+
agent: Type.Optional(
|
|
229
|
+
Type.String({ description: "Alias of name (Pi)" }),
|
|
230
|
+
),
|
|
231
|
+
profile: Type.Optional(
|
|
232
|
+
Type.String({
|
|
233
|
+
description:
|
|
234
|
+
"Devin profile: explorer | reviewer | worker. luna-explore → explorer, luna-general → worker.",
|
|
201
235
|
}),
|
|
202
236
|
),
|
|
203
237
|
description: Type.Optional(
|
|
@@ -205,6 +239,51 @@ export function guildTools(
|
|
|
205
239
|
description: "Short 3–8 word label for the chat UI",
|
|
206
240
|
}),
|
|
207
241
|
),
|
|
242
|
+
title: Type.Optional(
|
|
243
|
+
Type.String({ description: "Alias of description (Devin title)" }),
|
|
244
|
+
),
|
|
245
|
+
background: Type.Optional(
|
|
246
|
+
Type.Boolean({
|
|
247
|
+
description:
|
|
248
|
+
"If true, return agent_id immediately and keep working. Then call read_spawn.",
|
|
249
|
+
}),
|
|
250
|
+
),
|
|
251
|
+
is_background: Type.Optional(
|
|
252
|
+
Type.Boolean({ description: "Alias of background (Devin)" }),
|
|
253
|
+
),
|
|
254
|
+
tasks: Type.Optional(
|
|
255
|
+
Type.Array(
|
|
256
|
+
Type.Object({
|
|
257
|
+
prompt: Type.Optional(Type.String()),
|
|
258
|
+
task: Type.Optional(Type.String()),
|
|
259
|
+
name: Type.Optional(Type.String()),
|
|
260
|
+
agent: Type.Optional(Type.String()),
|
|
261
|
+
profile: Type.Optional(Type.String()),
|
|
262
|
+
description: Type.Optional(Type.String()),
|
|
263
|
+
title: Type.Optional(Type.String()),
|
|
264
|
+
}),
|
|
265
|
+
{
|
|
266
|
+
description:
|
|
267
|
+
"Pi parallel: run these subagents concurrently (max 8, 4 at a time).",
|
|
268
|
+
},
|
|
269
|
+
),
|
|
270
|
+
),
|
|
271
|
+
}),
|
|
272
|
+
});
|
|
273
|
+
tools.push({
|
|
274
|
+
name: "read_spawn",
|
|
275
|
+
description:
|
|
276
|
+
"Read a background spawn started with background=true (Devin read_subagent). Pass agent_id from spawn. block=true (default) waits; block=false returns running or the summary.",
|
|
277
|
+
parameters: Type.Object({
|
|
278
|
+
agent_id: Type.Optional(
|
|
279
|
+
Type.String({ description: "Id returned by background spawn" }),
|
|
280
|
+
),
|
|
281
|
+
id: Type.Optional(Type.String({ description: "Alias of agent_id" })),
|
|
282
|
+
block: Type.Optional(
|
|
283
|
+
Type.Boolean({
|
|
284
|
+
description: "Wait for the child. Default true.",
|
|
285
|
+
}),
|
|
286
|
+
),
|
|
208
287
|
}),
|
|
209
288
|
});
|
|
210
289
|
}
|
|
@@ -222,7 +301,7 @@ export const GUILD_TOOLS: Tool[] = guildTools();
|
|
|
222
301
|
|
|
223
302
|
function openaiParameters(name: string): {
|
|
224
303
|
type: "object";
|
|
225
|
-
properties: Record<string,
|
|
304
|
+
properties: Record<string, unknown>;
|
|
226
305
|
required: string[];
|
|
227
306
|
} {
|
|
228
307
|
if (name === "run") {
|
|
@@ -257,14 +336,42 @@ function openaiParameters(name: string): {
|
|
|
257
336
|
};
|
|
258
337
|
}
|
|
259
338
|
if (name === "spawn") {
|
|
260
|
-
|
|
339
|
+
const job = {
|
|
261
340
|
type: "object",
|
|
262
341
|
properties: {
|
|
263
|
-
prompt: { type: "string", description: "Self-contained task" },
|
|
342
|
+
prompt: { type: "string", description: "Self-contained task. Same as task." },
|
|
343
|
+
task: { type: "string", description: "Alias of prompt" },
|
|
264
344
|
name: { type: "string", description: "Subagent name or slug" },
|
|
345
|
+
agent: { type: "string", description: "Alias of name" },
|
|
346
|
+
profile: { type: "string", description: "explorer | reviewer | worker" },
|
|
265
347
|
description: { type: "string", description: "Short UI label" },
|
|
348
|
+
title: { type: "string", description: "Alias of description" },
|
|
266
349
|
},
|
|
267
|
-
|
|
350
|
+
};
|
|
351
|
+
return {
|
|
352
|
+
type: "object",
|
|
353
|
+
properties: {
|
|
354
|
+
...job.properties,
|
|
355
|
+
background: { type: "boolean", description: "Return agent_id immediately" },
|
|
356
|
+
is_background: { type: "boolean", description: "Alias of background" },
|
|
357
|
+
tasks: {
|
|
358
|
+
type: "array",
|
|
359
|
+
description: "Pi parallel: [{name, prompt}, ...] max 8, 4 at a time",
|
|
360
|
+
items: job,
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
required: [],
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
if (name === "read_spawn") {
|
|
367
|
+
return {
|
|
368
|
+
type: "object",
|
|
369
|
+
properties: {
|
|
370
|
+
agent_id: { type: "string", description: "Id from background spawn" },
|
|
371
|
+
id: { type: "string", description: "Alias of agent_id" },
|
|
372
|
+
block: { type: "boolean", description: "Wait. Default true." },
|
|
373
|
+
},
|
|
374
|
+
required: [],
|
|
268
375
|
};
|
|
269
376
|
}
|
|
270
377
|
if (name === "image_gen") {
|
|
@@ -327,6 +434,7 @@ export const BUILTIN_TOOL_NAMES = [
|
|
|
327
434
|
"list",
|
|
328
435
|
"skill",
|
|
329
436
|
"spawn",
|
|
437
|
+
"read_spawn",
|
|
330
438
|
"image_gen",
|
|
331
439
|
"browser",
|
|
332
440
|
] as const;
|
|
@@ -339,6 +447,7 @@ export async function executeTool(
|
|
|
339
447
|
try {
|
|
340
448
|
const refused = gateTool(name, args, ctx);
|
|
341
449
|
if (refused) return refused;
|
|
450
|
+
attachSpawnHandles(ctx);
|
|
342
451
|
if (ctx.dispatch) {
|
|
343
452
|
const { dispatch, ...rest } = ctx;
|
|
344
453
|
return await dispatch(name, args, rest);
|
|
@@ -359,9 +468,11 @@ export async function builtinExecute(
|
|
|
359
468
|
ctx: ToolContext = {},
|
|
360
469
|
): Promise<ToolOutcome> {
|
|
361
470
|
try {
|
|
471
|
+
// workspace_write resolves relative paths from the workspace root, which
|
|
472
|
+
// defaults to the guild checkout (same root gateTool checks against).
|
|
362
473
|
const pathBase =
|
|
363
|
-
parseSandbox(ctx.sandbox) === "workspace_write"
|
|
364
|
-
? resolveToolPath(ctx.workspace)
|
|
474
|
+
parseSandbox(ctx.sandbox) === "workspace_write"
|
|
475
|
+
? resolveToolPath(ctx.workspace?.trim() || defaultWorkspace())
|
|
365
476
|
: HOME;
|
|
366
477
|
if (name === "run") {
|
|
367
478
|
return await runCommand(
|
|
@@ -384,13 +495,12 @@ export async function builtinExecute(
|
|
|
384
495
|
if (name === "list") return listDir(asString(args.path), pathBase);
|
|
385
496
|
if (name === "skill") return loadSkill(asString(args.name), ctx.skills ?? []);
|
|
386
497
|
if (name === "spawn") {
|
|
387
|
-
const {
|
|
388
|
-
return
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
});
|
|
498
|
+
const { runSpawnJobs } = await import("./subagent.ts");
|
|
499
|
+
return runSpawnJobs(args, ctx);
|
|
500
|
+
}
|
|
501
|
+
if (name === "read_spawn") {
|
|
502
|
+
const { readSpawn } = await import("./subagent.ts");
|
|
503
|
+
return readSpawn(args, ctx);
|
|
394
504
|
}
|
|
395
505
|
if (name === "image_gen") {
|
|
396
506
|
const { generateImage } = await import("./image-gen.ts");
|
|
@@ -713,7 +823,8 @@ Never say you cannot access this machine. Never tell the user to run the command
|
|
|
713
823
|
When the question is about this computer, call tools first, then answer with evidence from the output.
|
|
714
824
|
To generate an image, call image_gen with a prompt. Do not search the disk or load skills looking for Imagine. After it returns, include the markdown image in your reply.
|
|
715
825
|
To use a real website in a browser, call browser with action=open and a url, then snapshot/click/type using refs like @e1. Default is a Hermes-shaped snapshot of the user's last_used Chrome profile (never the live profile). Set GUILD_BROWSER_REAL_PROFILE=0 for a throwaway empty profile.
|
|
716
|
-
|
|
826
|
+
You stay coordinator. Spawn is the specialist, not a last resort (Devin run_subagent / Pi subagent / Codex spawn_agent). Call spawn for a survey (explorer / luna-explore), a critique (reviewer), or a bounded patch (worker / luna-general) instead of stuffing that work into this turn with list/read/run. Do not spawn for one known file or a one-line change. Independent surveys: spawn with background=true, keep working, then read_spawn {agent_id, block:true} before the final reply. Or several spawn calls / tasks: [{title, task, profile}] this round. Task must be self-contained (child has a fresh context). Do not let a child commit, push, or decide architecture. A read_only seat can still spawn; the child stays read_only. Subagents cannot spawn children.
|
|
827
|
+
Independent tool calls in one round also run in parallel — fire several reads/searches together.
|
|
717
828
|
Check the [exit code: N] marker on every run result; investigate failures before moving on. Prefer the workdir argument over cd.
|
|
718
829
|
To follow a staffed skill, call skill with its exact name (or slug) before applying it. Relative paths in a skill resolve against that skill's base directory.
|
|
719
830
|
Prefer small commands. macOS RAM: sysctl hw.memsize ; memory_pressure. Disk: df -h.`;
|
package/src/trajectory.ts
CHANGED
|
@@ -38,6 +38,49 @@ export function clip(text: string, n = CLIP): string {
|
|
|
38
38
|
return one.slice(0, n - 1) + "…";
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
function recordOf(value: unknown): Record<string, unknown> | null {
|
|
42
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
43
|
+
return value as Record<string, unknown>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Old rows logged spawn as TOOL "spawn spawn". Present them as spawn. */
|
|
47
|
+
export function promoteSpawnEvent<T extends { kind: string; summary: string; payload?: unknown }>(
|
|
48
|
+
event: T,
|
|
49
|
+
): T & { kind: TrajectoryKind; summary: string } {
|
|
50
|
+
if (event.kind === "spawn") return event as T & { kind: TrajectoryKind; summary: string };
|
|
51
|
+
const payload = recordOf(event.payload);
|
|
52
|
+
const toolName = String(payload?.name || "");
|
|
53
|
+
const looksSpawn =
|
|
54
|
+
event.kind === "tool" &&
|
|
55
|
+
(toolName === "spawn" ||
|
|
56
|
+
toolName === "read_spawn" ||
|
|
57
|
+
/^\s*spawn(\s+spawn)?\s*$/i.test(event.summary || ""));
|
|
58
|
+
if (!looksSpawn) return event as T & { kind: TrajectoryKind; summary: string };
|
|
59
|
+
const args = recordOf(payload?.args) || payload || {};
|
|
60
|
+
if (toolName === "read_spawn") {
|
|
61
|
+
const waitId = String(args.agent_id || args.id || "").trim();
|
|
62
|
+
return {
|
|
63
|
+
...event,
|
|
64
|
+
kind: "spawn",
|
|
65
|
+
summary: clip(`read ${waitId}`),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const agent = String(args.name || args.profile || "worker").trim() || "worker";
|
|
69
|
+
const label = String(args.title || args.description || "")
|
|
70
|
+
.replace(/\s+/g, " ")
|
|
71
|
+
.trim();
|
|
72
|
+
const prompt = String(args.prompt || args.task || "")
|
|
73
|
+
.replace(/\s+/g, " ")
|
|
74
|
+
.trim();
|
|
75
|
+
return {
|
|
76
|
+
...event,
|
|
77
|
+
kind: "spawn",
|
|
78
|
+
summary: clip(
|
|
79
|
+
`${agent}${label ? " · " + label : ""}${prompt ? " — " + prompt : ""}`,
|
|
80
|
+
),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
41
84
|
function cap(value: unknown): unknown {
|
|
42
85
|
if (typeof value === "string") {
|
|
43
86
|
return value.length > FIELD_CAP ? value.slice(0, FIELD_CAP) : value;
|
|
@@ -140,17 +183,34 @@ export function turnTrajectoryEvents(input: {
|
|
|
140
183
|
});
|
|
141
184
|
continue;
|
|
142
185
|
}
|
|
143
|
-
if (trace.name === "spawn") {
|
|
144
|
-
const agent =
|
|
145
|
-
|
|
146
|
-
|
|
186
|
+
if (trace.name === "spawn" || trace.name === "read_spawn") {
|
|
187
|
+
const agent =
|
|
188
|
+
String(
|
|
189
|
+
trace.args.profile ||
|
|
190
|
+
trace.args.name ||
|
|
191
|
+
trace.args.agent ||
|
|
192
|
+
"worker",
|
|
193
|
+
).trim() || "worker";
|
|
194
|
+
const label = String(trace.args.title || trace.args.description || "")
|
|
195
|
+
.replace(/\s+/g, " ")
|
|
196
|
+
.trim();
|
|
197
|
+
const prompt = String(trace.args.prompt || trace.args.task || "")
|
|
198
|
+
.replace(/\s+/g, " ")
|
|
199
|
+
.trim();
|
|
200
|
+
const bg =
|
|
201
|
+
trace.args.background === true || trace.args.is_background === true
|
|
202
|
+
? " (bg)"
|
|
203
|
+
: "";
|
|
204
|
+
const waitId = String(trace.args.agent_id || trace.args.id || "").trim();
|
|
147
205
|
events.push({
|
|
148
206
|
ts,
|
|
149
207
|
turnId,
|
|
150
208
|
botId,
|
|
151
209
|
kind: "spawn",
|
|
152
210
|
summary: clip(
|
|
153
|
-
|
|
211
|
+
trace.name === "read_spawn"
|
|
212
|
+
? `read ${waitId || agent}${trace.running ? " …" : ""}`
|
|
213
|
+
: `${agent}${bg}${label ? " · " + label : ""}${prompt ? " — " + prompt : ""}${trace.running ? " …" : ""}`,
|
|
154
214
|
),
|
|
155
215
|
payload: cap(trace.args),
|
|
156
216
|
result: String(cap(trace.text ?? "")),
|
|
@@ -261,16 +321,19 @@ export function synthesizeTrajectory(messages: ChatMessage[]): TrajectoryEvent[]
|
|
|
261
321
|
continue;
|
|
262
322
|
}
|
|
263
323
|
if (part.type === "tool") {
|
|
324
|
+
const spawnish = part.name === "spawn" || part.name === "read_spawn";
|
|
264
325
|
events.push({
|
|
265
326
|
seq: seq++,
|
|
266
327
|
ts,
|
|
267
328
|
turnId,
|
|
268
329
|
botId: msg.author,
|
|
269
|
-
kind:
|
|
330
|
+
kind: spawnish ? "spawn" : "tool",
|
|
270
331
|
summary: clip(
|
|
271
|
-
part.name === "
|
|
272
|
-
? part.detail ||
|
|
273
|
-
:
|
|
332
|
+
part.name === "read_spawn"
|
|
333
|
+
? `read ${part.detail || ""}`
|
|
334
|
+
: part.name === "spawn"
|
|
335
|
+
? part.detail || part.label || "spawn"
|
|
336
|
+
: `${part.name} ${part.detail}`,
|
|
274
337
|
),
|
|
275
338
|
payload: { name: part.name, detail: part.detail },
|
|
276
339
|
result: part.output,
|
package/src/usage.ts
CHANGED
|
@@ -59,13 +59,19 @@ export function fromOpenAiUsage(usage?: {
|
|
|
59
59
|
prompt_tokens?: number;
|
|
60
60
|
completion_tokens?: number;
|
|
61
61
|
total_tokens?: number;
|
|
62
|
+
prompt_tokens_details?: { cached_tokens?: number };
|
|
63
|
+
input_tokens_details?: { cached_tokens?: number };
|
|
62
64
|
} | null): ChatUsage {
|
|
63
65
|
if (!usage) return { rounds: 1 };
|
|
64
66
|
const input = num(usage.prompt_tokens);
|
|
65
67
|
const output = num(usage.completion_tokens);
|
|
68
|
+
const cacheRead =
|
|
69
|
+
num(usage.prompt_tokens_details?.cached_tokens) ??
|
|
70
|
+
num(usage.input_tokens_details?.cached_tokens);
|
|
66
71
|
return {
|
|
67
72
|
input,
|
|
68
73
|
output,
|
|
74
|
+
cacheRead,
|
|
69
75
|
totalTokens: num(usage.total_tokens) || sum(input, output),
|
|
70
76
|
rounds: 1,
|
|
71
77
|
};
|
|
@@ -74,6 +80,8 @@ export function fromOpenAiUsage(usage?: {
|
|
|
74
80
|
export function fromAnthropicUsage(usage?: {
|
|
75
81
|
input_tokens?: number;
|
|
76
82
|
output_tokens?: number;
|
|
83
|
+
cache_read_input_tokens?: number;
|
|
84
|
+
cache_creation_input_tokens?: number;
|
|
77
85
|
} | null): ChatUsage {
|
|
78
86
|
if (!usage) return { rounds: 1 };
|
|
79
87
|
const input = num(usage.input_tokens);
|
|
@@ -81,6 +89,8 @@ export function fromAnthropicUsage(usage?: {
|
|
|
81
89
|
return {
|
|
82
90
|
input,
|
|
83
91
|
output,
|
|
92
|
+
cacheRead: num(usage.cache_read_input_tokens),
|
|
93
|
+
cacheWrite: num(usage.cache_creation_input_tokens),
|
|
84
94
|
totalTokens: sum(input, output),
|
|
85
95
|
rounds: 1,
|
|
86
96
|
};
|
package/src/version.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Outbound User-Agent. npm ships this package's source, so the manifest always
|
|
5
|
+
* sits one level above `src/` — same lookup as `--version` in cli.ts.
|
|
6
|
+
*/
|
|
7
|
+
export function guildUserAgent(): string {
|
|
8
|
+
return `Guild/${guildVersion()}`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let cached: string | null = null;
|
|
12
|
+
|
|
13
|
+
export function guildVersion(): string {
|
|
14
|
+
if (cached !== null) return cached;
|
|
15
|
+
try {
|
|
16
|
+
const pkg = JSON.parse(
|
|
17
|
+
readFileSync(new URL("../package.json", import.meta.url), "utf8"),
|
|
18
|
+
) as { version?: unknown };
|
|
19
|
+
const version = typeof pkg.version === "string" ? pkg.version.trim() : "";
|
|
20
|
+
cached = version || "0";
|
|
21
|
+
} catch {
|
|
22
|
+
cached = "0";
|
|
23
|
+
}
|
|
24
|
+
return cached;
|
|
25
|
+
}
|
|
@@ -37,6 +37,8 @@ export type Bot = {
|
|
|
37
37
|
skillIds: string[];
|
|
38
38
|
defaultPositionId: string;
|
|
39
39
|
oneLiner?: string;
|
|
40
|
+
/** Public /generated/… sprite. Missing → procedural tavern buddy. */
|
|
41
|
+
portrait?: string;
|
|
40
42
|
/** Chat model for this bot. Missing → guild default. */
|
|
41
43
|
model?: ModelRef | null;
|
|
42
44
|
createdAt: string;
|
|
@@ -53,6 +55,10 @@ export type Room = {
|
|
|
53
55
|
name: string;
|
|
54
56
|
memberIds: string[];
|
|
55
57
|
createdAt: string;
|
|
58
|
+
/** Parent channel id when this is a branched side quest. */
|
|
59
|
+
parentId?: string;
|
|
60
|
+
/** Message id in the parent room that opened this branch. */
|
|
61
|
+
branchFromId?: string;
|
|
56
62
|
};
|
|
57
63
|
|
|
58
64
|
export type ChatPart =
|
|
@@ -114,6 +120,12 @@ export type ChatMessage = {
|
|
|
114
120
|
steer?: boolean;
|
|
115
121
|
/** Live bot this steer was aimed at. */
|
|
116
122
|
steerBotId?: string;
|
|
123
|
+
/**
|
|
124
|
+
* Bot ids this message dispatches.
|
|
125
|
+
* User @mentions and bot handoffs. Missing on older rows (parse the body).
|
|
126
|
+
* Empty array means nobody — do not fall back to parsing.
|
|
127
|
+
*/
|
|
128
|
+
mentions?: string[];
|
|
117
129
|
};
|
|
118
130
|
|
|
119
131
|
export type LlmApi =
|
|
@@ -121,9 +133,20 @@ export type LlmApi =
|
|
|
121
133
|
| "anthropic-messages"
|
|
122
134
|
| "openai-responses";
|
|
123
135
|
|
|
136
|
+
export type ModelReasoning = {
|
|
137
|
+
/** Effort strings this model accepts, catalog order. Missing = no effort picker. */
|
|
138
|
+
supportedEfforts?: string[];
|
|
139
|
+
defaultEffort?: string;
|
|
140
|
+
/** When true, do not send `none` — reasoning cannot be turned off. */
|
|
141
|
+
mandatory?: boolean;
|
|
142
|
+
defaultEnabled?: boolean;
|
|
143
|
+
supportsMaxTokens?: boolean;
|
|
144
|
+
};
|
|
145
|
+
|
|
124
146
|
export type ModelEntry = {
|
|
125
147
|
id: string;
|
|
126
148
|
name?: string;
|
|
149
|
+
reasoning?: ModelReasoning;
|
|
127
150
|
};
|
|
128
151
|
|
|
129
152
|
export type ProviderEntry = {
|
|
@@ -141,11 +164,13 @@ export type AuxRole =
|
|
|
141
164
|
| "skills"
|
|
142
165
|
| "approval"
|
|
143
166
|
| "title"
|
|
144
|
-
| "generate"
|
|
167
|
+
| "generate"
|
|
168
|
+
| "spawn";
|
|
145
169
|
|
|
146
170
|
export type ModelsFile = {
|
|
147
171
|
default?: ModelRef | null;
|
|
148
|
-
|
|
172
|
+
/** Last chosen effort string (catalog-defined: low, high, xhigh, …). */
|
|
173
|
+
reasoning?: string;
|
|
149
174
|
fast?: boolean;
|
|
150
175
|
aux?: Partial<Record<AuxRole, ModelRef | null>>;
|
|
151
176
|
recent?: ModelRef[];
|