@kevin5251984/guild 0.2.12 → 0.2.13

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/src/subagent.ts CHANGED
@@ -1,8 +1,6 @@
1
- import { randomUUID } from "node:crypto";
2
1
  import { parseAgentFile } from "./agent-file.ts";
3
2
  import { listHostAgents, type HostAgent } from "./host-agents.ts";
4
3
  import type { LibraryItem } from "@guild/protocol";
5
- import { parseSandbox, type Sandbox } from "./harness.ts";
6
4
  import {
7
5
  hostContext,
8
6
  type SubAgentRef,
@@ -72,18 +70,6 @@ function agentKey(value: string): string {
72
70
  return value.trim().replace(/^\/+/, "").toLowerCase();
73
71
  }
74
72
 
75
- /** Parent read_only cannot escalate via spawn. Explorer from full_access keeps run. */
76
- export function childSpawnPolicy(
77
- parentSandbox: ToolContext["sandbox"],
78
- agentReadOnly: boolean,
79
- ): { sandbox: Sandbox; allowWrite: boolean } {
80
- const parent = parseSandbox(parentSandbox);
81
- if (parent === "read_only") {
82
- return { sandbox: "read_only", allowWrite: false };
83
- }
84
- return { sandbox: parent, allowWrite: !agentReadOnly };
85
- }
86
-
87
73
  export function resolveSubagent(
88
74
  name: string,
89
75
  agents: SubAgentRef[],
@@ -103,192 +89,11 @@ export function resolveSubagent(
103
89
 
104
90
  const CHILD_TOOLS = `You ARE already running on the user's local computer (Guild).
105
91
  Tools: run, read, write, list, skill, image_gen. You cannot spawn subagents.
106
- Never say you cannot access this machine. Check [exit code: N] on every run.
107
- Independent searches: emit multiple tool calls in one round; they run in parallel.`;
92
+ Never say you cannot access this machine. Check [exit code: N] on every run.`;
108
93
 
109
94
  const CHILD_TOOLS_RO = `You ARE already running on the user's local computer (Guild).
110
95
  Tools: run, read, list, skill. You cannot write files and cannot spawn subagents.
111
- Read-only. Never edit, patch, or create files. Check [exit code: N] on every run.
112
- Independent searches: emit multiple tool calls in one round; they run in parallel.`;
113
-
114
- export const SPAWN_MAX_PARALLEL = 8;
115
- export const SPAWN_CONCURRENCY = 4;
116
-
117
- export type SpawnJob = {
118
- prompt: string;
119
- name: string;
120
- description: string;
121
- };
122
-
123
- /** Devin luna-explore / Pi scout → Guild explorer. */
124
- export function spawnProfile(raw: string): string {
125
- const key = raw.trim().toLowerCase();
126
- if (!key) return "";
127
- if (key === "luna-explore" || key === "explore" || key === "scout") {
128
- return "explorer";
129
- }
130
- if (key === "luna-general" || key === "general") return "worker";
131
- if (key === "luna-reviewer") return "reviewer";
132
- return raw.trim();
133
- }
134
-
135
- function recordOf(value: unknown): Record<string, unknown> | null {
136
- if (!value || typeof value !== "object" || Array.isArray(value)) return null;
137
- return value as Record<string, unknown>;
138
- }
139
-
140
- function flagTrue(value: unknown): boolean {
141
- return value === true || value === "true";
142
- }
143
-
144
- function flagFalse(value: unknown): boolean {
145
- return value === false || value === "false";
146
- }
147
-
148
- function oneJob(raw: Record<string, unknown>): SpawnJob {
149
- const prompt = String(raw.prompt || raw.task || "").trim();
150
- const name = spawnProfile(
151
- String(raw.profile || raw.name || raw.agent || raw.subagent_type || ""),
152
- );
153
- const description = String(raw.title || raw.description || "").trim();
154
- return { prompt, name, description };
155
- }
156
-
157
- function handlesOf(ctx: ToolContext) {
158
- if (!ctx.spawnHandles) ctx.spawnHandles = new Map();
159
- return ctx.spawnHandles;
160
- }
161
-
162
- function startBackground(job: SpawnJob, ctx: ToolContext) {
163
- const id = randomUUID();
164
- const title = job.description || job.name || "worker";
165
- const profile = job.name || "worker";
166
- const handle: {
167
- id: string;
168
- title: string;
169
- profile: string;
170
- done: Promise<ToolOutcome>;
171
- outcome?: ToolOutcome;
172
- } = {
173
- id,
174
- title,
175
- profile,
176
- done: Promise.resolve({ text: "", isError: false }),
177
- };
178
- handle.done = spawnSubagent({ ...job, ctx }).then((outcome) => {
179
- handle.outcome = outcome;
180
- return outcome;
181
- });
182
- handlesOf(ctx).set(id, handle);
183
- return handle;
184
- }
185
-
186
- function ackBackground(
187
- rows: { id: string; title: string; profile: string }[],
188
- ): string {
189
- return rows
190
- .map(
191
- (row) =>
192
- `agent_id: ${row.id}\ntitle: ${row.title}\nprofile: ${row.profile}\nstatus: running\nCall read_spawn with this agent_id (block=true) before the final reply.`,
193
- )
194
- .join("\n\n");
195
- }
196
-
197
- /** Pi subagent: single {prompt|task, name|agent} or parallel tasks[]. */
198
- export function spawnJobs(args: Record<string, unknown>): SpawnJob[] {
199
- if (Array.isArray(args.tasks) && args.tasks.length) {
200
- return args.tasks.map((item) => oneJob(recordOf(item) || {}));
201
- }
202
- return [oneJob(args)];
203
- }
204
-
205
- async function mapWithConcurrency<T, R>(
206
- items: T[],
207
- concurrency: number,
208
- fn: (item: T, index: number) => Promise<R>,
209
- ): Promise<R[]> {
210
- if (!items.length) return [];
211
- const limit = Math.max(1, Math.min(concurrency, items.length));
212
- const out: R[] = new Array(items.length);
213
- let next = 0;
214
- await Promise.all(
215
- Array.from({ length: limit }, async () => {
216
- while (true) {
217
- const i = next++;
218
- if (i >= items.length) return;
219
- out[i] = await fn(items[i], i);
220
- }
221
- }),
222
- );
223
- return out;
224
- }
225
-
226
- export async function runSpawnJobs(
227
- args: Record<string, unknown>,
228
- ctx: ToolContext,
229
- ): Promise<ToolOutcome> {
230
- const jobs = spawnJobs(args);
231
- if (!jobs.length || jobs.some((job) => !job.prompt)) {
232
- return { text: "spawn needs a prompt or task", isError: true };
233
- }
234
- if (jobs.length > SPAWN_MAX_PARALLEL) {
235
- return {
236
- text: `Too many parallel tasks (${jobs.length}). Max is ${SPAWN_MAX_PARALLEL}.`,
237
- isError: true,
238
- };
239
- }
240
- const background = flagTrue(args.background) || flagTrue(args.is_background);
241
- if (background) {
242
- const started = jobs.map((job) => startBackground(job, ctx));
243
- return { text: ackBackground(started), isError: false };
244
- }
245
- if (jobs.length === 1) {
246
- return spawnSubagent({ ...jobs[0], ctx });
247
- }
248
- const results = await mapWithConcurrency(jobs, SPAWN_CONCURRENCY, (job) =>
249
- spawnSubagent({ ...job, ctx }),
250
- );
251
- const failed = results.filter((row) => row.isError).length;
252
- const body = results
253
- .map((row, i) => {
254
- const label = jobs[i].description || jobs[i].name || "worker";
255
- const status = row.isError ? "failed" : "completed";
256
- return `### [${label}] ${status}\n\n${row.text}`;
257
- })
258
- .join("\n\n---\n\n");
259
- return {
260
- text: `Parallel: ${results.length - failed}/${results.length} succeeded\n\n${body}`,
261
- isError: failed === results.length,
262
- };
263
- }
264
-
265
- export async function readSpawn(
266
- args: Record<string, unknown>,
267
- ctx: ToolContext,
268
- ): Promise<ToolOutcome> {
269
- const id = String(args.agent_id || args.id || "").trim();
270
- if (!id) return { text: "read_spawn needs agent_id", isError: true };
271
- const handle = handlesOf(ctx).get(id);
272
- if (!handle) {
273
- return {
274
- text: `unknown agent_id ${id}. It must come from a background spawn in this turn.`,
275
- isError: true,
276
- };
277
- }
278
- if (flagFalse(args.block) && !handle.outcome) {
279
- return {
280
- text: `agent_id: ${handle.id}\ntitle: ${handle.title}\nprofile: ${handle.profile}\nstatus: running`,
281
- isError: false,
282
- };
283
- }
284
- const outcome = await handle.done;
285
- return {
286
- text: `# ${handle.title}\nagent_id: ${handle.id}\nprofile: ${handle.profile}\nstatus: ${
287
- outcome.isError ? "failed" : "completed"
288
- }\n\n${outcome.text}`,
289
- isError: outcome.isError,
290
- };
291
- }
96
+ Read-only. Never edit, patch, or create files. Check [exit code: N] on every run.`;
292
97
 
293
98
  export async function spawnSubagent(input: {
294
99
  prompt: string;
@@ -310,13 +115,12 @@ export async function spawnSubagent(input: {
310
115
  ? input.ctx.subagents
311
116
  : listSpawnRefs([]);
312
117
  const agent = resolveSubagent(input.name || "worker", agents);
313
- const child = childSpawnPolicy(input.ctx.sandbox, agent.readOnly);
314
118
  const { llmComplete } = await import("./llm.ts");
315
119
  const label = (input.description || agent.name).trim();
316
120
  const system = [
317
121
  agent.instructions,
318
122
  hostContext(),
319
- child.allowWrite ? CHILD_TOOLS : CHILD_TOOLS_RO,
123
+ agent.readOnly ? CHILD_TOOLS_RO : CHILD_TOOLS,
320
124
  ]
321
125
  .filter(Boolean)
322
126
  .join("\n\n");
@@ -326,7 +130,7 @@ export async function spawnSubagent(input: {
326
130
  system,
327
131
  messages: [{ role: "user", content: prompt }],
328
132
  temperature: 0.3,
329
- role: "spawn",
133
+ role: "chat",
330
134
  tools: true,
331
135
  skills: input.ctx.skills,
332
136
  toolCtx: {
@@ -335,11 +139,10 @@ export async function spawnSubagent(input: {
335
139
  dataDir,
336
140
  env: input.ctx.env,
337
141
  spawnDepth: 1,
338
- allowWrite: child.allowWrite,
339
- sandbox: child.sandbox,
142
+ allowWrite: !agent.readOnly,
143
+ sandbox: input.ctx.sandbox,
340
144
  workspace: input.ctx.workspace,
341
145
  dispatch: input.ctx.dispatch,
342
- signal: input.ctx.signal,
343
146
  },
344
147
  });
345
148
  if (!result) {
package/src/tools.ts CHANGED
@@ -75,17 +75,6 @@ export type ToolContext = {
75
75
  args: Record<string, unknown>,
76
76
  ctx: ToolContext,
77
77
  ) => Promise<ToolOutcome>;
78
- /** Devin-style background spawn handles for this turn. */
79
- spawnHandles?: Map<
80
- string,
81
- {
82
- id: string;
83
- title: string;
84
- profile: string;
85
- done: Promise<ToolOutcome>;
86
- outcome?: ToolOutcome;
87
- }
88
- >;
89
78
  };
90
79
 
91
80
  const BASE_TOOLS: Tool[] = [
@@ -189,7 +178,7 @@ export function guildTools(
189
178
  name: Type.String({ description: "Skill name or slug" }),
190
179
  }),
191
180
  });
192
- if ((ctx.spawnDepth ?? 0) < 1) {
181
+ if ((ctx.spawnDepth ?? 0) < 1 && sandbox !== "read_only") {
193
182
  const agents = ctx.subagents ?? [];
194
183
  const listed = agents
195
184
  .slice(0, 40)
@@ -201,28 +190,14 @@ export function guildTools(
201
190
  const catalog = listed ? ` Available: ${listed}.` : "";
202
191
  tools.push({
203
192
  name: "spawn",
204
- 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.`,
193
+ description: `Spawn a subagent with a fresh context. It returns a summary, not a transcript. Use for parallel exploration, review, or a bounded implementation slice.${catalog} Subagents cannot spawn children.`,
205
194
  parameters: Type.Object({
206
- prompt: Type.Optional(
207
- Type.String({
208
- description: "Self-contained task. Same as task.",
209
- }),
210
- ),
211
- task: Type.Optional(
212
- Type.String({ description: "Alias of prompt (Devin/Pi)" }),
213
- ),
195
+ prompt: Type.String({
196
+ description: "Self-contained task for the subagent. Include paths, constraints, and the deliverable.",
197
+ }),
214
198
  name: Type.Optional(
215
199
  Type.String({
216
- description: "Subagent name or slug. Default worker.",
217
- }),
218
- ),
219
- agent: Type.Optional(
220
- Type.String({ description: "Alias of name (Pi)" }),
221
- ),
222
- profile: Type.Optional(
223
- Type.String({
224
- description:
225
- "Devin profile: explorer | reviewer | worker. luna-explore → explorer, luna-general → worker.",
200
+ description: "Subagent name or slug from the library. Default worker.",
226
201
  }),
227
202
  ),
228
203
  description: Type.Optional(
@@ -230,51 +205,6 @@ export function guildTools(
230
205
  description: "Short 3–8 word label for the chat UI",
231
206
  }),
232
207
  ),
233
- title: Type.Optional(
234
- Type.String({ description: "Alias of description (Devin title)" }),
235
- ),
236
- background: Type.Optional(
237
- Type.Boolean({
238
- description:
239
- "If true, return agent_id immediately and keep working. Then call read_spawn.",
240
- }),
241
- ),
242
- is_background: Type.Optional(
243
- Type.Boolean({ description: "Alias of background (Devin)" }),
244
- ),
245
- tasks: Type.Optional(
246
- Type.Array(
247
- Type.Object({
248
- prompt: Type.Optional(Type.String()),
249
- task: Type.Optional(Type.String()),
250
- name: Type.Optional(Type.String()),
251
- agent: Type.Optional(Type.String()),
252
- profile: Type.Optional(Type.String()),
253
- description: Type.Optional(Type.String()),
254
- title: Type.Optional(Type.String()),
255
- }),
256
- {
257
- description:
258
- "Pi parallel: run these subagents concurrently (max 8, 4 at a time).",
259
- },
260
- ),
261
- ),
262
- }),
263
- });
264
- tools.push({
265
- name: "read_spawn",
266
- description:
267
- "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.",
268
- parameters: Type.Object({
269
- agent_id: Type.Optional(
270
- Type.String({ description: "Id returned by background spawn" }),
271
- ),
272
- id: Type.Optional(Type.String({ description: "Alias of agent_id" })),
273
- block: Type.Optional(
274
- Type.Boolean({
275
- description: "Wait for the child. Default true.",
276
- }),
277
- ),
278
208
  }),
279
209
  });
280
210
  }
@@ -292,7 +222,7 @@ export const GUILD_TOOLS: Tool[] = guildTools();
292
222
 
293
223
  function openaiParameters(name: string): {
294
224
  type: "object";
295
- properties: Record<string, unknown>;
225
+ properties: Record<string, { type: "string"; description?: string }>;
296
226
  required: string[];
297
227
  } {
298
228
  if (name === "run") {
@@ -327,42 +257,14 @@ function openaiParameters(name: string): {
327
257
  };
328
258
  }
329
259
  if (name === "spawn") {
330
- const job = {
260
+ return {
331
261
  type: "object",
332
262
  properties: {
333
- prompt: { type: "string", description: "Self-contained task. Same as task." },
334
- task: { type: "string", description: "Alias of prompt" },
263
+ prompt: { type: "string", description: "Self-contained task" },
335
264
  name: { type: "string", description: "Subagent name or slug" },
336
- agent: { type: "string", description: "Alias of name" },
337
- profile: { type: "string", description: "explorer | reviewer | worker" },
338
265
  description: { type: "string", description: "Short UI label" },
339
- title: { type: "string", description: "Alias of description" },
340
- },
341
- };
342
- return {
343
- type: "object",
344
- properties: {
345
- ...job.properties,
346
- background: { type: "boolean", description: "Return agent_id immediately" },
347
- is_background: { type: "boolean", description: "Alias of background" },
348
- tasks: {
349
- type: "array",
350
- description: "Pi parallel: [{name, prompt}, ...] max 8, 4 at a time",
351
- items: job,
352
- },
353
266
  },
354
- required: [],
355
- };
356
- }
357
- if (name === "read_spawn") {
358
- return {
359
- type: "object",
360
- properties: {
361
- agent_id: { type: "string", description: "Id from background spawn" },
362
- id: { type: "string", description: "Alias of agent_id" },
363
- block: { type: "boolean", description: "Wait. Default true." },
364
- },
365
- required: [],
267
+ required: ["prompt"],
366
268
  };
367
269
  }
368
270
  if (name === "image_gen") {
@@ -425,7 +327,6 @@ export const BUILTIN_TOOL_NAMES = [
425
327
  "list",
426
328
  "skill",
427
329
  "spawn",
428
- "read_spawn",
429
330
  "image_gen",
430
331
  "browser",
431
332
  ] as const;
@@ -483,12 +384,13 @@ export async function builtinExecute(
483
384
  if (name === "list") return listDir(asString(args.path), pathBase);
484
385
  if (name === "skill") return loadSkill(asString(args.name), ctx.skills ?? []);
485
386
  if (name === "spawn") {
486
- const { runSpawnJobs } = await import("./subagent.ts");
487
- return runSpawnJobs(args, ctx);
488
- }
489
- if (name === "read_spawn") {
490
- const { readSpawn } = await import("./subagent.ts");
491
- return readSpawn(args, ctx);
387
+ const { spawnSubagent } = await import("./subagent.ts");
388
+ return spawnSubagent({
389
+ prompt: asString(args.prompt),
390
+ name: typeof args.name === "string" ? args.name : "",
391
+ description: typeof args.description === "string" ? args.description : "",
392
+ ctx,
393
+ });
492
394
  }
493
395
  if (name === "image_gen") {
494
396
  const { generateImage } = await import("./image-gen.ts");
@@ -811,8 +713,7 @@ Never say you cannot access this machine. Never tell the user to run the command
811
713
  When the question is about this computer, call tools first, then answer with evidence from the output.
812
714
  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.
813
715
  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.
814
- 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.
815
- Independent tool calls in one round also run in parallel — fire several reads/searches together.
716
+ To delegate a bounded slice (explore, review, implement) to a specialist with its own context, call spawn. Pass a self-contained prompt. The subagent returns a summary. Subagents cannot spawn children.
816
717
  Check the [exit code: N] marker on every run result; investigate failures before moving on. Prefer the workdir argument over cd.
817
718
  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.
818
719
  Prefer small commands. macOS RAM: sysctl hw.memsize ; memory_pressure. Disk: df -h.`;
package/src/trajectory.ts CHANGED
@@ -38,35 +38,6 @@ 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" || /^\s*spawn(\s+spawn)?\s*$/i.test(event.summary || ""));
56
- if (!looksSpawn) return event as T & { kind: TrajectoryKind; summary: string };
57
- const args = recordOf(payload?.args) || payload || {};
58
- const agent = String(args.name || "worker").trim() || "worker";
59
- const label = String(args.description || "").replace(/\s+/g, " ").trim();
60
- const prompt = String(args.prompt || "").replace(/\s+/g, " ").trim();
61
- return {
62
- ...event,
63
- kind: "spawn",
64
- summary: clip(
65
- `${agent}${label ? " · " + label : ""}${prompt ? " — " + prompt : ""}`,
66
- ),
67
- };
68
- }
69
-
70
41
  function cap(value: unknown): unknown {
71
42
  if (typeof value === "string") {
72
43
  return value.length > FIELD_CAP ? value.slice(0, FIELD_CAP) : value;
@@ -169,34 +140,17 @@ export function turnTrajectoryEvents(input: {
169
140
  });
170
141
  continue;
171
142
  }
172
- if (trace.name === "spawn" || trace.name === "read_spawn") {
173
- const agent =
174
- String(
175
- trace.args.profile ||
176
- trace.args.name ||
177
- trace.args.agent ||
178
- "worker",
179
- ).trim() || "worker";
180
- const label = String(trace.args.title || trace.args.description || "")
181
- .replace(/\s+/g, " ")
182
- .trim();
183
- const prompt = String(trace.args.prompt || trace.args.task || "")
184
- .replace(/\s+/g, " ")
185
- .trim();
186
- const bg =
187
- trace.args.background === true || trace.args.is_background === true
188
- ? " (bg)"
189
- : "";
190
- const waitId = String(trace.args.agent_id || trace.args.id || "").trim();
143
+ if (trace.name === "spawn") {
144
+ const agent = String(trace.args.name || "worker").trim() || "worker";
145
+ const label = String(trace.args.description || "").replace(/\s+/g, " ").trim();
146
+ const prompt = String(trace.args.prompt || "").replace(/\s+/g, " ").trim();
191
147
  events.push({
192
148
  ts,
193
149
  turnId,
194
150
  botId,
195
151
  kind: "spawn",
196
152
  summary: clip(
197
- trace.name === "read_spawn"
198
- ? `read ${waitId || agent}${trace.running ? " …" : ""}`
199
- : `${agent}${bg}${label ? " · " + label : ""}${prompt ? " — " + prompt : ""}${trace.running ? " …" : ""}`,
153
+ `${agent}${label ? " · " + label : ""}${prompt ? " — " + prompt : ""}${trace.running ? "" : ""}`,
200
154
  ),
201
155
  payload: cap(trace.args),
202
156
  result: String(cap(trace.text ?? "")),
package/src/usage.ts CHANGED
@@ -59,19 +59,13 @@ 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 };
64
62
  } | null): ChatUsage {
65
63
  if (!usage) return { rounds: 1 };
66
64
  const input = num(usage.prompt_tokens);
67
65
  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);
71
66
  return {
72
67
  input,
73
68
  output,
74
- cacheRead,
75
69
  totalTokens: num(usage.total_tokens) || sum(input, output),
76
70
  rounds: 1,
77
71
  };
@@ -80,8 +74,6 @@ export function fromOpenAiUsage(usage?: {
80
74
  export function fromAnthropicUsage(usage?: {
81
75
  input_tokens?: number;
82
76
  output_tokens?: number;
83
- cache_read_input_tokens?: number;
84
- cache_creation_input_tokens?: number;
85
77
  } | null): ChatUsage {
86
78
  if (!usage) return { rounds: 1 };
87
79
  const input = num(usage.input_tokens);
@@ -89,8 +81,6 @@ export function fromAnthropicUsage(usage?: {
89
81
  return {
90
82
  input,
91
83
  output,
92
- cacheRead: num(usage.cache_read_input_tokens),
93
- cacheWrite: num(usage.cache_creation_input_tokens),
94
84
  totalTokens: sum(input, output),
95
85
  rounds: 1,
96
86
  };
@@ -37,8 +37,6 @@ 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;
42
40
  /** Chat model for this bot. Missing → guild default. */
43
41
  model?: ModelRef | null;
44
42
  createdAt: string;
@@ -143,8 +141,7 @@ export type AuxRole =
143
141
  | "skills"
144
142
  | "approval"
145
143
  | "title"
146
- | "generate"
147
- | "spawn";
144
+ | "generate";
148
145
 
149
146
  export type ModelsFile = {
150
147
  default?: ModelRef | null;