@link-assistant/agent 0.0.9 → 0.0.12

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.
Files changed (104) hide show
  1. package/EXAMPLES.md +36 -0
  2. package/MODELS.md +72 -24
  3. package/README.md +59 -2
  4. package/TOOLS.md +20 -0
  5. package/package.json +35 -2
  6. package/src/agent/agent.ts +68 -54
  7. package/src/auth/claude-oauth.ts +426 -0
  8. package/src/auth/index.ts +28 -26
  9. package/src/auth/plugins.ts +876 -0
  10. package/src/bun/index.ts +53 -43
  11. package/src/bus/global.ts +5 -5
  12. package/src/bus/index.ts +59 -53
  13. package/src/cli/bootstrap.js +12 -12
  14. package/src/cli/bootstrap.ts +6 -6
  15. package/src/cli/cmd/agent.ts +97 -92
  16. package/src/cli/cmd/auth.ts +469 -0
  17. package/src/cli/cmd/cmd.ts +2 -2
  18. package/src/cli/cmd/export.ts +41 -41
  19. package/src/cli/cmd/mcp.ts +144 -119
  20. package/src/cli/cmd/models.ts +30 -29
  21. package/src/cli/cmd/run.ts +269 -213
  22. package/src/cli/cmd/stats.ts +185 -146
  23. package/src/cli/error.ts +17 -13
  24. package/src/cli/ui.ts +39 -24
  25. package/src/command/index.ts +26 -26
  26. package/src/config/config.ts +528 -288
  27. package/src/config/markdown.ts +15 -15
  28. package/src/file/ripgrep.ts +201 -169
  29. package/src/file/time.ts +21 -18
  30. package/src/file/watcher.ts +51 -42
  31. package/src/file.ts +1 -1
  32. package/src/flag/flag.ts +26 -11
  33. package/src/format/formatter.ts +206 -162
  34. package/src/format/index.ts +61 -61
  35. package/src/global/index.ts +21 -21
  36. package/src/id/id.ts +47 -33
  37. package/src/index.js +346 -199
  38. package/src/json-standard/index.ts +67 -51
  39. package/src/mcp/index.ts +135 -128
  40. package/src/patch/index.ts +336 -267
  41. package/src/project/bootstrap.ts +15 -15
  42. package/src/project/instance.ts +43 -36
  43. package/src/project/project.ts +47 -47
  44. package/src/project/state.ts +37 -33
  45. package/src/provider/models-macro.ts +5 -5
  46. package/src/provider/models.ts +32 -32
  47. package/src/provider/opencode.js +19 -19
  48. package/src/provider/provider.ts +518 -277
  49. package/src/provider/transform.ts +143 -102
  50. package/src/server/project.ts +21 -21
  51. package/src/server/server.ts +111 -105
  52. package/src/session/agent.js +66 -60
  53. package/src/session/compaction.ts +136 -111
  54. package/src/session/index.ts +189 -156
  55. package/src/session/message-v2.ts +312 -268
  56. package/src/session/message.ts +73 -57
  57. package/src/session/processor.ts +180 -166
  58. package/src/session/prompt.ts +678 -533
  59. package/src/session/retry.ts +26 -23
  60. package/src/session/revert.ts +76 -62
  61. package/src/session/status.ts +26 -26
  62. package/src/session/summary.ts +97 -76
  63. package/src/session/system.ts +77 -63
  64. package/src/session/todo.ts +22 -16
  65. package/src/snapshot/index.ts +92 -76
  66. package/src/storage/storage.ts +157 -120
  67. package/src/tool/bash.ts +116 -106
  68. package/src/tool/batch.ts +73 -59
  69. package/src/tool/codesearch.ts +60 -53
  70. package/src/tool/edit.ts +319 -263
  71. package/src/tool/glob.ts +32 -28
  72. package/src/tool/grep.ts +72 -53
  73. package/src/tool/invalid.ts +7 -7
  74. package/src/tool/ls.ts +77 -64
  75. package/src/tool/multiedit.ts +30 -21
  76. package/src/tool/patch.ts +121 -94
  77. package/src/tool/read.ts +140 -122
  78. package/src/tool/registry.ts +38 -38
  79. package/src/tool/task.ts +93 -60
  80. package/src/tool/todo.ts +16 -16
  81. package/src/tool/tool.ts +45 -36
  82. package/src/tool/webfetch.ts +97 -74
  83. package/src/tool/websearch.ts +78 -64
  84. package/src/tool/write.ts +21 -15
  85. package/src/util/binary.ts +27 -19
  86. package/src/util/context.ts +8 -8
  87. package/src/util/defer.ts +7 -5
  88. package/src/util/error.ts +24 -19
  89. package/src/util/eventloop.ts +16 -10
  90. package/src/util/filesystem.ts +37 -33
  91. package/src/util/fn.ts +11 -8
  92. package/src/util/iife.ts +1 -1
  93. package/src/util/keybind.ts +44 -44
  94. package/src/util/lazy.ts +7 -7
  95. package/src/util/locale.ts +20 -16
  96. package/src/util/lock.ts +43 -38
  97. package/src/util/log.ts +95 -85
  98. package/src/util/queue.ts +8 -8
  99. package/src/util/rpc.ts +35 -23
  100. package/src/util/scrap.ts +4 -4
  101. package/src/util/signal.ts +5 -5
  102. package/src/util/timeout.ts +6 -6
  103. package/src/util/token.ts +2 -2
  104. package/src/util/wildcard.ts +38 -27
@@ -1,14 +1,14 @@
1
- import path from "path"
2
- import os from "os"
3
- import fs from "fs/promises"
4
- import z from "zod"
5
- import { Identifier } from "../id/id"
6
- import { MessageV2 } from "./message-v2"
7
- import { Log } from "../util/log"
8
- import { SessionRevert } from "./revert"
9
- import { Session } from "."
10
- import { Agent } from "../agent/agent"
11
- import { Provider } from "../provider/provider"
1
+ import path from 'path';
2
+ import os from 'os';
3
+ import fs from 'fs/promises';
4
+ import z from 'zod';
5
+ import { Identifier } from '../id/id';
6
+ import { MessageV2 } from './message-v2';
7
+ import { Log } from '../util/log';
8
+ import { SessionRevert } from './revert';
9
+ import { Session } from '.';
10
+ import { Agent } from '../agent/agent';
11
+ import { Provider } from '../provider/provider';
12
12
  import {
13
13
  generateText,
14
14
  streamText,
@@ -18,68 +18,70 @@ import {
18
18
  wrapLanguageModel,
19
19
  stepCountIs,
20
20
  jsonSchema,
21
- } from "ai"
22
- import { SessionCompaction } from "./compaction"
23
- import { Instance } from "../project/instance"
24
- import { Bus } from "../bus"
25
- import { ProviderTransform } from "../provider/transform"
26
- import { SystemPrompt } from "./system"
27
-
28
- import PROMPT_PLAN from "../session/prompt/plan.txt"
29
- import BUILD_SWITCH from "../session/prompt/build-switch.txt"
30
- import { defer } from "../util/defer"
31
- import { mergeDeep, pipe } from "remeda"
32
- import { ToolRegistry } from "../tool/registry"
33
- import { Wildcard } from "../util/wildcard"
34
- import { MCP } from "../mcp"
35
- import { ReadTool } from "../tool/read"
36
- import { ListTool } from "../tool/ls"
37
- import { FileTime } from "../file/time"
38
- import { ulid } from "ulid"
39
- import { spawn } from "child_process"
40
- import { Command } from "../command"
41
- import { $, fileURLToPath } from "bun"
42
- import { ConfigMarkdown } from "../config/markdown"
43
- import { SessionSummary } from "./summary"
44
- import { NamedError } from "../util/error"
45
- import { fn } from "../util/fn"
46
- import { SessionProcessor } from "./processor"
47
- import { TaskTool } from "../tool/task"
48
- import { SessionStatus } from "./status"
21
+ } from 'ai';
22
+ import { SessionCompaction } from './compaction';
23
+ import { Instance } from '../project/instance';
24
+ import { Bus } from '../bus';
25
+ import { ProviderTransform } from '../provider/transform';
26
+ import { SystemPrompt } from './system';
27
+ import { Flag } from '../flag/flag';
28
+ import { Token } from '../util/token';
29
+
30
+ import PROMPT_PLAN from '../session/prompt/plan.txt';
31
+ import BUILD_SWITCH from '../session/prompt/build-switch.txt';
32
+ import { defer } from '../util/defer';
33
+ import { mergeDeep, pipe } from 'remeda';
34
+ import { ToolRegistry } from '../tool/registry';
35
+ import { Wildcard } from '../util/wildcard';
36
+ import { MCP } from '../mcp';
37
+ import { ReadTool } from '../tool/read';
38
+ import { ListTool } from '../tool/ls';
39
+ import { FileTime } from '../file/time';
40
+ import { ulid } from 'ulid';
41
+ import { spawn } from 'child_process';
42
+ import { Command } from '../command';
43
+ import { $, fileURLToPath } from 'bun';
44
+ import { ConfigMarkdown } from '../config/markdown';
45
+ import { SessionSummary } from './summary';
46
+ import { NamedError } from '../util/error';
47
+ import { fn } from '../util/fn';
48
+ import { SessionProcessor } from './processor';
49
+ import { TaskTool } from '../tool/task';
50
+ import { SessionStatus } from './status';
49
51
 
50
52
  export namespace SessionPrompt {
51
- const log = Log.create({ service: "session.prompt" })
52
- export const OUTPUT_TOKEN_MAX = 32_000
53
+ const log = Log.create({ service: 'session.prompt' });
54
+ export const OUTPUT_TOKEN_MAX = 32_000;
53
55
 
54
56
  const state = Instance.state(
55
57
  () => {
56
58
  const data: Record<
57
59
  string,
58
60
  {
59
- abort: AbortController
61
+ abort: AbortController;
60
62
  callbacks: {
61
- resolve(input: MessageV2.WithParts): void
62
- reject(): void
63
- }[]
63
+ resolve(input: MessageV2.WithParts): void;
64
+ reject(): void;
65
+ }[];
64
66
  }
65
- > = {}
66
- return data
67
+ > = {};
68
+ return data;
67
69
  },
68
70
  async (current) => {
69
71
  for (const item of Object.values(current)) {
70
- item.abort.abort()
72
+ item.abort.abort();
71
73
  }
72
- },
73
- )
74
+ }
75
+ );
74
76
 
75
77
  export function assertNotBusy(sessionID: string) {
76
- const match = state()[sessionID]
77
- if (match) throw new Session.BusyError(sessionID)
78
+ const match = state()[sessionID];
79
+ if (match) throw new Session.BusyError(sessionID);
78
80
  }
79
81
 
80
82
  export const PromptInput = z.object({
81
- sessionID: Identifier.schema("session"),
82
- messageID: Identifier.schema("message").optional(),
83
+ sessionID: Identifier.schema('session'),
84
+ messageID: Identifier.schema('message').optional(),
83
85
  model: z
84
86
  .object({
85
87
  providerID: z.string(),
@@ -92,7 +94,7 @@ export namespace SessionPrompt {
92
94
  appendSystem: z.string().optional(),
93
95
  tools: z.record(z.string(), z.boolean()).optional(),
94
96
  parts: z.array(
95
- z.discriminatedUnion("type", [
97
+ z.discriminatedUnion('type', [
96
98
  MessageV2.TextPart.omit({
97
99
  messageID: true,
98
100
  sessionID: true,
@@ -101,7 +103,7 @@ export namespace SessionPrompt {
101
103
  id: true,
102
104
  })
103
105
  .meta({
104
- ref: "TextPartInput",
106
+ ref: 'TextPartInput',
105
107
  }),
106
108
  MessageV2.FilePart.omit({
107
109
  messageID: true,
@@ -111,7 +113,7 @@ export namespace SessionPrompt {
111
113
  id: true,
112
114
  })
113
115
  .meta({
114
- ref: "FilePartInput",
116
+ ref: 'FilePartInput',
115
117
  }),
116
118
  MessageV2.AgentPart.omit({
117
119
  messageID: true,
@@ -121,7 +123,7 @@ export namespace SessionPrompt {
121
123
  id: true,
122
124
  })
123
125
  .meta({
124
- ref: "AgentPartInput",
126
+ ref: 'AgentPartInput',
125
127
  }),
126
128
  MessageV2.SubtaskPart.omit({
127
129
  messageID: true,
@@ -131,160 +133,176 @@ export namespace SessionPrompt {
131
133
  id: true,
132
134
  })
133
135
  .meta({
134
- ref: "SubtaskPartInput",
136
+ ref: 'SubtaskPartInput',
135
137
  }),
136
- ]),
138
+ ])
137
139
  ),
138
- })
139
- export type PromptInput = z.infer<typeof PromptInput>
140
+ });
141
+ export type PromptInput = z.infer<typeof PromptInput>;
140
142
 
141
- export async function resolvePromptParts(template: string): Promise<PromptInput["parts"]> {
142
- const parts: PromptInput["parts"] = [
143
+ export async function resolvePromptParts(
144
+ template: string
145
+ ): Promise<PromptInput['parts']> {
146
+ const parts: PromptInput['parts'] = [
143
147
  {
144
- type: "text",
148
+ type: 'text',
145
149
  text: template,
146
150
  },
147
- ]
148
- const files = ConfigMarkdown.files(template)
151
+ ];
152
+ const files = ConfigMarkdown.files(template);
149
153
  await Promise.all(
150
154
  files.map(async (match) => {
151
- const name = match[1]
152
- const filepath = name.startsWith("~/")
155
+ const name = match[1];
156
+ const filepath = name.startsWith('~/')
153
157
  ? path.join(os.homedir(), name.slice(2))
154
- : path.resolve(Instance.worktree, name)
158
+ : path.resolve(Instance.worktree, name);
155
159
 
156
- const stats = await fs.stat(filepath).catch(() => undefined)
160
+ const stats = await fs.stat(filepath).catch(() => undefined);
157
161
  if (!stats) {
158
- const agent = await Agent.get(name)
162
+ const agent = await Agent.get(name);
159
163
  if (agent) {
160
164
  parts.push({
161
- type: "agent",
165
+ type: 'agent',
162
166
  name: agent.name,
163
- })
167
+ });
164
168
  }
165
- return
169
+ return;
166
170
  }
167
171
 
168
172
  if (stats.isDirectory()) {
169
173
  parts.push({
170
- type: "file",
174
+ type: 'file',
171
175
  url: `file://${filepath}`,
172
176
  filename: name,
173
- mime: "application/x-directory",
174
- })
175
- return
177
+ mime: 'application/x-directory',
178
+ });
179
+ return;
176
180
  }
177
181
 
178
182
  parts.push({
179
- type: "file",
183
+ type: 'file',
180
184
  url: `file://${filepath}`,
181
185
  filename: name,
182
- mime: "text/plain",
183
- })
184
- }),
185
- )
186
- return parts
186
+ mime: 'text/plain',
187
+ });
188
+ })
189
+ );
190
+ return parts;
187
191
  }
188
192
 
189
193
  export const prompt = fn(PromptInput, async (input) => {
190
- const session = await Session.get(input.sessionID)
191
- await SessionRevert.cleanup(session)
194
+ const session = await Session.get(input.sessionID);
195
+ await SessionRevert.cleanup(session);
192
196
 
193
- const message = await createUserMessage(input)
194
- await Session.touch(input.sessionID)
197
+ const message = await createUserMessage(input);
198
+ await Session.touch(input.sessionID);
195
199
 
196
200
  if (input.noReply) {
197
- return message
201
+ return message;
198
202
  }
199
203
 
200
- return loop(input.sessionID)
201
- })
204
+ return loop(input.sessionID);
205
+ });
202
206
 
203
207
  function start(sessionID: string) {
204
- const s = state()
205
- if (s[sessionID]) return
206
- const controller = new AbortController()
208
+ const s = state();
209
+ if (s[sessionID]) return;
210
+ const controller = new AbortController();
207
211
  s[sessionID] = {
208
212
  abort: controller,
209
213
  callbacks: [],
210
- }
211
- return controller.signal
214
+ };
215
+ return controller.signal;
212
216
  }
213
217
 
214
218
  export function cancel(sessionID: string) {
215
- log.info("cancel", { sessionID })
216
- const s = state()
217
- const match = s[sessionID]
218
- if (!match) return
219
- match.abort.abort()
219
+ log.info('cancel', { sessionID });
220
+ const s = state();
221
+ const match = s[sessionID];
222
+ if (!match) return;
223
+ match.abort.abort();
220
224
  for (const item of match.callbacks) {
221
- item.reject()
225
+ item.reject();
222
226
  }
223
- delete s[sessionID]
224
- SessionStatus.set(sessionID, { type: "idle" })
225
- return
227
+ delete s[sessionID];
228
+ SessionStatus.set(sessionID, { type: 'idle' });
229
+ return;
226
230
  }
227
231
 
228
- export const loop = fn(Identifier.schema("session"), async (sessionID) => {
229
- const abort = start(sessionID)
232
+ export const loop = fn(Identifier.schema('session'), async (sessionID) => {
233
+ const abort = start(sessionID);
230
234
  if (!abort) {
231
235
  return new Promise<MessageV2.WithParts>((resolve, reject) => {
232
- const callbacks = state()[sessionID].callbacks
233
- callbacks.push({ resolve, reject })
234
- })
236
+ const callbacks = state()[sessionID].callbacks;
237
+ callbacks.push({ resolve, reject });
238
+ });
235
239
  }
236
240
 
237
- using _ = defer(() => cancel(sessionID))
241
+ using _ = defer(() => cancel(sessionID));
238
242
 
239
- let step = 0
243
+ let step = 0;
240
244
  while (true) {
241
- log.info("loop", { step, sessionID })
242
- if (abort.aborted) break
243
- let msgs = await MessageV2.filterCompacted(MessageV2.stream(sessionID))
244
-
245
- let lastUser: MessageV2.User | undefined
246
- let lastAssistant: MessageV2.Assistant | undefined
247
- let lastFinished: MessageV2.Assistant | undefined
248
- let tasks: (MessageV2.CompactionPart | MessageV2.SubtaskPart)[] = []
245
+ log.info('loop', { step, sessionID });
246
+ if (abort.aborted) break;
247
+ let msgs = await MessageV2.filterCompacted(MessageV2.stream(sessionID));
248
+
249
+ let lastUser: MessageV2.User | undefined;
250
+ let lastAssistant: MessageV2.Assistant | undefined;
251
+ let lastFinished: MessageV2.Assistant | undefined;
252
+ let tasks: (MessageV2.CompactionPart | MessageV2.SubtaskPart)[] = [];
249
253
  for (let i = msgs.length - 1; i >= 0; i--) {
250
- const msg = msgs[i]
251
- if (!lastUser && msg.info.role === "user") lastUser = msg.info as MessageV2.User
252
- if (!lastAssistant && msg.info.role === "assistant") lastAssistant = msg.info as MessageV2.Assistant
253
- if (!lastFinished && msg.info.role === "assistant" && msg.info.finish)
254
- lastFinished = msg.info as MessageV2.Assistant
255
- if (lastUser && lastFinished) break
256
- const task = msg.parts.filter((part) => part.type === "compaction" || part.type === "subtask")
254
+ const msg = msgs[i];
255
+ if (!lastUser && msg.info.role === 'user')
256
+ lastUser = msg.info as MessageV2.User;
257
+ if (!lastAssistant && msg.info.role === 'assistant')
258
+ lastAssistant = msg.info as MessageV2.Assistant;
259
+ if (!lastFinished && msg.info.role === 'assistant' && msg.info.finish)
260
+ lastFinished = msg.info as MessageV2.Assistant;
261
+ if (lastUser && lastFinished) break;
262
+ const task = msg.parts.filter(
263
+ (part) => part.type === 'compaction' || part.type === 'subtask'
264
+ );
257
265
  if (task && !lastFinished) {
258
- tasks.push(...task)
266
+ tasks.push(...task);
259
267
  }
260
268
  }
261
269
 
262
- if (!lastUser) throw new Error("No user message found in stream. This should never happen.")
263
- if (lastAssistant?.finish && lastAssistant.finish !== "tool-calls" && lastUser.id < lastAssistant.id) {
264
- log.info("exiting loop", { sessionID })
265
- break
270
+ if (!lastUser)
271
+ throw new Error(
272
+ 'No user message found in stream. This should never happen.'
273
+ );
274
+ if (
275
+ lastAssistant?.finish &&
276
+ lastAssistant.finish !== 'tool-calls' &&
277
+ lastUser.id < lastAssistant.id
278
+ ) {
279
+ log.info('exiting loop', { sessionID });
280
+ break;
266
281
  }
267
282
 
268
- step++
283
+ step++;
269
284
  if (step === 1)
270
285
  ensureTitle({
271
286
  session: await Session.get(sessionID),
272
287
  modelID: lastUser.model.modelID,
273
288
  providerID: lastUser.model.providerID,
274
- message: msgs.find((m) => m.info.role === "user")!,
289
+ message: msgs.find((m) => m.info.role === 'user')!,
275
290
  history: msgs,
276
- })
291
+ });
277
292
 
278
- const model = await Provider.getModel(lastUser.model.providerID, lastUser.model.modelID)
279
- const task = tasks.pop()
293
+ const model = await Provider.getModel(
294
+ lastUser.model.providerID,
295
+ lastUser.model.modelID
296
+ );
297
+ const task = tasks.pop();
280
298
 
281
299
  // pending subtask
282
300
  // TODO: centralize "invoke tool" logic
283
- if (task?.type === "subtask") {
284
- const taskTool = await TaskTool.init()
301
+ if (task?.type === 'subtask') {
302
+ const taskTool = await TaskTool.init();
285
303
  const assistantMessage = (await Session.updateMessage({
286
- id: Identifier.ascending("message"),
287
- role: "assistant",
304
+ id: Identifier.ascending('message'),
305
+ role: 'assistant',
288
306
  parentID: lastUser.id,
289
307
  sessionID,
290
308
  mode: task.agent,
@@ -304,16 +322,16 @@ export namespace SessionPrompt {
304
322
  time: {
305
323
  created: Date.now(),
306
324
  },
307
- })) as MessageV2.Assistant
325
+ })) as MessageV2.Assistant;
308
326
  let part = (await Session.updatePart({
309
- id: Identifier.ascending("part"),
327
+ id: Identifier.ascending('part'),
310
328
  messageID: assistantMessage.id,
311
329
  sessionID: assistantMessage.sessionID,
312
- type: "tool",
330
+ type: 'tool',
313
331
  callID: ulid(),
314
332
  tool: TaskTool.id,
315
333
  state: {
316
- status: "running",
334
+ status: 'running',
317
335
  input: {
318
336
  prompt: task.prompt,
319
337
  description: task.description,
@@ -323,7 +341,7 @@ export namespace SessionPrompt {
323
341
  start: Date.now(),
324
342
  },
325
343
  },
326
- })) as MessageV2.ToolPart
344
+ })) as MessageV2.ToolPart;
327
345
  const result = await taskTool
328
346
  .execute(
329
347
  {
@@ -339,24 +357,24 @@ export namespace SessionPrompt {
339
357
  async metadata(input) {
340
358
  await Session.updatePart({
341
359
  ...part,
342
- type: "tool",
360
+ type: 'tool',
343
361
  state: {
344
362
  ...part.state,
345
363
  ...input,
346
364
  },
347
- } satisfies MessageV2.ToolPart)
365
+ } satisfies MessageV2.ToolPart);
348
366
  },
349
- },
367
+ }
350
368
  )
351
- .catch(() => {})
352
- assistantMessage.finish = "tool-calls"
353
- assistantMessage.time.completed = Date.now()
354
- await Session.updateMessage(assistantMessage)
355
- if (result && part.state.status === "running") {
369
+ .catch(() => {});
370
+ assistantMessage.finish = 'tool-calls';
371
+ assistantMessage.time.completed = Date.now();
372
+ await Session.updateMessage(assistantMessage);
373
+ if (result && part.state.status === 'running') {
356
374
  await Session.updatePart({
357
375
  ...part,
358
376
  state: {
359
- status: "completed",
377
+ status: 'completed',
360
378
  input: part.state.input,
361
379
  title: result.title,
362
380
  metadata: result.metadata,
@@ -367,28 +385,31 @@ export namespace SessionPrompt {
367
385
  end: Date.now(),
368
386
  },
369
387
  },
370
- } satisfies MessageV2.ToolPart)
388
+ } satisfies MessageV2.ToolPart);
371
389
  }
372
390
  if (!result) {
373
391
  await Session.updatePart({
374
392
  ...part,
375
393
  state: {
376
- status: "error",
377
- error: "Tool execution failed",
394
+ status: 'error',
395
+ error: 'Tool execution failed',
378
396
  time: {
379
- start: part.state.status === "running" ? part.state.time.start : Date.now(),
397
+ start:
398
+ part.state.status === 'running'
399
+ ? part.state.time.start
400
+ : Date.now(),
380
401
  end: Date.now(),
381
402
  },
382
403
  metadata: part.metadata,
383
404
  input: part.state.input,
384
405
  },
385
- } satisfies MessageV2.ToolPart)
406
+ } satisfies MessageV2.ToolPart);
386
407
  }
387
- continue
408
+ continue;
388
409
  }
389
410
 
390
411
  // pending compaction
391
- if (task?.type === "compaction") {
412
+ if (task?.type === 'compaction') {
392
413
  const result = await SessionCompaction.process({
393
414
  messages: msgs,
394
415
  parentID: lastUser.id,
@@ -398,35 +419,38 @@ export namespace SessionPrompt {
398
419
  modelID: model.modelID,
399
420
  },
400
421
  sessionID,
401
- })
402
- if (result === "stop") break
403
- continue
422
+ });
423
+ if (result === 'stop') break;
424
+ continue;
404
425
  }
405
426
 
406
427
  // context overflow, needs compaction
407
428
  if (
408
429
  lastFinished &&
409
430
  lastFinished.summary !== true &&
410
- SessionCompaction.isOverflow({ tokens: lastFinished.tokens, model: model.info })
431
+ SessionCompaction.isOverflow({
432
+ tokens: lastFinished.tokens,
433
+ model: model.info,
434
+ })
411
435
  ) {
412
436
  await SessionCompaction.create({
413
437
  sessionID,
414
438
  model: lastUser.model,
415
- })
416
- continue
439
+ });
440
+ continue;
417
441
  }
418
442
 
419
443
  // normal processing
420
- const agent = await Agent.get(lastUser.agent)
444
+ const agent = await Agent.get(lastUser.agent);
421
445
  msgs = insertReminders({
422
446
  messages: msgs,
423
447
  agent,
424
- })
448
+ });
425
449
  const processor = SessionProcessor.create({
426
450
  assistantMessage: (await Session.updateMessage({
427
- id: Identifier.ascending("message"),
451
+ id: Identifier.ascending('message'),
428
452
  parentID: lastUser.id,
429
- role: "assistant",
453
+ role: 'assistant',
430
454
  mode: agent.name,
431
455
  path: {
432
456
  cwd: Instance.directory,
@@ -450,58 +474,114 @@ export namespace SessionPrompt {
450
474
  model: model.info,
451
475
  providerID: model.providerID,
452
476
  abort,
453
- })
477
+ });
454
478
  const system = await resolveSystemPrompt({
455
479
  providerID: model.providerID,
456
480
  modelID: model.info.id,
457
481
  agent,
458
482
  system: lastUser.system,
459
483
  appendSystem: lastUser.appendSystem,
460
- })
484
+ });
461
485
  const tools = await resolveTools({
462
486
  agent,
463
487
  sessionID,
464
488
  model: lastUser.model,
465
489
  tools: lastUser.tools,
466
490
  processor,
467
- })
491
+ });
468
492
  const params = {
469
493
  temperature: model.info.temperature
470
- ? (agent.temperature ?? ProviderTransform.temperature(model.providerID, model.modelID))
494
+ ? (agent.temperature ??
495
+ ProviderTransform.temperature(model.providerID, model.modelID))
471
496
  : undefined,
472
- topP: agent.topP ?? ProviderTransform.topP(model.providerID, model.modelID),
497
+ topP:
498
+ agent.topP ?? ProviderTransform.topP(model.providerID, model.modelID),
473
499
  options: {
474
- ...ProviderTransform.options(model.providerID, model.modelID, model.npm ?? "", sessionID),
500
+ ...ProviderTransform.options(
501
+ model.providerID,
502
+ model.modelID,
503
+ model.npm ?? '',
504
+ sessionID
505
+ ),
475
506
  ...model.info.options,
476
507
  ...agent.options,
477
508
  },
478
- }
509
+ };
479
510
 
480
511
  if (step === 1) {
481
512
  SessionSummary.summarize({
482
513
  sessionID: sessionID,
483
514
  messageID: lastUser.id,
484
- })
515
+ });
516
+ }
517
+
518
+ // Verbose logging: output request details for debugging
519
+ if (Flag.OPENCODE_VERBOSE) {
520
+ const systemTokens = system.reduce(
521
+ (acc, s) => acc + Token.estimate(s),
522
+ 0
523
+ );
524
+ const userMessages = msgs.filter((m) => m.info.role === 'user');
525
+ const userTokens = userMessages.reduce(
526
+ (acc, m) =>
527
+ acc +
528
+ m.parts.reduce(
529
+ (a, p) => a + Token.estimate('text' in p ? p.text || '' : ''),
530
+ 0
531
+ ),
532
+ 0
533
+ );
534
+ const totalEstimatedTokens = systemTokens + userTokens;
535
+
536
+ log.info('=== VERBOSE: API Request Details ===');
537
+ log.info(`Model: ${model.providerID}/${model.modelID}`);
538
+ log.info(`Session ID: ${sessionID}`);
539
+ log.info(`Agent: ${agent.name}`);
540
+ log.info(`Temperature: ${params.temperature ?? 'default'}`);
541
+ log.info(`Top P: ${params.topP ?? 'default'}`);
542
+ log.info(
543
+ `Active Tools: ${Object.keys(tools)
544
+ .filter((x) => x !== 'invalid')
545
+ .join(', ')}`
546
+ );
547
+ log.info('--- System Prompt ---');
548
+ for (let i = 0; i < system.length; i++) {
549
+ const tokens = Token.estimate(system[i]);
550
+ log.info(`System Message ${i + 1} (${tokens} tokens estimated):`);
551
+ log.info(
552
+ system[i].slice(0, 2000) +
553
+ (system[i].length > 2000 ? '... [truncated]' : '')
554
+ );
555
+ }
556
+ log.info('--- Token Summary ---');
557
+ log.info(`System prompt tokens (estimated): ${systemTokens}`);
558
+ log.info(`User message tokens (estimated): ${userTokens}`);
559
+ log.info(`Total estimated tokens: ${totalEstimatedTokens}`);
560
+ log.info(
561
+ `Model context limit: ${model.info.limit.context || 'unknown'}`
562
+ );
563
+ log.info(`Model output limit: ${model.info.limit.output || 'unknown'}`);
564
+ log.info('=== END VERBOSE ===');
485
565
  }
486
566
 
487
567
  const result = await processor.process(() =>
488
568
  streamText({
489
569
  onError(error) {
490
- log.error("stream error", {
570
+ log.error('stream error', {
491
571
  error,
492
- })
572
+ });
493
573
  },
494
574
  async experimental_repairToolCall(input) {
495
- const lower = input.toolCall.toolName.toLowerCase()
575
+ const lower = input.toolCall.toolName.toLowerCase();
496
576
  if (lower !== input.toolCall.toolName && tools[lower]) {
497
- log.info("repairing tool call", {
577
+ log.info('repairing tool call', {
498
578
  tool: input.toolCall.toolName,
499
579
  repaired: lower,
500
- })
580
+ });
501
581
  return {
502
582
  ...input.toolCall,
503
583
  toolName: lower,
504
- }
584
+ };
505
585
  }
506
586
  return {
507
587
  ...input.toolCall,
@@ -509,53 +589,60 @@ export namespace SessionPrompt {
509
589
  tool: input.toolCall.toolName,
510
590
  error: input.error.message,
511
591
  }),
512
- toolName: "invalid",
513
- }
592
+ toolName: 'invalid',
593
+ };
514
594
  },
515
595
  headers: {
516
- ...(model.providerID === "opencode"
596
+ ...(model.providerID === 'opencode'
517
597
  ? {
518
- "x-opencode-session": sessionID,
519
- "x-opencode-request": lastUser.id,
598
+ 'x-opencode-session': sessionID,
599
+ 'x-opencode-request': lastUser.id,
520
600
  }
521
601
  : undefined),
522
602
  ...model.info.headers,
523
603
  },
524
604
  // set to 0, we handle loop
525
605
  maxRetries: 0,
526
- activeTools: Object.keys(tools).filter((x) => x !== "invalid"),
606
+ activeTools: Object.keys(tools).filter((x) => x !== 'invalid'),
527
607
  maxOutputTokens: ProviderTransform.maxOutputTokens(
528
608
  model.providerID,
529
609
  params.options,
530
610
  model.info.limit.output,
531
- OUTPUT_TOKEN_MAX,
611
+ OUTPUT_TOKEN_MAX
532
612
  ),
533
613
  abortSignal: abort,
534
- providerOptions: ProviderTransform.providerOptions(model.npm, model.providerID, params.options),
614
+ providerOptions: ProviderTransform.providerOptions(
615
+ model.npm,
616
+ model.providerID,
617
+ params.options
618
+ ),
535
619
  stopWhen: stepCountIs(1),
536
620
  temperature: params.temperature,
537
621
  topP: params.topP,
538
622
  messages: [
539
623
  ...system.map(
540
624
  (x): ModelMessage => ({
541
- role: "system",
625
+ role: 'system',
542
626
  content: x,
543
- }),
627
+ })
544
628
  ),
545
629
  ...MessageV2.toModelMessage(
546
630
  msgs.filter((m) => {
547
- if (m.info.role !== "assistant" || m.info.error === undefined) {
548
- return true
631
+ if (m.info.role !== 'assistant' || m.info.error === undefined) {
632
+ return true;
549
633
  }
550
634
  if (
551
635
  MessageV2.AbortedError.isInstance(m.info.error) &&
552
- m.parts.some((part) => part.type !== "step-start" && part.type !== "reasoning")
636
+ m.parts.some(
637
+ (part) =>
638
+ part.type !== 'step-start' && part.type !== 'reasoning'
639
+ )
553
640
  ) {
554
- return true
641
+ return true;
555
642
  }
556
643
 
557
- return false
558
- }),
644
+ return false;
645
+ })
559
646
  ),
560
647
  ],
561
648
  tools: model.info.tool_call === false ? undefined : tools,
@@ -564,93 +651,116 @@ export namespace SessionPrompt {
564
651
  middleware: [
565
652
  {
566
653
  async transformParams(args) {
567
- if (args.type === "stream") {
654
+ if (args.type === 'stream') {
568
655
  // @ts-expect-error
569
- args.params.prompt = ProviderTransform.message(args.params.prompt, model.providerID, model.modelID)
656
+ args.params.prompt = ProviderTransform.message(
657
+ args.params.prompt,
658
+ model.providerID,
659
+ model.modelID
660
+ );
570
661
  }
571
- return args.params
662
+ return args.params;
572
663
  },
573
664
  },
574
665
  ],
575
666
  }),
576
- }),
577
- )
578
- if (result === "stop") break
579
- continue
667
+ })
668
+ );
669
+ if (result === 'stop') break;
670
+ continue;
580
671
  }
581
- SessionCompaction.prune({ sessionID })
672
+ SessionCompaction.prune({ sessionID });
582
673
  for await (const item of MessageV2.stream(sessionID)) {
583
- if (item.info.role === "user") continue
584
- const queued = state()[sessionID]?.callbacks ?? []
674
+ if (item.info.role === 'user') continue;
675
+ const queued = state()[sessionID]?.callbacks ?? [];
585
676
  for (const q of queued) {
586
- q.resolve(item)
677
+ q.resolve(item);
587
678
  }
588
- return item
679
+ return item;
589
680
  }
590
- throw new Error("Impossible")
591
- })
681
+ throw new Error('Impossible');
682
+ });
592
683
 
593
- async function resolveModel(input: { model: PromptInput["model"]; agent: Agent.Info }) {
684
+ async function resolveModel(input: {
685
+ model: PromptInput['model'];
686
+ agent: Agent.Info;
687
+ }) {
594
688
  if (input.model) {
595
- return input.model
689
+ return input.model;
596
690
  }
597
691
  if (input.agent.model) {
598
- return input.agent.model
692
+ return input.agent.model;
599
693
  }
600
- return Provider.defaultModel()
694
+ return Provider.defaultModel();
601
695
  }
602
696
 
603
697
  async function resolveSystemPrompt(input: {
604
- system?: string
605
- appendSystem?: string
606
- agent: Agent.Info
607
- providerID: string
608
- modelID: string
698
+ system?: string;
699
+ appendSystem?: string;
700
+ agent: Agent.Info;
701
+ providerID: string;
702
+ modelID: string;
609
703
  }) {
610
- let system = SystemPrompt.header(input.providerID)
704
+ // When --system-message is provided, use it exclusively without any
705
+ // additional context (no environment, no custom instructions, no header).
706
+ // This is critical for models with low token limits (e.g., qwen3-32b with 6K TPM).
707
+ if (input.system) {
708
+ return [input.system];
709
+ }
710
+
711
+ let system = SystemPrompt.header(input.providerID);
611
712
  system.push(
612
713
  ...(() => {
613
- if (input.system) return [input.system]
614
- const base = input.agent.prompt ? [input.agent.prompt] : SystemPrompt.provider(input.modelID)
714
+ const base = input.agent.prompt
715
+ ? [input.agent.prompt]
716
+ : SystemPrompt.provider(input.modelID);
615
717
  if (input.appendSystem) {
616
- return [base[0] + "\n" + input.appendSystem]
718
+ return [base[0] + '\n' + input.appendSystem];
617
719
  }
618
- return base
619
- })(),
620
- )
621
- if (!input.system) {
622
- system.push(...(await SystemPrompt.environment()))
623
- system.push(...(await SystemPrompt.custom()))
624
- }
720
+ return base;
721
+ })()
722
+ );
723
+ system.push(...(await SystemPrompt.environment()));
724
+ system.push(...(await SystemPrompt.custom()));
725
+
625
726
  // max 2 system prompt messages for caching purposes
626
- const [first, ...rest] = system
627
- system = [first, rest.join("\n")]
628
- return system
727
+ const [first, ...rest] = system;
728
+ system = [first, rest.join('\n')];
729
+ return system;
629
730
  }
630
731
 
631
732
  async function resolveTools(input: {
632
- agent: Agent.Info
733
+ agent: Agent.Info;
633
734
  model: {
634
- providerID: string
635
- modelID: string
636
- }
637
- sessionID: string
638
- tools?: Record<string, boolean>
639
- processor: SessionProcessor.Info
735
+ providerID: string;
736
+ modelID: string;
737
+ };
738
+ sessionID: string;
739
+ tools?: Record<string, boolean>;
740
+ processor: SessionProcessor.Info;
640
741
  }) {
641
- const tools: Record<string, AITool> = {}
742
+ const tools: Record<string, AITool> = {};
642
743
  const enabledTools = pipe(
643
744
  input.agent.tools,
644
- mergeDeep(await ToolRegistry.enabled(input.model.providerID, input.model.modelID, input.agent)),
645
- mergeDeep(input.tools ?? {}),
646
- )
647
- for (const item of await ToolRegistry.tools(input.model.providerID, input.model.modelID)) {
648
- if (Wildcard.all(item.id, enabledTools) === false) continue
745
+ mergeDeep(
746
+ await ToolRegistry.enabled(
747
+ input.model.providerID,
748
+ input.model.modelID,
749
+ input.agent
750
+ )
751
+ ),
752
+ mergeDeep(input.tools ?? {})
753
+ );
754
+ for (const item of await ToolRegistry.tools(
755
+ input.model.providerID,
756
+ input.model.modelID
757
+ )) {
758
+ if (Wildcard.all(item.id, enabledTools) === false) continue;
649
759
  const schema = ProviderTransform.schema(
650
760
  input.model.providerID,
651
761
  input.model.modelID,
652
- z.toJSONSchema(item.parameters),
653
- )
762
+ z.toJSONSchema(item.parameters)
763
+ );
654
764
  tools[item.id] = tool({
655
765
  id: item.id as any,
656
766
  description: item.description,
@@ -664,84 +774,86 @@ export namespace SessionPrompt {
664
774
  extra: input.model,
665
775
  agent: input.agent.name,
666
776
  metadata: async (val) => {
667
- const match = input.processor.partFromToolCall(options.toolCallId)
668
- if (match && match.state.status === "running") {
777
+ const match = input.processor.partFromToolCall(
778
+ options.toolCallId
779
+ );
780
+ if (match && match.state.status === 'running') {
669
781
  await Session.updatePart({
670
782
  ...match,
671
783
  state: {
672
784
  title: val.title,
673
785
  metadata: val.metadata,
674
- status: "running",
786
+ status: 'running',
675
787
  input: args,
676
788
  time: {
677
789
  start: Date.now(),
678
790
  },
679
791
  },
680
- })
792
+ });
681
793
  }
682
794
  },
683
- })
684
- return result
795
+ });
796
+ return result;
685
797
  },
686
798
  toModelOutput(result) {
687
799
  return {
688
- type: "text",
800
+ type: 'text',
689
801
  value: result.output,
690
- }
802
+ };
691
803
  },
692
- })
804
+ });
693
805
  }
694
806
 
695
807
  for (const [key, item] of Object.entries(await MCP.tools())) {
696
- if (Wildcard.all(key, enabledTools) === false) continue
697
- const execute = item.execute
698
- if (!execute) continue
808
+ if (Wildcard.all(key, enabledTools) === false) continue;
809
+ const execute = item.execute;
810
+ if (!execute) continue;
699
811
  item.execute = async (args, opts) => {
700
- const result = await execute(args, opts)
812
+ const result = await execute(args, opts);
701
813
 
702
- const textParts: string[] = []
703
- const attachments: MessageV2.FilePart[] = []
814
+ const textParts: string[] = [];
815
+ const attachments: MessageV2.FilePart[] = [];
704
816
 
705
817
  for (const item of result.content) {
706
- if (item.type === "text") {
707
- textParts.push(item.text)
708
- } else if (item.type === "image") {
818
+ if (item.type === 'text') {
819
+ textParts.push(item.text);
820
+ } else if (item.type === 'image') {
709
821
  attachments.push({
710
- id: Identifier.ascending("part"),
822
+ id: Identifier.ascending('part'),
711
823
  sessionID: input.sessionID,
712
824
  messageID: input.processor.message.id,
713
- type: "file",
825
+ type: 'file',
714
826
  mime: item.mimeType,
715
827
  url: `data:${item.mimeType};base64,${item.data}`,
716
- })
828
+ });
717
829
  }
718
830
  // Add support for other types if needed
719
831
  }
720
832
 
721
833
  return {
722
- title: "",
834
+ title: '',
723
835
  metadata: result.metadata ?? {},
724
- output: textParts.join("\n\n"),
836
+ output: textParts.join('\n\n'),
725
837
  attachments,
726
838
  content: result.content, // directly return content to preserve ordering when outputting to model
727
- }
728
- }
839
+ };
840
+ };
729
841
  item.toModelOutput = (result) => {
730
842
  return {
731
- type: "text",
843
+ type: 'text',
732
844
  value: result.output,
733
- }
734
- }
735
- tools[key] = item
845
+ };
846
+ };
847
+ tools[key] = item;
736
848
  }
737
- return tools
849
+ return tools;
738
850
  }
739
851
 
740
852
  async function createUserMessage(input: PromptInput) {
741
- const agent = await Agent.get(input.agent ?? "build")
853
+ const agent = await Agent.get(input.agent ?? 'build');
742
854
  const info: MessageV2.Info = {
743
- id: input.messageID ?? Identifier.ascending("message"),
744
- role: "user",
855
+ id: input.messageID ?? Identifier.ascending('message'),
856
+ role: 'user',
745
857
  sessionID: input.sessionID,
746
858
  time: {
747
859
  created: Date.now(),
@@ -754,80 +866,80 @@ export namespace SessionPrompt {
754
866
  model: input.model,
755
867
  agent,
756
868
  }),
757
- }
869
+ };
758
870
 
759
871
  const parts = await Promise.all(
760
872
  input.parts.map(async (part): Promise<MessageV2.Part[]> => {
761
- if (part.type === "file") {
762
- const url = new URL(part.url)
873
+ if (part.type === 'file') {
874
+ const url = new URL(part.url);
763
875
  switch (url.protocol) {
764
- case "data:":
765
- if (part.mime === "text/plain") {
876
+ case 'data:':
877
+ if (part.mime === 'text/plain') {
766
878
  return [
767
879
  {
768
- id: Identifier.ascending("part"),
880
+ id: Identifier.ascending('part'),
769
881
  messageID: info.id,
770
882
  sessionID: input.sessionID,
771
- type: "text",
883
+ type: 'text',
772
884
  synthetic: true,
773
885
  text: `Called the Read tool with the following input: ${JSON.stringify({ filePath: part.filename })}`,
774
886
  },
775
887
  {
776
- id: Identifier.ascending("part"),
888
+ id: Identifier.ascending('part'),
777
889
  messageID: info.id,
778
890
  sessionID: input.sessionID,
779
- type: "text",
891
+ type: 'text',
780
892
  synthetic: true,
781
- text: Buffer.from(part.url, "base64url").toString(),
893
+ text: Buffer.from(part.url, 'base64url').toString(),
782
894
  },
783
895
  {
784
896
  ...part,
785
- id: part.id ?? Identifier.ascending("part"),
897
+ id: part.id ?? Identifier.ascending('part'),
786
898
  messageID: info.id,
787
899
  sessionID: input.sessionID,
788
900
  },
789
- ]
901
+ ];
790
902
  }
791
- break
792
- case "file:":
793
- log.info("file", { mime: part.mime })
903
+ break;
904
+ case 'file:':
905
+ log.info('file', { mime: part.mime });
794
906
  // have to normalize, symbol search returns absolute paths
795
907
  // Decode the pathname since URL constructor doesn't automatically decode it
796
- const filepath = fileURLToPath(part.url)
797
- const stat = await Bun.file(filepath).stat()
908
+ const filepath = fileURLToPath(part.url);
909
+ const stat = await Bun.file(filepath).stat();
798
910
 
799
911
  if (stat.isDirectory()) {
800
- part.mime = "application/x-directory"
912
+ part.mime = 'application/x-directory';
801
913
  }
802
914
 
803
- if (part.mime === "text/plain") {
804
- let offset: number | undefined = undefined
805
- let limit: number | undefined = undefined
915
+ if (part.mime === 'text/plain') {
916
+ let offset: number | undefined = undefined;
917
+ let limit: number | undefined = undefined;
806
918
  const range = {
807
- start: url.searchParams.get("start"),
808
- end: url.searchParams.get("end"),
809
- }
919
+ start: url.searchParams.get('start'),
920
+ end: url.searchParams.get('end'),
921
+ };
810
922
  if (range.start != null) {
811
- const filePathURI = part.url.split("?")[0]
812
- let start = parseInt(range.start)
813
- let end = range.end ? parseInt(range.end) : undefined
814
- offset = Math.max(start - 1, 0)
923
+ const filePathURI = part.url.split('?')[0];
924
+ let start = parseInt(range.start);
925
+ let end = range.end ? parseInt(range.end) : undefined;
926
+ offset = Math.max(start - 1, 0);
815
927
  if (end) {
816
- limit = end - offset
928
+ limit = end - offset;
817
929
  }
818
930
  }
819
- const args = { filePath: filepath, offset, limit }
931
+ const args = { filePath: filepath, offset, limit };
820
932
 
821
933
  const pieces: MessageV2.Part[] = [
822
934
  {
823
- id: Identifier.ascending("part"),
935
+ id: Identifier.ascending('part'),
824
936
  messageID: info.id,
825
937
  sessionID: input.sessionID,
826
- type: "text",
938
+ type: 'text',
827
939
  synthetic: true,
828
940
  text: `Called the Read tool with the following input: ${JSON.stringify(args)}`,
829
941
  },
830
- ]
942
+ ];
831
943
 
832
944
  await ReadTool.init()
833
945
  .then(async (t) => {
@@ -838,48 +950,49 @@ export namespace SessionPrompt {
838
950
  messageID: info.id,
839
951
  extra: { bypassCwdCheck: true, ...info.model },
840
952
  metadata: async () => {},
841
- })
953
+ });
842
954
  pieces.push(
843
955
  {
844
- id: Identifier.ascending("part"),
956
+ id: Identifier.ascending('part'),
845
957
  messageID: info.id,
846
958
  sessionID: input.sessionID,
847
- type: "text",
959
+ type: 'text',
848
960
  synthetic: true,
849
961
  text: result.output,
850
962
  },
851
963
  {
852
964
  ...part,
853
- id: part.id ?? Identifier.ascending("part"),
965
+ id: part.id ?? Identifier.ascending('part'),
854
966
  messageID: info.id,
855
967
  sessionID: input.sessionID,
856
- },
857
- )
968
+ }
969
+ );
858
970
  })
859
971
  .catch((error) => {
860
- log.error("failed to read file", { error })
861
- const message = error instanceof Error ? error.message : error.toString()
972
+ log.error('failed to read file', { error });
973
+ const message =
974
+ error instanceof Error ? error.message : error.toString();
862
975
  Bus.publish(Session.Event.Error, {
863
976
  sessionID: input.sessionID,
864
977
  error: new NamedError.Unknown({
865
978
  message,
866
979
  }).toObject(),
867
- })
980
+ });
868
981
  pieces.push({
869
- id: Identifier.ascending("part"),
982
+ id: Identifier.ascending('part'),
870
983
  messageID: info.id,
871
984
  sessionID: input.sessionID,
872
- type: "text",
985
+ type: 'text',
873
986
  synthetic: true,
874
987
  text: `Read tool failed to read ${filepath} with the following error: ${message}`,
875
- })
876
- })
988
+ });
989
+ });
877
990
 
878
- return pieces
991
+ return pieces;
879
992
  }
880
993
 
881
- if (part.mime === "application/x-directory") {
882
- const args = { path: filepath }
994
+ if (part.mime === 'application/x-directory') {
995
+ const args = { path: filepath };
883
996
  const result = await ListTool.init().then((t) =>
884
997
  t.execute(args, {
885
998
  sessionID: input.sessionID,
@@ -888,168 +1001,177 @@ export namespace SessionPrompt {
888
1001
  messageID: info.id,
889
1002
  extra: { bypassCwdCheck: true },
890
1003
  metadata: async () => {},
891
- }),
892
- )
1004
+ })
1005
+ );
893
1006
  return [
894
1007
  {
895
- id: Identifier.ascending("part"),
1008
+ id: Identifier.ascending('part'),
896
1009
  messageID: info.id,
897
1010
  sessionID: input.sessionID,
898
- type: "text",
1011
+ type: 'text',
899
1012
  synthetic: true,
900
1013
  text: `Called the list tool with the following input: ${JSON.stringify(args)}`,
901
1014
  },
902
1015
  {
903
- id: Identifier.ascending("part"),
1016
+ id: Identifier.ascending('part'),
904
1017
  messageID: info.id,
905
1018
  sessionID: input.sessionID,
906
- type: "text",
1019
+ type: 'text',
907
1020
  synthetic: true,
908
1021
  text: result.output,
909
1022
  },
910
1023
  {
911
1024
  ...part,
912
- id: part.id ?? Identifier.ascending("part"),
1025
+ id: part.id ?? Identifier.ascending('part'),
913
1026
  messageID: info.id,
914
1027
  sessionID: input.sessionID,
915
1028
  },
916
- ]
1029
+ ];
917
1030
  }
918
1031
 
919
- const file = Bun.file(filepath)
920
- FileTime.read(input.sessionID, filepath)
1032
+ const file = Bun.file(filepath);
1033
+ FileTime.read(input.sessionID, filepath);
921
1034
  return [
922
1035
  {
923
- id: Identifier.ascending("part"),
1036
+ id: Identifier.ascending('part'),
924
1037
  messageID: info.id,
925
1038
  sessionID: input.sessionID,
926
- type: "text",
1039
+ type: 'text',
927
1040
  text: `Called the Read tool with the following input: {\"filePath\":\"${filepath}\"}`,
928
1041
  synthetic: true,
929
1042
  },
930
1043
  {
931
- id: part.id ?? Identifier.ascending("part"),
1044
+ id: part.id ?? Identifier.ascending('part'),
932
1045
  messageID: info.id,
933
1046
  sessionID: input.sessionID,
934
- type: "file",
935
- url: `data:${part.mime};base64,` + Buffer.from(await file.bytes()).toString("base64"),
1047
+ type: 'file',
1048
+ url:
1049
+ `data:${part.mime};base64,` +
1050
+ Buffer.from(await file.bytes()).toString('base64'),
936
1051
  mime: part.mime,
937
1052
  filename: part.filename!,
938
1053
  source: part.source,
939
1054
  },
940
- ]
1055
+ ];
941
1056
  }
942
1057
  }
943
1058
 
944
- if (part.type === "agent") {
1059
+ if (part.type === 'agent') {
945
1060
  return [
946
1061
  {
947
- id: Identifier.ascending("part"),
1062
+ id: Identifier.ascending('part'),
948
1063
  ...part,
949
1064
  messageID: info.id,
950
1065
  sessionID: input.sessionID,
951
1066
  },
952
1067
  {
953
- id: Identifier.ascending("part"),
1068
+ id: Identifier.ascending('part'),
954
1069
  messageID: info.id,
955
1070
  sessionID: input.sessionID,
956
- type: "text",
1071
+ type: 'text',
957
1072
  synthetic: true,
958
1073
  text:
959
- "Use the above message and context to generate a prompt and call the task tool with subagent: " +
1074
+ 'Use the above message and context to generate a prompt and call the task tool with subagent: ' +
960
1075
  part.name,
961
1076
  },
962
- ]
1077
+ ];
963
1078
  }
964
1079
 
965
1080
  return [
966
1081
  {
967
- id: Identifier.ascending("part"),
1082
+ id: Identifier.ascending('part'),
968
1083
  ...part,
969
1084
  messageID: info.id,
970
1085
  sessionID: input.sessionID,
971
1086
  },
972
- ]
973
- }),
974
- ).then((x) => x.flat())
1087
+ ];
1088
+ })
1089
+ ).then((x) => x.flat());
975
1090
 
976
- await Session.updateMessage(info)
1091
+ await Session.updateMessage(info);
977
1092
  for (const part of parts) {
978
- await Session.updatePart(part)
1093
+ await Session.updatePart(part);
979
1094
  }
980
1095
 
981
1096
  return {
982
1097
  info,
983
1098
  parts,
984
- }
1099
+ };
985
1100
  }
986
1101
 
987
- function insertReminders(input: { messages: MessageV2.WithParts[]; agent: Agent.Info }) {
988
- const userMessage = input.messages.findLast((msg) => msg.info.role === "user")
989
- if (!userMessage) return input.messages
990
- if (input.agent.name === "plan") {
1102
+ function insertReminders(input: {
1103
+ messages: MessageV2.WithParts[];
1104
+ agent: Agent.Info;
1105
+ }) {
1106
+ const userMessage = input.messages.findLast(
1107
+ (msg) => msg.info.role === 'user'
1108
+ );
1109
+ if (!userMessage) return input.messages;
1110
+ if (input.agent.name === 'plan') {
991
1111
  userMessage.parts.push({
992
- id: Identifier.ascending("part"),
1112
+ id: Identifier.ascending('part'),
993
1113
  messageID: userMessage.info.id,
994
1114
  sessionID: userMessage.info.sessionID,
995
- type: "text",
1115
+ type: 'text',
996
1116
  text: PROMPT_PLAN,
997
1117
  synthetic: true,
998
- })
1118
+ });
999
1119
  }
1000
- const wasPlan = input.messages.some((msg) => msg.info.role === "assistant" && msg.info.mode === "plan")
1001
- if (wasPlan && input.agent.name === "build") {
1120
+ const wasPlan = input.messages.some(
1121
+ (msg) => msg.info.role === 'assistant' && msg.info.mode === 'plan'
1122
+ );
1123
+ if (wasPlan && input.agent.name === 'build') {
1002
1124
  userMessage.parts.push({
1003
- id: Identifier.ascending("part"),
1125
+ id: Identifier.ascending('part'),
1004
1126
  messageID: userMessage.info.id,
1005
1127
  sessionID: userMessage.info.sessionID,
1006
- type: "text",
1128
+ type: 'text',
1007
1129
  text: BUILD_SWITCH,
1008
1130
  synthetic: true,
1009
- })
1131
+ });
1010
1132
  }
1011
- return input.messages
1133
+ return input.messages;
1012
1134
  }
1013
1135
 
1014
1136
  export const ShellInput = z.object({
1015
- sessionID: Identifier.schema("session"),
1137
+ sessionID: Identifier.schema('session'),
1016
1138
  agent: z.string(),
1017
1139
  command: z.string(),
1018
- })
1019
- export type ShellInput = z.infer<typeof ShellInput>
1140
+ });
1141
+ export type ShellInput = z.infer<typeof ShellInput>;
1020
1142
  export async function shell(input: ShellInput) {
1021
- const session = await Session.get(input.sessionID)
1143
+ const session = await Session.get(input.sessionID);
1022
1144
  if (session.revert) {
1023
- SessionRevert.cleanup(session)
1145
+ SessionRevert.cleanup(session);
1024
1146
  }
1025
- const agent = await Agent.get(input.agent)
1026
- const model = await resolveModel({ agent, model: undefined })
1147
+ const agent = await Agent.get(input.agent);
1148
+ const model = await resolveModel({ agent, model: undefined });
1027
1149
  const userMsg: MessageV2.User = {
1028
- id: Identifier.ascending("message"),
1150
+ id: Identifier.ascending('message'),
1029
1151
  sessionID: input.sessionID,
1030
1152
  time: {
1031
1153
  created: Date.now(),
1032
1154
  },
1033
- role: "user",
1155
+ role: 'user',
1034
1156
  agent: input.agent,
1035
1157
  model: {
1036
1158
  providerID: model.providerID,
1037
1159
  modelID: model.modelID,
1038
1160
  },
1039
- }
1040
- await Session.updateMessage(userMsg)
1161
+ };
1162
+ await Session.updateMessage(userMsg);
1041
1163
  const userPart: MessageV2.Part = {
1042
- type: "text",
1043
- id: Identifier.ascending("part"),
1164
+ type: 'text',
1165
+ id: Identifier.ascending('part'),
1044
1166
  messageID: userMsg.id,
1045
1167
  sessionID: input.sessionID,
1046
- text: "The following tool was executed by the user",
1168
+ text: 'The following tool was executed by the user',
1047
1169
  synthetic: true,
1048
- }
1049
- await Session.updatePart(userPart)
1170
+ };
1171
+ await Session.updatePart(userPart);
1050
1172
 
1051
1173
  const msg: MessageV2.Assistant = {
1052
- id: Identifier.ascending("message"),
1174
+ id: Identifier.ascending('message'),
1053
1175
  sessionID: input.sessionID,
1054
1176
  parentID: userMsg.id,
1055
1177
  mode: input.agent,
@@ -1061,7 +1183,7 @@ export namespace SessionPrompt {
1061
1183
  time: {
1062
1184
  created: Date.now(),
1063
1185
  },
1064
- role: "assistant",
1186
+ role: 'assistant',
1065
1187
  tokens: {
1066
1188
  input: 0,
1067
1189
  output: 0,
@@ -1070,17 +1192,17 @@ export namespace SessionPrompt {
1070
1192
  },
1071
1193
  modelID: model.modelID,
1072
1194
  providerID: model.providerID,
1073
- }
1074
- await Session.updateMessage(msg)
1195
+ };
1196
+ await Session.updateMessage(msg);
1075
1197
  const part: MessageV2.Part = {
1076
- type: "tool",
1077
- id: Identifier.ascending("part"),
1198
+ type: 'tool',
1199
+ id: Identifier.ascending('part'),
1078
1200
  messageID: msg.id,
1079
1201
  sessionID: input.sessionID,
1080
- tool: "bash",
1202
+ tool: 'bash',
1081
1203
  callID: ulid(),
1082
1204
  state: {
1083
- status: "running",
1205
+ status: 'running',
1084
1206
  time: {
1085
1207
  start: Date.now(),
1086
1208
  },
@@ -1088,22 +1210,22 @@ export namespace SessionPrompt {
1088
1210
  command: input.command,
1089
1211
  },
1090
1212
  },
1091
- }
1092
- await Session.updatePart(part)
1093
- const shell = process.env["SHELL"] ?? "bash"
1094
- const shellName = path.basename(shell)
1213
+ };
1214
+ await Session.updatePart(part);
1215
+ const shell = process.env['SHELL'] ?? 'bash';
1216
+ const shellName = path.basename(shell);
1095
1217
 
1096
1218
  const invocations: Record<string, { args: string[] }> = {
1097
1219
  nu: {
1098
- args: ["-c", input.command],
1220
+ args: ['-c', input.command],
1099
1221
  },
1100
1222
  fish: {
1101
- args: ["-c", input.command],
1223
+ args: ['-c', input.command],
1102
1224
  },
1103
1225
  zsh: {
1104
1226
  args: [
1105
- "-c",
1106
- "-l",
1227
+ '-c',
1228
+ '-l',
1107
1229
  `
1108
1230
  [[ -f ~/.zshenv ]] && source ~/.zshenv >/dev/null 2>&1 || true
1109
1231
  [[ -f "\${ZDOTDIR:-$HOME}/.zshrc" ]] && source "\${ZDOTDIR:-$HOME}/.zshrc" >/dev/null 2>&1 || true
@@ -1113,8 +1235,8 @@ export namespace SessionPrompt {
1113
1235
  },
1114
1236
  bash: {
1115
1237
  args: [
1116
- "-c",
1117
- "-l",
1238
+ '-c',
1239
+ '-l',
1118
1240
  `
1119
1241
  [[ -f ~/.bashrc ]] && source ~/.bashrc >/dev/null 2>&1 || true
1120
1242
  ${input.command}
@@ -1122,88 +1244,88 @@ export namespace SessionPrompt {
1122
1244
  ],
1123
1245
  },
1124
1246
  // Fallback: any shell that doesn't match those above
1125
- "": {
1126
- args: ["-c", "-l", `${input.command}`],
1247
+ '': {
1248
+ args: ['-c', '-l', `${input.command}`],
1127
1249
  },
1128
- }
1250
+ };
1129
1251
 
1130
- const matchingInvocation = invocations[shellName] ?? invocations[""]
1131
- const args = matchingInvocation?.args
1252
+ const matchingInvocation = invocations[shellName] ?? invocations[''];
1253
+ const args = matchingInvocation?.args;
1132
1254
 
1133
1255
  const proc = spawn(shell, args, {
1134
1256
  cwd: Instance.directory,
1135
1257
  detached: true,
1136
- stdio: ["ignore", "pipe", "pipe"],
1258
+ stdio: ['ignore', 'pipe', 'pipe'],
1137
1259
  env: {
1138
1260
  ...process.env,
1139
- TERM: "dumb",
1261
+ TERM: 'dumb',
1140
1262
  },
1141
- })
1263
+ });
1142
1264
 
1143
- let output = ""
1265
+ let output = '';
1144
1266
 
1145
- proc.stdout?.on("data", (chunk) => {
1146
- output += chunk.toString()
1147
- if (part.state.status === "running") {
1267
+ proc.stdout?.on('data', (chunk) => {
1268
+ output += chunk.toString();
1269
+ if (part.state.status === 'running') {
1148
1270
  part.state.metadata = {
1149
1271
  output: output,
1150
- description: "",
1151
- }
1152
- Session.updatePart(part)
1272
+ description: '',
1273
+ };
1274
+ Session.updatePart(part);
1153
1275
  }
1154
- })
1276
+ });
1155
1277
 
1156
- proc.stderr?.on("data", (chunk) => {
1157
- output += chunk.toString()
1158
- if (part.state.status === "running") {
1278
+ proc.stderr?.on('data', (chunk) => {
1279
+ output += chunk.toString();
1280
+ if (part.state.status === 'running') {
1159
1281
  part.state.metadata = {
1160
1282
  output: output,
1161
- description: "",
1162
- }
1163
- Session.updatePart(part)
1283
+ description: '',
1284
+ };
1285
+ Session.updatePart(part);
1164
1286
  }
1165
- })
1287
+ });
1166
1288
 
1167
1289
  await new Promise<void>((resolve) => {
1168
- proc.on("close", () => {
1169
- resolve()
1170
- })
1171
- })
1172
- msg.time.completed = Date.now()
1173
- await Session.updateMessage(msg)
1174
- if (part.state.status === "running") {
1290
+ proc.on('close', () => {
1291
+ resolve();
1292
+ });
1293
+ });
1294
+ msg.time.completed = Date.now();
1295
+ await Session.updateMessage(msg);
1296
+ if (part.state.status === 'running') {
1175
1297
  part.state = {
1176
- status: "completed",
1298
+ status: 'completed',
1177
1299
  time: {
1178
1300
  ...part.state.time,
1179
1301
  end: Date.now(),
1180
1302
  },
1181
1303
  input: part.state.input,
1182
- title: "",
1304
+ title: '',
1183
1305
  metadata: {
1184
1306
  output,
1185
- description: "",
1307
+ description: '',
1186
1308
  },
1187
1309
  output,
1188
- }
1189
- await Session.updatePart(part)
1310
+ };
1311
+ await Session.updatePart(part);
1190
1312
  }
1191
- return { info: msg, parts: [part] }
1313
+ return { info: msg, parts: [part] };
1192
1314
  }
1193
1315
 
1194
1316
  export const CommandInput = z.object({
1195
- messageID: Identifier.schema("message").optional(),
1196
- sessionID: Identifier.schema("session"),
1317
+ messageID: Identifier.schema('message').optional(),
1318
+ sessionID: Identifier.schema('session'),
1197
1319
  agent: z.string().optional(),
1198
1320
  model: z.string().optional(),
1199
1321
  arguments: z.string(),
1200
1322
  command: z.string(),
1201
- })
1202
- export type CommandInput = z.infer<typeof CommandInput>
1203
- const bashRegex = /!`([^`]+)`/g
1204
- const argsRegex = /(?:[^\s"']+|"[^"]*"|'[^']*')+/g
1205
- const placeholderRegex = /\$(\d+)/g
1206
- const quoteTrimRegex = /^["']|["']$/g
1323
+ });
1324
+ export type CommandInput = z.infer<typeof CommandInput>;
1325
+ const bashRegex = /!`([^`]+)`/g;
1326
+ const argsRegex = /(?:[^\s"']+|"[^"]*"|'[^']*')+/g;
1327
+ const placeholderRegex = /\$(\d+)/g;
1328
+ const quoteTrimRegex = /^["']|["']$/g;
1207
1329
  /**
1208
1330
  * Regular expression to match @ file references in text
1209
1331
  * Matches @ followed by file paths, excluding commas, periods at end of sentences, and backticks
@@ -1211,75 +1333,81 @@ export namespace SessionPrompt {
1211
1333
  */
1212
1334
 
1213
1335
  export async function command(input: CommandInput) {
1214
- log.info("command", input)
1215
- const command = await Command.get(input.command)
1216
- const agentName = command.agent ?? input.agent ?? "build"
1336
+ log.info('command', input);
1337
+ const command = await Command.get(input.command);
1338
+ const agentName = command.agent ?? input.agent ?? 'build';
1217
1339
 
1218
- const raw = input.arguments.match(argsRegex) ?? []
1219
- const args = raw.map((arg) => arg.replace(quoteTrimRegex, ""))
1340
+ const raw = input.arguments.match(argsRegex) ?? [];
1341
+ const args = raw.map((arg) => arg.replace(quoteTrimRegex, ''));
1220
1342
 
1221
- const placeholders = command.template.match(placeholderRegex) ?? []
1222
- let last = 0
1343
+ const placeholders = command.template.match(placeholderRegex) ?? [];
1344
+ let last = 0;
1223
1345
  for (const item of placeholders) {
1224
- const value = Number(item.slice(1))
1225
- if (value > last) last = value
1346
+ const value = Number(item.slice(1));
1347
+ if (value > last) last = value;
1226
1348
  }
1227
1349
 
1228
1350
  // Let the final placeholder swallow any extra arguments so prompts read naturally
1229
- const withArgs = command.template.replaceAll(placeholderRegex, (_, index) => {
1230
- const position = Number(index)
1231
- const argIndex = position - 1
1232
- if (argIndex >= args.length) return ""
1233
- if (position === last) return args.slice(argIndex).join(" ")
1234
- return args[argIndex]
1235
- })
1236
- let template = withArgs.replaceAll("$ARGUMENTS", input.arguments)
1351
+ const withArgs = command.template.replaceAll(
1352
+ placeholderRegex,
1353
+ (_, index) => {
1354
+ const position = Number(index);
1355
+ const argIndex = position - 1;
1356
+ if (argIndex >= args.length) return '';
1357
+ if (position === last) return args.slice(argIndex).join(' ');
1358
+ return args[argIndex];
1359
+ }
1360
+ );
1361
+ let template = withArgs.replaceAll('$ARGUMENTS', input.arguments);
1237
1362
 
1238
- const shell = ConfigMarkdown.shell(template)
1363
+ const shell = ConfigMarkdown.shell(template);
1239
1364
  if (shell.length > 0) {
1240
1365
  const results = await Promise.all(
1241
1366
  shell.map(async ([, cmd]) => {
1242
1367
  try {
1243
- return await $`${{ raw: cmd }}`.nothrow().text()
1368
+ return await $`${{ raw: cmd }}`.nothrow().text();
1244
1369
  } catch (error) {
1245
- return `Error executing command: ${error instanceof Error ? error.message : String(error)}`
1370
+ return `Error executing command: ${error instanceof Error ? error.message : String(error)}`;
1246
1371
  }
1247
- }),
1248
- )
1249
- let index = 0
1250
- template = template.replace(bashRegex, () => results[index++])
1372
+ })
1373
+ );
1374
+ let index = 0;
1375
+ template = template.replace(bashRegex, () => results[index++]);
1251
1376
  }
1252
- template = template.trim()
1377
+ template = template.trim();
1253
1378
 
1254
1379
  const model = await (async () => {
1255
1380
  if (command.model) {
1256
- return Provider.parseModel(command.model)
1381
+ return Provider.parseModel(command.model);
1257
1382
  }
1258
1383
  if (command.agent) {
1259
- const cmdAgent = await Agent.get(command.agent)
1384
+ const cmdAgent = await Agent.get(command.agent);
1260
1385
  if (cmdAgent.model) {
1261
- return cmdAgent.model
1386
+ return cmdAgent.model;
1262
1387
  }
1263
1388
  }
1264
1389
  if (input.model) {
1265
- return Provider.parseModel(input.model)
1390
+ return Provider.parseModel(input.model);
1266
1391
  }
1267
- return await Provider.defaultModel()
1268
- })()
1269
- const agent = await Agent.get(agentName)
1392
+ return await Provider.defaultModel();
1393
+ })();
1394
+ const agent = await Agent.get(agentName);
1270
1395
 
1271
1396
  const parts =
1272
- (agent.mode === "subagent" && command.subtask !== false) || command.subtask === true
1397
+ (agent.mode === 'subagent' && command.subtask !== false) ||
1398
+ command.subtask === true
1273
1399
  ? [
1274
1400
  {
1275
- type: "subtask" as const,
1401
+ type: 'subtask' as const,
1276
1402
  agent: agent.name,
1277
- description: command.description ?? "",
1403
+ description: command.description ?? '',
1278
1404
  // TODO: how can we make task tool accept a more complex input?
1279
- prompt: await resolvePromptParts(template).then((x) => x.find((y) => y.type === "text")?.text ?? ""),
1405
+ prompt: await resolvePromptParts(template).then(
1406
+ (x) => x.find((y) => y.type === 'text')?.text ?? ''
1407
+ ),
1280
1408
  },
1281
1409
  ]
1282
- : await resolvePromptParts(template)
1410
+ : await resolvePromptParts(template);
1283
1411
 
1284
1412
  const result = (await prompt({
1285
1413
  sessionID: input.sessionID,
@@ -1287,62 +1415,75 @@ export namespace SessionPrompt {
1287
1415
  model,
1288
1416
  agent: agentName,
1289
1417
  parts,
1290
- })) as MessageV2.WithParts
1418
+ })) as MessageV2.WithParts;
1291
1419
 
1292
1420
  Bus.publish(Command.Event.Executed, {
1293
1421
  name: input.command,
1294
1422
  sessionID: input.sessionID,
1295
1423
  arguments: input.arguments,
1296
1424
  messageID: result.info.id,
1297
- })
1425
+ });
1298
1426
 
1299
- return result
1427
+ return result;
1300
1428
  }
1301
1429
 
1302
1430
  // TODO: wire this back up
1303
1431
  async function ensureTitle(input: {
1304
- session: Session.Info
1305
- message: MessageV2.WithParts
1306
- history: MessageV2.WithParts[]
1307
- providerID: string
1308
- modelID: string
1432
+ session: Session.Info;
1433
+ message: MessageV2.WithParts;
1434
+ history: MessageV2.WithParts[];
1435
+ providerID: string;
1436
+ modelID: string;
1309
1437
  }) {
1310
- if (input.session.parentID) return
1311
- if (!Session.isDefaultTitle(input.session.title)) return
1438
+ if (input.session.parentID) return;
1439
+ if (!Session.isDefaultTitle(input.session.title)) return;
1312
1440
  const isFirst =
1313
- input.history.filter((m) => m.info.role === "user" && !m.parts.every((p) => "synthetic" in p && p.synthetic))
1314
- .length === 1
1315
- if (!isFirst) return
1441
+ input.history.filter(
1442
+ (m) =>
1443
+ m.info.role === 'user' &&
1444
+ !m.parts.every((p) => 'synthetic' in p && p.synthetic)
1445
+ ).length === 1;
1446
+ if (!isFirst) return;
1316
1447
  const small =
1317
- (await Provider.getSmallModel(input.providerID)) ?? (await Provider.getModel(input.providerID, input.modelID))
1448
+ (await Provider.getSmallModel(input.providerID)) ??
1449
+ (await Provider.getModel(input.providerID, input.modelID));
1318
1450
  const options = {
1319
- ...ProviderTransform.options(small.providerID, small.modelID, small.npm ?? "", input.session.id),
1451
+ ...ProviderTransform.options(
1452
+ small.providerID,
1453
+ small.modelID,
1454
+ small.npm ?? '',
1455
+ input.session.id
1456
+ ),
1320
1457
  ...small.info.options,
1321
- }
1322
- if (small.providerID === "openai" || small.modelID.includes("gpt-5")) {
1323
- if (small.modelID.includes("5.1")) {
1324
- options["reasoningEffort"] = "low"
1458
+ };
1459
+ if (small.providerID === 'openai' || small.modelID.includes('gpt-5')) {
1460
+ if (small.modelID.includes('5.1')) {
1461
+ options['reasoningEffort'] = 'low';
1325
1462
  } else {
1326
- options["reasoningEffort"] = "minimal"
1463
+ options['reasoningEffort'] = 'minimal';
1327
1464
  }
1328
1465
  }
1329
- if (small.providerID === "google") {
1330
- options["thinkingConfig"] = {
1466
+ if (small.providerID === 'google') {
1467
+ options['thinkingConfig'] = {
1331
1468
  thinkingBudget: 0,
1332
- }
1469
+ };
1333
1470
  }
1334
1471
  await generateText({
1335
1472
  maxOutputTokens: small.info.reasoning ? 1500 : 20,
1336
- providerOptions: ProviderTransform.providerOptions(small.npm, small.providerID, options),
1473
+ providerOptions: ProviderTransform.providerOptions(
1474
+ small.npm,
1475
+ small.providerID,
1476
+ options
1477
+ ),
1337
1478
  messages: [
1338
1479
  ...SystemPrompt.title(small.providerID).map(
1339
1480
  (x): ModelMessage => ({
1340
- role: "system",
1481
+ role: 'system',
1341
1482
  content: x,
1342
- }),
1483
+ })
1343
1484
  ),
1344
1485
  {
1345
- role: "user" as const,
1486
+ role: 'user' as const,
1346
1487
  content: `
1347
1488
  The following is the text to summarize:
1348
1489
  `,
@@ -1350,13 +1491,16 @@ export namespace SessionPrompt {
1350
1491
  ...MessageV2.toModelMessage([
1351
1492
  {
1352
1493
  info: {
1353
- id: Identifier.ascending("message"),
1354
- role: "user",
1494
+ id: Identifier.ascending('message'),
1495
+ role: 'user',
1355
1496
  sessionID: input.session.id,
1356
1497
  time: {
1357
1498
  created: Date.now(),
1358
1499
  },
1359
- agent: input.message.info.role === "user" ? input.message.info.agent : "build",
1500
+ agent:
1501
+ input.message.info.role === 'user'
1502
+ ? input.message.info.agent
1503
+ : 'build',
1360
1504
  model: {
1361
1505
  providerID: input.providerID,
1362
1506
  modelID: input.modelID,
@@ -1373,18 +1517,19 @@ export namespace SessionPrompt {
1373
1517
  if (result.text)
1374
1518
  return Session.update(input.session.id, (draft) => {
1375
1519
  const cleaned = result.text
1376
- .replace(/<think>[\s\S]*?<\/think>\s*/g, "")
1377
- .split("\n")
1520
+ .replace(/<think>[\s\S]*?<\/think>\s*/g, '')
1521
+ .split('\n')
1378
1522
  .map((line) => line.trim())
1379
- .find((line) => line.length > 0)
1380
- if (!cleaned) return
1523
+ .find((line) => line.length > 0);
1524
+ if (!cleaned) return;
1381
1525
 
1382
- const title = cleaned.length > 100 ? cleaned.substring(0, 97) + "..." : cleaned
1383
- draft.title = title
1384
- })
1526
+ const title =
1527
+ cleaned.length > 100 ? cleaned.substring(0, 97) + '...' : cleaned;
1528
+ draft.title = title;
1529
+ });
1385
1530
  })
1386
1531
  .catch((error) => {
1387
- log.error("failed to generate title", { error, model: small.info.id })
1388
- })
1532
+ log.error('failed to generate title', { error, model: small.info.id });
1533
+ });
1389
1534
  }
1390
1535
  }