@kooka/agent-sdk 0.1.0

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 (91) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +293 -0
  3. package/dist/abort.d.ts +2 -0
  4. package/dist/abort.d.ts.map +1 -0
  5. package/dist/abort.js +19 -0
  6. package/dist/abort.js.map +1 -0
  7. package/dist/agent/agent.d.ts +67 -0
  8. package/dist/agent/agent.d.ts.map +1 -0
  9. package/dist/agent/agent.js +1061 -0
  10. package/dist/agent/agent.js.map +1 -0
  11. package/dist/agent/constants.d.ts +6 -0
  12. package/dist/agent/constants.d.ts.map +1 -0
  13. package/dist/agent/constants.js +40 -0
  14. package/dist/agent/constants.js.map +1 -0
  15. package/dist/agent/prompts.d.ts +4 -0
  16. package/dist/agent/prompts.d.ts.map +1 -0
  17. package/dist/agent/prompts.js +31 -0
  18. package/dist/agent/prompts.js.map +1 -0
  19. package/dist/agent/reminders.d.ts +3 -0
  20. package/dist/agent/reminders.d.ts.map +1 -0
  21. package/dist/agent/reminders.js +31 -0
  22. package/dist/agent/reminders.js.map +1 -0
  23. package/dist/agent/retry.d.ts +12 -0
  24. package/dist/agent/retry.d.ts.map +1 -0
  25. package/dist/agent/retry.js +200 -0
  26. package/dist/agent/retry.js.map +1 -0
  27. package/dist/index.d.ts +49 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +50 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/llm/openaiCompatible.d.ts +23 -0
  32. package/dist/llm/openaiCompatible.d.ts.map +1 -0
  33. package/dist/llm/openaiCompatible.js +91 -0
  34. package/dist/llm/openaiCompatible.js.map +1 -0
  35. package/dist/plugins/pluginManager.d.ts +22 -0
  36. package/dist/plugins/pluginManager.d.ts.map +1 -0
  37. package/dist/plugins/pluginManager.js +171 -0
  38. package/dist/plugins/pluginManager.js.map +1 -0
  39. package/dist/plugins/types.d.ts +97 -0
  40. package/dist/plugins/types.d.ts.map +1 -0
  41. package/dist/plugins/types.js +2 -0
  42. package/dist/plugins/types.js.map +1 -0
  43. package/dist/skills.d.ts +28 -0
  44. package/dist/skills.d.ts.map +1 -0
  45. package/dist/skills.js +188 -0
  46. package/dist/skills.js.map +1 -0
  47. package/dist/tools/builtin/bash.d.ts +4 -0
  48. package/dist/tools/builtin/bash.d.ts.map +1 -0
  49. package/dist/tools/builtin/bash.js +364 -0
  50. package/dist/tools/builtin/bash.js.map +1 -0
  51. package/dist/tools/builtin/glob.d.ts +4 -0
  52. package/dist/tools/builtin/glob.d.ts.map +1 -0
  53. package/dist/tools/builtin/glob.js +87 -0
  54. package/dist/tools/builtin/glob.js.map +1 -0
  55. package/dist/tools/builtin/grep.d.ts +4 -0
  56. package/dist/tools/builtin/grep.d.ts.map +1 -0
  57. package/dist/tools/builtin/grep.js +190 -0
  58. package/dist/tools/builtin/grep.js.map +1 -0
  59. package/dist/tools/builtin/index.d.ts +15 -0
  60. package/dist/tools/builtin/index.d.ts.map +1 -0
  61. package/dist/tools/builtin/index.js +37 -0
  62. package/dist/tools/builtin/index.js.map +1 -0
  63. package/dist/tools/builtin/list.d.ts +4 -0
  64. package/dist/tools/builtin/list.d.ts.map +1 -0
  65. package/dist/tools/builtin/list.js +162 -0
  66. package/dist/tools/builtin/list.js.map +1 -0
  67. package/dist/tools/builtin/read.d.ts +4 -0
  68. package/dist/tools/builtin/read.d.ts.map +1 -0
  69. package/dist/tools/builtin/read.js +92 -0
  70. package/dist/tools/builtin/read.js.map +1 -0
  71. package/dist/tools/builtin/skill.d.ts +9 -0
  72. package/dist/tools/builtin/skill.d.ts.map +1 -0
  73. package/dist/tools/builtin/skill.js +126 -0
  74. package/dist/tools/builtin/skill.js.map +1 -0
  75. package/dist/tools/builtin/workspace.d.ts +13 -0
  76. package/dist/tools/builtin/workspace.d.ts.map +1 -0
  77. package/dist/tools/builtin/workspace.js +74 -0
  78. package/dist/tools/builtin/workspace.js.map +1 -0
  79. package/dist/tools/builtin/write.d.ts +4 -0
  80. package/dist/tools/builtin/write.d.ts.map +1 -0
  81. package/dist/tools/builtin/write.js +73 -0
  82. package/dist/tools/builtin/write.js.map +1 -0
  83. package/dist/tools/registry.d.ts +21 -0
  84. package/dist/tools/registry.d.ts.map +1 -0
  85. package/dist/tools/registry.js +150 -0
  86. package/dist/tools/registry.js.map +1 -0
  87. package/dist/types.d.ts +251 -0
  88. package/dist/types.d.ts.map +1 -0
  89. package/dist/types.js +2 -0
  90. package/dist/types.js.map +1 -0
  91. package/package.json +46 -0
@@ -0,0 +1,1061 @@
1
+ import * as path from 'path';
2
+ import { convertToModelMessages, extractReasoningMiddleware, jsonSchema, streamText, tool as aiTool, wrapLanguageModel, } from 'ai';
3
+ import { combineAbortSignals } from '../abort.js';
4
+ import { COMPACTION_AUTO_CONTINUE_TEXT, COMPACTION_MARKER_TEXT, COMPACTION_PROMPT_TEXT, COMPACTION_SYSTEM_PROMPT, createAssistantHistoryMessage, createHistoryForModel, createUserHistoryMessage, evaluatePermission, evaluateShellCommand, extractUsageTokens, finalizeStreamingParts, findExternalPathReferencesInShellCommand, getEffectiveHistory, getMessageText, getReservedOutputTokens, isOverflow as isContextOverflow, isPathInsideWorkspace, markPrunableToolOutputs, setDynamicToolError, setDynamicToolOutput, upsertDynamicToolCall, } from '@kooka/core';
5
+ import { PluginManager } from '../plugins/pluginManager.js';
6
+ import { insertModeReminders } from './reminders.js';
7
+ import { DEFAULT_SYSTEM_PROMPT } from './prompts.js';
8
+ import { EDIT_TOOL_IDS, MAX_TOOL_RESULT_LENGTH, THINK_BLOCK_REGEX, TOOL_BLOCK_REGEX } from './constants.js';
9
+ import { delay as getRetryDelayMs, retryable as getRetryableLlmError, sleep as retrySleep } from './retry.js';
10
+ class AsyncQueue {
11
+ state = {
12
+ values: [],
13
+ resolvers: [],
14
+ rejecters: [],
15
+ closed: false,
16
+ };
17
+ push(value) {
18
+ if (this.state.closed)
19
+ return;
20
+ const resolver = this.state.resolvers.shift();
21
+ const rejecter = this.state.rejecters.shift();
22
+ if (resolver && rejecter) {
23
+ resolver({ value, done: false });
24
+ return;
25
+ }
26
+ this.state.values.push(value);
27
+ }
28
+ close() {
29
+ if (this.state.closed)
30
+ return;
31
+ this.state.closed = true;
32
+ for (const resolve of this.state.resolvers) {
33
+ resolve({ value: undefined, done: true });
34
+ }
35
+ this.state.resolvers = [];
36
+ this.state.rejecters = [];
37
+ }
38
+ fail(error) {
39
+ if (this.state.closed)
40
+ return;
41
+ this.state.closed = true;
42
+ this.state.error = error;
43
+ for (const reject of this.state.rejecters) {
44
+ reject(error);
45
+ }
46
+ this.state.resolvers = [];
47
+ this.state.rejecters = [];
48
+ }
49
+ [Symbol.asyncIterator]() {
50
+ return {
51
+ next: () => {
52
+ if (this.state.error) {
53
+ return Promise.reject(this.state.error);
54
+ }
55
+ const value = this.state.values.shift();
56
+ if (value !== undefined) {
57
+ return Promise.resolve({ value, done: false });
58
+ }
59
+ if (this.state.closed) {
60
+ return Promise.resolve({ value: undefined, done: true });
61
+ }
62
+ return new Promise((resolve, reject) => {
63
+ this.state.resolvers.push(resolve);
64
+ this.state.rejecters.push(reject);
65
+ });
66
+ },
67
+ return: () => {
68
+ this.close();
69
+ return Promise.resolve({ value: undefined, done: true });
70
+ },
71
+ throw: (error) => {
72
+ this.fail(error);
73
+ return Promise.reject(error);
74
+ },
75
+ };
76
+ }
77
+ }
78
+ function stripThinkBlocks(content) {
79
+ return content.replace(THINK_BLOCK_REGEX, '');
80
+ }
81
+ function stripToolBlocks(content) {
82
+ return content.replace(TOOL_BLOCK_REGEX, '');
83
+ }
84
+ function isRecord(value) {
85
+ return !!value && typeof value === 'object' && !Array.isArray(value);
86
+ }
87
+ function toToolCall(toolCallId, toolName, input) {
88
+ let args = '{}';
89
+ try {
90
+ args = JSON.stringify(input ?? {});
91
+ }
92
+ catch {
93
+ args = '{}';
94
+ }
95
+ return {
96
+ id: toolCallId,
97
+ type: 'function',
98
+ function: {
99
+ name: toolName,
100
+ arguments: args,
101
+ },
102
+ };
103
+ }
104
+ export class LingyunSession {
105
+ history = [];
106
+ pendingPlan;
107
+ sessionId;
108
+ fileHandles;
109
+ constructor(init) {
110
+ if (init?.history)
111
+ this.history = [...init.history];
112
+ if (init?.pendingPlan)
113
+ this.pendingPlan = init.pendingPlan;
114
+ if (init?.sessionId)
115
+ this.sessionId = init.sessionId;
116
+ if (init?.fileHandles)
117
+ this.fileHandles = init.fileHandles;
118
+ }
119
+ getHistory() {
120
+ return [...this.history];
121
+ }
122
+ }
123
+ export class LingyunAgent {
124
+ llm;
125
+ config;
126
+ registry;
127
+ plugins;
128
+ workspaceRoot;
129
+ allowExternalPaths;
130
+ modelLimits;
131
+ compactionConfig;
132
+ registeredPluginTools = new Set();
133
+ constructor(llm, config, registry, runtime) {
134
+ this.llm = llm;
135
+ this.config = config;
136
+ this.registry = registry;
137
+ this.plugins = runtime?.plugins ?? new PluginManager({ workspaceRoot: runtime?.workspaceRoot });
138
+ this.workspaceRoot = runtime?.workspaceRoot ? path.resolve(runtime.workspaceRoot) : undefined;
139
+ this.allowExternalPaths = !!runtime?.allowExternalPaths;
140
+ this.modelLimits = runtime?.modelLimits;
141
+ const baseCompaction = {
142
+ auto: true,
143
+ prune: true,
144
+ pruneProtectTokens: 40_000,
145
+ pruneMinimumTokens: 20_000,
146
+ };
147
+ const c = runtime?.compaction ?? {};
148
+ this.compactionConfig = {
149
+ auto: c.auto ?? baseCompaction.auto,
150
+ prune: c.prune ?? baseCompaction.prune,
151
+ pruneProtectTokens: Math.max(0, c.pruneProtectTokens ?? baseCompaction.pruneProtectTokens),
152
+ pruneMinimumTokens: Math.max(0, c.pruneMinimumTokens ?? baseCompaction.pruneMinimumTokens),
153
+ };
154
+ }
155
+ updateConfig(config) {
156
+ this.config = { ...this.config, ...config };
157
+ }
158
+ setAllowExternalPaths(allow) {
159
+ this.allowExternalPaths = !!allow;
160
+ }
161
+ getMode() {
162
+ return this.config.mode === 'plan' ? 'plan' : 'build';
163
+ }
164
+ getModelLimit(modelId) {
165
+ return this.modelLimits?.[modelId];
166
+ }
167
+ getPermissionRuleset(mode) {
168
+ if (mode === 'plan') {
169
+ return [
170
+ { permission: '*', pattern: '*', action: 'ask' },
171
+ { permission: 'read', pattern: '*', action: 'allow' },
172
+ { permission: 'list', pattern: '*', action: 'allow' },
173
+ { permission: 'glob', pattern: '*', action: 'allow' },
174
+ { permission: 'grep', pattern: '*', action: 'allow' },
175
+ { permission: 'edit', pattern: '*', action: 'deny' },
176
+ ];
177
+ }
178
+ return [{ permission: '*', pattern: '*', action: 'allow' }];
179
+ }
180
+ getPermissionName(def) {
181
+ const explicit = def.metadata?.permission;
182
+ if (explicit && explicit.trim())
183
+ return explicit.trim();
184
+ if (EDIT_TOOL_IDS.has(def.id))
185
+ return 'edit';
186
+ return def.id;
187
+ }
188
+ normalizePermissionPath(input) {
189
+ const workspaceRoot = this.workspaceRoot;
190
+ if (!workspaceRoot)
191
+ return input;
192
+ try {
193
+ const abs = path.isAbsolute(input) ? path.resolve(input) : path.resolve(workspaceRoot, input);
194
+ const rel = path.relative(workspaceRoot, abs);
195
+ if (!rel || rel === '.')
196
+ return '.';
197
+ if (rel.startsWith('..') || path.isAbsolute(rel)) {
198
+ return abs;
199
+ }
200
+ return rel.replace(/\\/g, '/');
201
+ }
202
+ catch {
203
+ return input;
204
+ }
205
+ }
206
+ getPermissionPatterns(def, args) {
207
+ const patternsMeta = def.metadata?.permissionPatterns;
208
+ if (!patternsMeta || patternsMeta.length === 0) {
209
+ return ['*'];
210
+ }
211
+ const patterns = [];
212
+ for (const item of patternsMeta) {
213
+ if (!item || typeof item.arg !== 'string' || !item.arg)
214
+ continue;
215
+ const raw = args?.[item.arg];
216
+ if (typeof raw !== 'string')
217
+ continue;
218
+ const value = raw.trim();
219
+ if (!value)
220
+ continue;
221
+ if (item.kind === 'path') {
222
+ patterns.push(this.normalizePermissionPath(value));
223
+ }
224
+ else {
225
+ patterns.push(value);
226
+ }
227
+ }
228
+ return patterns.length > 0 ? patterns : ['*'];
229
+ }
230
+ getExternalPathPatterns(def, args) {
231
+ if (!def.metadata?.supportsExternalPaths)
232
+ return [];
233
+ const patternsMeta = def.metadata?.permissionPatterns;
234
+ if (!patternsMeta || patternsMeta.length === 0)
235
+ return [];
236
+ const workspaceRoot = this.workspaceRoot;
237
+ if (!workspaceRoot)
238
+ return [];
239
+ const out = new Set();
240
+ for (const item of patternsMeta) {
241
+ if (!item || typeof item.arg !== 'string' || !item.arg)
242
+ continue;
243
+ if (item.kind !== 'path')
244
+ continue;
245
+ const raw = args?.[item.arg];
246
+ if (typeof raw !== 'string')
247
+ continue;
248
+ const value = raw.trim();
249
+ if (!value)
250
+ continue;
251
+ const normalized = this.normalizePermissionPath(value);
252
+ if (path.isAbsolute(normalized)) {
253
+ out.add(normalized);
254
+ }
255
+ }
256
+ return [...out];
257
+ }
258
+ combinePermissionActions(current, next) {
259
+ if (current === 'deny' || next === 'deny')
260
+ return 'deny';
261
+ if (current === 'ask' || next === 'ask')
262
+ return 'ask';
263
+ return 'allow';
264
+ }
265
+ getMaxOutputTokens() {
266
+ const max = this.config.maxOutputTokens;
267
+ if (typeof max === 'number' && Number.isFinite(max) && max > 0)
268
+ return Math.floor(max);
269
+ return 4096;
270
+ }
271
+ async ensurePluginToolsRegistered() {
272
+ const entries = await this.plugins.getPluginTools();
273
+ if (entries.length === 0)
274
+ return;
275
+ const existing = await this.registry.getTools();
276
+ const existingById = new Map(existing.map((t) => [t.id, t]));
277
+ for (const entry of entries) {
278
+ const key = `${entry.pluginId}:${entry.toolId}`;
279
+ if (this.registeredPluginTools.has(key))
280
+ continue;
281
+ const conflict = existingById.get(entry.toolId);
282
+ if (conflict) {
283
+ throw new Error(`Plugin tool id collision: "${entry.toolId}" from plugin "${entry.pluginId}" conflicts with existing tool "${conflict.id}" (${conflict.name}). ` +
284
+ `Choose a unique tool id for the plugin tool.`);
285
+ }
286
+ this.registeredPluginTools.add(key);
287
+ existingById.set(entry.toolId, {
288
+ id: entry.toolId,
289
+ name: entry.tool.name || entry.toolId,
290
+ description: entry.tool.description,
291
+ parameters: entry.tool.parameters,
292
+ execution: { type: 'function', handler: `plugin:${key}` },
293
+ metadata: entry.tool.metadata,
294
+ });
295
+ this.registry.registerTool({
296
+ id: entry.toolId,
297
+ name: entry.tool.name || entry.toolId,
298
+ description: entry.tool.description,
299
+ parameters: entry.tool.parameters,
300
+ execution: { type: 'function', handler: `plugin:${key}` },
301
+ metadata: entry.tool.metadata,
302
+ }, async (args, context) => {
303
+ const out = await entry.tool.execute(args, context);
304
+ if (out && typeof out === 'object' && typeof out.success === 'boolean') {
305
+ return out;
306
+ }
307
+ return { success: true, data: out };
308
+ });
309
+ }
310
+ }
311
+ async toModelMessages(session, tools, modelId) {
312
+ const effective = getEffectiveHistory(session.history);
313
+ const prepared = createHistoryForModel(effective);
314
+ const reminded = insertModeReminders(prepared, this.getMode());
315
+ const withoutIds = reminded.map(({ id: _id, ...rest }) => rest);
316
+ const messagesOutput = await this.plugins.trigger('experimental.chat.messages.transform', { sessionId: session.sessionId ?? this.config.sessionId, mode: this.getMode(), modelId }, { messages: [...withoutIds] });
317
+ const messages = Array.isArray(messagesOutput.messages) ? messagesOutput.messages : withoutIds;
318
+ return convertToModelMessages(messages, { tools: tools });
319
+ }
320
+ createToolContext(signal, session, callbacks) {
321
+ return {
322
+ workspaceRoot: this.workspaceRoot,
323
+ allowExternalPaths: this.allowExternalPaths,
324
+ sessionId: session.sessionId ?? this.config.sessionId,
325
+ signal,
326
+ log: (message) => callbacks?.onDebug?.(message),
327
+ };
328
+ }
329
+ async formatToolResult(result, toolName) {
330
+ let content;
331
+ const outputOverride = isRecord(result.metadata) ? result.metadata.outputText : undefined;
332
+ if (typeof outputOverride === 'string' && outputOverride) {
333
+ content = outputOverride;
334
+ if (content.length > MAX_TOOL_RESULT_LENGTH) {
335
+ content = content.substring(0, MAX_TOOL_RESULT_LENGTH) + '\n\n... [TRUNCATED]';
336
+ }
337
+ return content;
338
+ }
339
+ if (result.success) {
340
+ if (typeof result.data === 'string') {
341
+ content = result.data;
342
+ }
343
+ else if (result.data === undefined || result.data === null) {
344
+ content = 'Done';
345
+ }
346
+ else {
347
+ content = JSON.stringify(result.data, null, 2);
348
+ }
349
+ }
350
+ else {
351
+ content = JSON.stringify({ error: result.error });
352
+ }
353
+ if (content.length > MAX_TOOL_RESULT_LENGTH) {
354
+ content = content.substring(0, MAX_TOOL_RESULT_LENGTH) + '\n\n... [TRUNCATED]';
355
+ }
356
+ return content;
357
+ }
358
+ async pruneToolResultForHistory(output, toolLabel) {
359
+ const result = output && typeof output === 'object' && typeof output.success === 'boolean'
360
+ ? output
361
+ : { success: true, data: output };
362
+ if (!result.success) {
363
+ const rawError = typeof result.error === 'string' ? result.error : String(result.error ?? 'Unknown error');
364
+ const errorText = rawError.length > MAX_TOOL_RESULT_LENGTH
365
+ ? rawError.substring(0, MAX_TOOL_RESULT_LENGTH) + '\n\n... [TRUNCATED]'
366
+ : rawError;
367
+ return {
368
+ ...result,
369
+ error: errorText,
370
+ metadata: { ...(result.metadata || {}), truncated: rawError.length > MAX_TOOL_RESULT_LENGTH || result.metadata?.truncated },
371
+ };
372
+ }
373
+ const formatted = await this.formatToolResult(result, toolLabel);
374
+ return {
375
+ ...result,
376
+ data: formatted,
377
+ metadata: { ...(result.metadata || {}), truncated: formatted.includes('[TRUNCATED]') || result.metadata?.truncated },
378
+ };
379
+ }
380
+ normalizeFileHandlePath(raw) {
381
+ const value = raw.trim();
382
+ if (!value)
383
+ return '';
384
+ const workspaceRoot = this.workspaceRoot;
385
+ if (!workspaceRoot)
386
+ return value;
387
+ try {
388
+ const abs = path.isAbsolute(value) ? path.resolve(value) : path.resolve(workspaceRoot, value);
389
+ const rel = path.relative(workspaceRoot, abs);
390
+ if (rel && rel !== '.' && !rel.startsWith('..') && !path.isAbsolute(rel)) {
391
+ return rel.replace(/\\/g, '/');
392
+ }
393
+ return abs;
394
+ }
395
+ catch {
396
+ return value;
397
+ }
398
+ }
399
+ ensureFileHandles(session) {
400
+ if (!session.fileHandles) {
401
+ session.fileHandles = { nextId: 1, byId: {} };
402
+ return session.fileHandles;
403
+ }
404
+ const nextId = session.fileHandles.nextId;
405
+ const byId = session.fileHandles.byId;
406
+ if (typeof nextId !== 'number' || !Number.isFinite(nextId) || nextId < 1 || !byId || typeof byId !== 'object') {
407
+ session.fileHandles = { nextId: 1, byId: {} };
408
+ return session.fileHandles;
409
+ }
410
+ return session.fileHandles;
411
+ }
412
+ resolveFileHandle(session, fileId) {
413
+ const id = fileId.trim();
414
+ if (!id)
415
+ return undefined;
416
+ const handles = this.ensureFileHandles(session);
417
+ const resolved = handles.byId[id];
418
+ return typeof resolved === 'string' && resolved.trim() ? resolved.trim() : undefined;
419
+ }
420
+ getOrCreateFileHandle(session, filePath) {
421
+ const normalizedPath = this.normalizeFileHandlePath(filePath);
422
+ if (!normalizedPath) {
423
+ return { id: 'F0', filePath: filePath.trim() };
424
+ }
425
+ const handles = this.ensureFileHandles(session);
426
+ for (const [existingId, existingPath] of Object.entries(handles.byId)) {
427
+ if (existingPath === normalizedPath) {
428
+ return { id: existingId, filePath: normalizedPath };
429
+ }
430
+ }
431
+ const id = `F${handles.nextId++}`;
432
+ handles.byId[id] = normalizedPath;
433
+ return { id, filePath: normalizedPath };
434
+ }
435
+ decorateGlobResultWithFileHandles(session, result) {
436
+ if (!result.success)
437
+ return result;
438
+ const data = result.data;
439
+ if (!isRecord(data))
440
+ return result;
441
+ const filesRaw = data.files;
442
+ if (!Array.isArray(filesRaw))
443
+ return result;
444
+ const files = filesRaw
445
+ .filter((value) => typeof value === 'string')
446
+ .map((value) => value.trim())
447
+ .filter(Boolean);
448
+ const notesRaw = data.notes;
449
+ const notes = Array.isArray(notesRaw)
450
+ ? notesRaw.filter((value) => typeof value === 'string').map((value) => value.trim()).filter(Boolean)
451
+ : [];
452
+ const truncated = Boolean(data.truncated);
453
+ const lines = [];
454
+ if (notes.length > 0) {
455
+ lines.push(`Note: ${notes.join(' ')}`, '');
456
+ }
457
+ if (files.length === 0) {
458
+ lines.push('No files found');
459
+ }
460
+ else {
461
+ lines.push('Use fileId with read/write:', '');
462
+ for (const filePath of files) {
463
+ const handle = this.getOrCreateFileHandle(session, filePath);
464
+ lines.push(`${handle.id} ${handle.filePath}`);
465
+ }
466
+ if (truncated) {
467
+ lines.push('', '(Results are truncated. Consider using a more specific path or pattern.)');
468
+ }
469
+ }
470
+ return {
471
+ ...result,
472
+ metadata: {
473
+ ...(result.metadata || {}),
474
+ outputText: lines.join('\n').trimEnd(),
475
+ },
476
+ };
477
+ }
478
+ createAISDKTools(tools, mode, session, callbacks, toolNameToDefinition) {
479
+ const out = {};
480
+ for (const def of tools) {
481
+ const toolName = def.id;
482
+ toolNameToDefinition.set(toolName, def);
483
+ out[toolName] = aiTool({
484
+ id: toolName,
485
+ description: def.description,
486
+ inputSchema: jsonSchema(def.parameters),
487
+ execute: async (args, options) => {
488
+ const callId = String(options.toolCallId);
489
+ let resolvedArgs = args ?? {};
490
+ const sessionId = session.sessionId ?? this.config.sessionId;
491
+ // tool.execute.before
492
+ {
493
+ const before = await this.plugins.trigger('tool.execute.before', { tool: toolName, sessionId, callId }, { args: resolvedArgs });
494
+ if (before && typeof before.args === 'object' && before.args !== null) {
495
+ resolvedArgs = before.args;
496
+ }
497
+ }
498
+ if (isRecord(resolvedArgs) &&
499
+ (def.id === 'read' || def.id === 'write') &&
500
+ typeof resolvedArgs.fileId === 'string') {
501
+ const filePathRaw = typeof resolvedArgs.filePath === 'string' ? String(resolvedArgs.filePath) : '';
502
+ if (!filePathRaw.trim()) {
503
+ const fileId = String(resolvedArgs.fileId);
504
+ const resolvedPath = this.resolveFileHandle(session, fileId);
505
+ if (!resolvedPath) {
506
+ return {
507
+ success: false,
508
+ error: `Unknown fileId: ${fileId}. Run glob first and use one of the returned fileId values.`,
509
+ metadata: { errorType: 'unknown_file_id', fileId },
510
+ };
511
+ }
512
+ resolvedArgs = { ...resolvedArgs, filePath: resolvedPath };
513
+ }
514
+ }
515
+ const tc = toToolCall(callId, toolName, resolvedArgs);
516
+ const permission = this.getPermissionName(def);
517
+ const patterns = this.getPermissionPatterns(def, resolvedArgs);
518
+ const ruleset = this.getPermissionRuleset(mode);
519
+ let permissionAction = 'allow';
520
+ for (const pattern of patterns) {
521
+ const rule = evaluatePermission(permission, pattern, ruleset);
522
+ const action = rule?.action ?? 'ask';
523
+ permissionAction = this.combinePermissionActions(permissionAction, action);
524
+ }
525
+ if (permissionAction === 'deny') {
526
+ const reason = mode === 'plan' ? 'Tool is disabled in Plan mode. Switch to Build mode to use it.' : 'Tool is denied by permissions.';
527
+ callbacks?.onToolBlocked?.(tc, def, reason);
528
+ return { success: false, error: reason };
529
+ }
530
+ let requiresApproval = permissionAction === 'ask' || !!def.metadata?.requiresApproval;
531
+ const isShellExecutionTool = def.id === 'bash' || def.id === 'shell.run' || def.id === 'shell.terminal' || def.execution?.type === 'shell';
532
+ const workspaceRoot = this.workspaceRoot;
533
+ if (isShellExecutionTool && !this.allowExternalPaths && workspaceRoot) {
534
+ const cwdRaw = typeof resolvedArgs?.workdir === 'string'
535
+ ? resolvedArgs.workdir
536
+ : def.execution?.type === 'shell' && typeof def.execution.cwd === 'string'
537
+ ? String(def.execution.cwd)
538
+ : '';
539
+ const cwd = cwdRaw && cwdRaw.trim()
540
+ ? path.isAbsolute(cwdRaw.trim())
541
+ ? path.resolve(cwdRaw.trim())
542
+ : path.resolve(workspaceRoot, cwdRaw.trim())
543
+ : workspaceRoot;
544
+ const commandText = typeof resolvedArgs?.command === 'string'
545
+ ? resolvedArgs.command
546
+ : def.execution?.type === 'shell' && typeof def.execution.script === 'string'
547
+ ? String(def.execution.script)
548
+ : undefined;
549
+ const externalRefs = new Set();
550
+ if (!isPathInsideWorkspace(cwd, workspaceRoot)) {
551
+ externalRefs.add(cwd);
552
+ }
553
+ if (typeof commandText === 'string' && commandText.trim()) {
554
+ for (const p of findExternalPathReferencesInShellCommand(commandText, { cwd, workspaceRoot })) {
555
+ externalRefs.add(p);
556
+ }
557
+ }
558
+ if (externalRefs.size > 0) {
559
+ const reason = 'External paths are disabled. This shell command references paths outside the current workspace. Enable allowExternalPaths to allow external path access.';
560
+ callbacks?.onToolBlocked?.(tc, def, reason);
561
+ const blockedPaths = [...externalRefs];
562
+ const blockedPathsMax = 20;
563
+ return {
564
+ success: false,
565
+ error: reason,
566
+ metadata: {
567
+ errorType: 'external_paths_disabled',
568
+ blockedSettingKey: 'allowExternalPaths',
569
+ isOutsideWorkspace: true,
570
+ blockedPaths: blockedPaths.slice(0, blockedPathsMax),
571
+ blockedPathsTruncated: blockedPaths.length > blockedPathsMax,
572
+ },
573
+ };
574
+ }
575
+ }
576
+ const commandForSafety = typeof resolvedArgs?.command === 'string'
577
+ ? resolvedArgs.command
578
+ : def.execution?.type === 'shell' && typeof def.execution.script === 'string'
579
+ ? String(def.execution.script)
580
+ : undefined;
581
+ if (isShellExecutionTool && typeof commandForSafety === 'string') {
582
+ const safety = evaluateShellCommand(commandForSafety);
583
+ if (safety.verdict === 'deny') {
584
+ const reason = `Blocked command: ${safety.reason}`;
585
+ callbacks?.onToolBlocked?.(tc, def, reason);
586
+ return { success: false, error: reason };
587
+ }
588
+ if (safety.verdict === 'needs_approval') {
589
+ requiresApproval = true;
590
+ }
591
+ }
592
+ const externalPaths = this.getExternalPathPatterns(def, resolvedArgs);
593
+ if (externalPaths.length > 0 && !this.allowExternalPaths) {
594
+ const reason = 'External paths are disabled. Enable allowExternalPaths to allow access outside the current workspace.';
595
+ callbacks?.onToolBlocked?.(tc, def, reason);
596
+ return {
597
+ success: false,
598
+ error: reason,
599
+ metadata: {
600
+ errorType: 'external_paths_disabled',
601
+ blockedSettingKey: 'allowExternalPaths',
602
+ isOutsideWorkspace: true,
603
+ },
604
+ };
605
+ }
606
+ // permission.ask plugin hook
607
+ {
608
+ const permissionDecision = await this.plugins.trigger('permission.ask', {
609
+ tool: toolName,
610
+ sessionId,
611
+ callId,
612
+ patterns,
613
+ metadata: {
614
+ mode,
615
+ requiresApproval,
616
+ permission,
617
+ },
618
+ }, { status: requiresApproval ? 'ask' : 'allow' });
619
+ if (permissionDecision?.status === 'deny') {
620
+ const reason = 'Tool is denied by a plugin permission hook.';
621
+ callbacks?.onToolBlocked?.(tc, def, reason);
622
+ return { success: false, error: reason };
623
+ }
624
+ if (permissionDecision?.status === 'allow') {
625
+ requiresApproval = false;
626
+ }
627
+ if (permissionDecision?.status === 'ask') {
628
+ requiresApproval = true;
629
+ }
630
+ }
631
+ const allowAutoApprove = mode !== 'plan' && !!this.config.autoApprove;
632
+ if (requiresApproval && !allowAutoApprove) {
633
+ const approved = (await callbacks?.onRequestApproval?.(tc, def)) ?? false;
634
+ if (!approved) {
635
+ return { success: false, error: 'User rejected this action' };
636
+ }
637
+ }
638
+ const ctx = this.createToolContext(options.abortSignal, session, callbacks);
639
+ let result = await this.registry.executeTool(def.id, resolvedArgs ?? {}, ctx);
640
+ if (def.id === 'glob') {
641
+ result = this.decorateGlobResultWithFileHandles(session, result);
642
+ }
643
+ // tool.execute.after
644
+ {
645
+ const baseText = await this.formatToolResult(result, def.name);
646
+ const output = await this.plugins.trigger('tool.execute.after', { tool: toolName, sessionId, callId }, {
647
+ title: def.name,
648
+ output: baseText,
649
+ metadata: isRecord(result.metadata) ? { ...result.metadata } : {},
650
+ });
651
+ const mergedMeta = {
652
+ ...(isRecord(result.metadata) ? result.metadata : {}),
653
+ ...(isRecord(output.metadata) ? output.metadata : {}),
654
+ };
655
+ if (typeof output.title === 'string' && output.title.trim()) {
656
+ mergedMeta.title = output.title;
657
+ }
658
+ if (typeof output.output === 'string') {
659
+ mergedMeta.outputText = output.output;
660
+ }
661
+ result = { ...result, metadata: mergedMeta };
662
+ }
663
+ return result;
664
+ },
665
+ toModelOutput: async (options) => {
666
+ const output = options.output;
667
+ const content = await this.formatToolResult(output, def.name);
668
+ return { type: 'text', value: content };
669
+ },
670
+ });
671
+ }
672
+ return out;
673
+ }
674
+ filterTools(tools) {
675
+ const filter = this.config.toolFilter;
676
+ if (!filter || filter.length === 0) {
677
+ return tools;
678
+ }
679
+ return tools.filter((tool) => {
680
+ return filter.some((pattern) => {
681
+ if (pattern.includes('*')) {
682
+ const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$');
683
+ return regex.test(tool.id);
684
+ }
685
+ return tool.id === pattern || tool.id.startsWith(pattern + '.');
686
+ });
687
+ });
688
+ }
689
+ composeSystemPrompt(modelId) {
690
+ const basePrompt = this.config.systemPrompt || DEFAULT_SYSTEM_PROMPT;
691
+ return this.plugins
692
+ .trigger('experimental.chat.system.transform', { sessionId: this.config.sessionId, mode: this.getMode(), modelId }, { system: [basePrompt] })
693
+ .then((out) => (Array.isArray(out.system) ? out.system.filter(Boolean) : [basePrompt]));
694
+ }
695
+ async compactSessionInternal(session, params, callbacks) {
696
+ const maybeAwait = async (value) => {
697
+ if (value && typeof value.then === 'function') {
698
+ await value;
699
+ }
700
+ };
701
+ const markerMessage = createUserHistoryMessage(COMPACTION_MARKER_TEXT, { synthetic: true, compaction: { auto: params.auto } });
702
+ session.history.push(markerMessage);
703
+ const markerIndex = session.history.length - 1;
704
+ try {
705
+ await maybeAwait(callbacks?.onCompactionStart?.({ auto: params.auto, markerMessageId: markerMessage.id }));
706
+ }
707
+ catch {
708
+ // ignore
709
+ }
710
+ const abortController = new AbortController();
711
+ const signal = abortController.signal;
712
+ try {
713
+ const compacting = await this.plugins.trigger('experimental.session.compacting', { sessionId: session.sessionId ?? this.config.sessionId }, { context: [], prompt: undefined });
714
+ const extraContext = Array.isArray(compacting.context) ? compacting.context.filter(Boolean) : [];
715
+ const promptText = typeof compacting.prompt === 'string' && compacting.prompt.trim()
716
+ ? compacting.prompt
717
+ : [COMPACTION_PROMPT_TEXT, ...extraContext].join('\n\n');
718
+ const rawModel = await this.llm.getModel(params.modelId);
719
+ const compactionModel = wrapLanguageModel({
720
+ model: rawModel,
721
+ middleware: [extractReasoningMiddleware({ tagName: 'think', startWithReasoning: false })],
722
+ });
723
+ const effective = getEffectiveHistory(session.history);
724
+ const prepared = createHistoryForModel(effective);
725
+ const withoutIds = prepared.map(({ id: _id, ...rest }) => rest);
726
+ const compactionUser = createUserHistoryMessage(promptText, { synthetic: true });
727
+ const compactionModelMessages = await convertToModelMessages([...withoutIds, compactionUser], { tools: {} });
728
+ const stream = streamText({
729
+ model: compactionModel,
730
+ system: COMPACTION_SYSTEM_PROMPT,
731
+ messages: compactionModelMessages,
732
+ maxRetries: 0,
733
+ temperature: 0.0,
734
+ maxOutputTokens: this.getMaxOutputTokens(),
735
+ abortSignal: signal,
736
+ });
737
+ const summaryTextRaw = await stream.text;
738
+ const summaryUsage = await stream.usage;
739
+ const finishReason = await stream.finishReason;
740
+ const summaryText = stripThinkBlocks(String(summaryTextRaw || '')).trim();
741
+ const summaryMessage = createAssistantHistoryMessage();
742
+ const summaryTokens = extractUsageTokens(summaryUsage);
743
+ summaryMessage.metadata = {
744
+ mode: this.getMode(),
745
+ finishReason,
746
+ summary: true,
747
+ ...(summaryTokens ? { tokens: summaryTokens } : {}),
748
+ };
749
+ if (summaryText) {
750
+ summaryMessage.parts.push({ type: 'text', text: summaryText, state: 'done' });
751
+ }
752
+ session.history.push(summaryMessage);
753
+ if (params.auto) {
754
+ session.history.push(createUserHistoryMessage(COMPACTION_AUTO_CONTINUE_TEXT, { synthetic: true }));
755
+ }
756
+ session.history = getEffectiveHistory(session.history);
757
+ try {
758
+ await maybeAwait(callbacks?.onCompactionEnd?.({
759
+ auto: params.auto,
760
+ markerMessageId: markerMessage.id,
761
+ summaryMessageId: summaryMessage.id,
762
+ status: 'done',
763
+ }));
764
+ }
765
+ catch {
766
+ // ignore
767
+ }
768
+ }
769
+ catch (error) {
770
+ const message = error instanceof Error ? error.message : String(error);
771
+ const status = /aborterror/i.test(message) || /aborted/i.test(message) ? 'canceled' : 'error';
772
+ if (session.history[markerIndex]?.id === markerMessage.id) {
773
+ session.history.splice(markerIndex, 1);
774
+ }
775
+ try {
776
+ await maybeAwait(callbacks?.onCompactionEnd?.({ auto: params.auto, markerMessageId: markerMessage.id, status, error: message }));
777
+ }
778
+ catch {
779
+ // ignore
780
+ }
781
+ throw error;
782
+ }
783
+ }
784
+ async runOnce(session, callbacks, signal) {
785
+ const modelId = (this.config.model || '').trim();
786
+ if (!modelId) {
787
+ throw new Error('No model configured');
788
+ }
789
+ await this.ensurePluginToolsRegistered();
790
+ const mode = this.getMode();
791
+ const sessionId = session.sessionId ?? this.config.sessionId;
792
+ const rawModel = await this.llm.getModel(modelId);
793
+ const model = wrapLanguageModel({
794
+ model: rawModel,
795
+ middleware: [extractReasoningMiddleware({ tagName: 'think', startWithReasoning: false })],
796
+ });
797
+ const systemParts = await this.composeSystemPrompt(modelId);
798
+ const tools = this.filterTools(await this.registry.getTools());
799
+ const toolNameToDefinition = new Map();
800
+ const callbacksSafe = callbacks;
801
+ const callParams = await this.plugins.trigger('chat.params', {
802
+ sessionId,
803
+ mode,
804
+ modelId,
805
+ message: (() => {
806
+ const lastUserMessage = [...session.history].reverse().find((msg) => msg.role === 'user');
807
+ return lastUserMessage ? getMessageText(lastUserMessage) : undefined;
808
+ })(),
809
+ }, {
810
+ temperature: this.config.temperature ?? 0.0,
811
+ topP: undefined,
812
+ topK: undefined,
813
+ options: undefined,
814
+ });
815
+ let lastResponse = '';
816
+ const maxIterations = 50;
817
+ for (let iteration = 1; iteration <= maxIterations; iteration++) {
818
+ await Promise.resolve(callbacksSafe?.onIterationStart?.(iteration));
819
+ const abortController = new AbortController();
820
+ const combined = signal ? combineAbortSignals([signal, abortController.signal]) : abortController.signal;
821
+ const aiTools = this.createAISDKTools(tools, mode, session, callbacksSafe, toolNameToDefinition);
822
+ const modelMessages = await this.toModelMessages(session, aiTools, modelId);
823
+ const promptMessages = [
824
+ ...systemParts.map((text) => ({ role: 'system', content: text })),
825
+ ...modelMessages,
826
+ ];
827
+ let assistantMessage = createAssistantHistoryMessage();
828
+ let attemptText = '';
829
+ let attemptReasoning = '';
830
+ let streamFinishReason;
831
+ let streamUsage;
832
+ const maxRetries = Math.max(0, Math.floor(this.config.maxRetries ?? 0));
833
+ let retryAttempt = 0;
834
+ while (true) {
835
+ assistantMessage = createAssistantHistoryMessage();
836
+ attemptText = '';
837
+ attemptReasoning = '';
838
+ streamFinishReason = undefined;
839
+ streamUsage = undefined;
840
+ let sawToolCall = false;
841
+ try {
842
+ const stream = streamText({
843
+ model: model,
844
+ messages: promptMessages,
845
+ tools: aiTools,
846
+ maxRetries: 0,
847
+ temperature: callParams.temperature,
848
+ topP: callParams.topP,
849
+ topK: callParams.topK,
850
+ ...(callParams.options ? { providerOptions: callParams.options } : {}),
851
+ maxOutputTokens: this.getMaxOutputTokens(),
852
+ abortSignal: combined,
853
+ });
854
+ for await (const part of stream.fullStream) {
855
+ switch (part.type) {
856
+ case 'text-delta': {
857
+ callbacksSafe?.onToken?.(part.text);
858
+ attemptText += part.text;
859
+ callbacksSafe?.onAssistantToken?.(part.text);
860
+ break;
861
+ }
862
+ case 'reasoning-delta': {
863
+ attemptReasoning += part.text;
864
+ callbacksSafe?.onThoughtToken?.(part.text);
865
+ break;
866
+ }
867
+ case 'tool-call': {
868
+ sawToolCall = true;
869
+ const toolName = String(part.toolName);
870
+ const toolCallId = String(part.toolCallId);
871
+ const def = toolNameToDefinition.get(toolName);
872
+ if (def) {
873
+ callbacksSafe?.onStatusChange?.({ type: 'running', message: '' });
874
+ callbacksSafe?.onToolCall?.(toToolCall(toolCallId, toolName, part.input), def);
875
+ }
876
+ upsertDynamicToolCall(assistantMessage, {
877
+ toolName,
878
+ toolCallId,
879
+ input: part.input,
880
+ });
881
+ break;
882
+ }
883
+ case 'tool-result': {
884
+ const toolName = String(part.toolName);
885
+ const toolCallId = String(part.toolCallId);
886
+ const def = toolNameToDefinition.get(toolName);
887
+ const toolLabel = def?.name || toolName;
888
+ const output = await this.pruneToolResultForHistory(part.output, toolLabel);
889
+ setDynamicToolOutput(assistantMessage, {
890
+ toolName,
891
+ toolCallId,
892
+ input: part.input,
893
+ output,
894
+ });
895
+ const tc = toToolCall(toolCallId, toolName, part.input);
896
+ callbacksSafe?.onToolResult?.(tc, output);
897
+ callbacksSafe?.onStatusChange?.({ type: 'running', message: '' });
898
+ break;
899
+ }
900
+ case 'tool-error': {
901
+ const toolName = String(part.toolName);
902
+ const toolCallId = String(part.toolCallId);
903
+ const errorText = part.error instanceof Error ? part.error.message : String(part.error);
904
+ setDynamicToolError(assistantMessage, {
905
+ toolName,
906
+ toolCallId,
907
+ input: part.input,
908
+ errorText,
909
+ });
910
+ const tc = toToolCall(toolCallId, toolName, part.input);
911
+ callbacksSafe?.onToolResult?.(tc, { success: false, error: errorText });
912
+ break;
913
+ }
914
+ case 'error':
915
+ throw part.error;
916
+ default:
917
+ break;
918
+ }
919
+ }
920
+ streamFinishReason = await stream.finishReason;
921
+ streamUsage = await stream.usage;
922
+ break;
923
+ }
924
+ catch (e) {
925
+ const retryable = getRetryableLlmError(e);
926
+ const canRetry = !!retryable && retryAttempt < maxRetries && !sawToolCall && !attemptText.trim() && !combined.aborted;
927
+ if (canRetry) {
928
+ retryAttempt += 1;
929
+ const waitMs = getRetryDelayMs(retryAttempt, retryable.retryAfterMs);
930
+ callbacksSafe?.onStatusChange?.({
931
+ type: 'retry',
932
+ attempt: retryAttempt,
933
+ nextRetryTime: Date.now() + waitMs,
934
+ message: retryable.message,
935
+ });
936
+ await retrySleep(waitMs, combined).catch(() => { });
937
+ continue;
938
+ }
939
+ if (retryable) {
940
+ const wrapped = new Error(retryable.message);
941
+ wrapped.cause = e;
942
+ throw wrapped;
943
+ }
944
+ throw e;
945
+ }
946
+ }
947
+ const tokens = extractUsageTokens(streamUsage);
948
+ assistantMessage.metadata = {
949
+ mode: this.getMode(),
950
+ finishReason: streamFinishReason,
951
+ ...(tokens ? { tokens } : {}),
952
+ };
953
+ const cleanedText = stripToolBlocks(stripThinkBlocks(attemptText)).trim();
954
+ assistantMessage.parts = assistantMessage.parts.filter((p) => p.type !== 'text' && p.type !== 'reasoning');
955
+ let finalText = cleanedText;
956
+ if (finalText) {
957
+ const textOutput = await this.plugins.trigger('experimental.text.complete', { sessionId, messageId: assistantMessage.id }, { text: finalText });
958
+ finalText = typeof textOutput.text === 'string' ? textOutput.text : finalText;
959
+ }
960
+ if (finalText) {
961
+ assistantMessage.parts.unshift({ type: 'text', text: finalText, state: 'streaming' });
962
+ }
963
+ finalizeStreamingParts(assistantMessage);
964
+ session.history.push(assistantMessage);
965
+ const lastAssistantText = getMessageText(assistantMessage).trim();
966
+ lastResponse = lastAssistantText || lastResponse;
967
+ markPrunableToolOutputs(session.history, this.compactionConfig);
968
+ await Promise.resolve(callbacksSafe?.onIterationEnd?.(iteration));
969
+ const modelLimit = this.getModelLimit(modelId);
970
+ const reservedOutputTokens = getReservedOutputTokens({ modelLimit, maxOutputTokens: this.getMaxOutputTokens() });
971
+ if (streamFinishReason === 'tool-calls' &&
972
+ isContextOverflow({ lastTokens: assistantMessage.metadata?.tokens, modelLimit, reservedOutputTokens, config: this.compactionConfig })) {
973
+ await this.compactSessionInternal(session, { auto: true, modelId }, callbacksSafe);
974
+ continue;
975
+ }
976
+ const hasToolParts = assistantMessage.parts.some((part) => part.type === 'dynamic-tool');
977
+ if (streamFinishReason === 'tool-calls' || hasToolParts)
978
+ continue;
979
+ await this.plugins.trigger('experimental.chat.complete', {
980
+ sessionId,
981
+ mode,
982
+ modelId,
983
+ messageId: assistantMessage.id,
984
+ assistantText: lastAssistantText,
985
+ returnedText: lastResponse,
986
+ }, {});
987
+ callbacksSafe?.onComplete?.(lastResponse);
988
+ return lastResponse;
989
+ }
990
+ callbacksSafe?.onComplete?.(lastResponse);
991
+ return lastResponse;
992
+ }
993
+ run(params) {
994
+ const queue = new AsyncQueue();
995
+ const callbacks = params.callbacks;
996
+ const proxy = {
997
+ ...callbacks,
998
+ onDebug: (message) => {
999
+ callbacks?.onDebug?.(message);
1000
+ queue.push({ type: 'debug', message });
1001
+ },
1002
+ onStatusChange: (status) => {
1003
+ callbacks?.onStatusChange?.(status);
1004
+ queue.push({ type: 'status', status: status });
1005
+ },
1006
+ onAssistantToken: (token) => {
1007
+ callbacks?.onAssistantToken?.(token);
1008
+ queue.push({ type: 'assistant_token', token });
1009
+ },
1010
+ onThoughtToken: (token) => {
1011
+ callbacks?.onThoughtToken?.(token);
1012
+ queue.push({ type: 'thought_token', token });
1013
+ },
1014
+ onToolCall: (tool, definition) => {
1015
+ callbacks?.onToolCall?.(tool, definition);
1016
+ queue.push({ type: 'tool_call', tool, definition });
1017
+ },
1018
+ onToolBlocked: (tool, definition, reason) => {
1019
+ callbacks?.onToolBlocked?.(tool, definition, reason);
1020
+ queue.push({ type: 'tool_blocked', tool, definition, reason });
1021
+ },
1022
+ onToolResult: (tool, result) => {
1023
+ callbacks?.onToolResult?.(tool, result);
1024
+ queue.push({ type: 'tool_result', tool, result });
1025
+ },
1026
+ onCompactionStart: (event) => {
1027
+ callbacks?.onCompactionStart?.(event);
1028
+ queue.push({ type: 'compaction_start', auto: event.auto, markerMessageId: event.markerMessageId });
1029
+ },
1030
+ onCompactionEnd: (event) => {
1031
+ callbacks?.onCompactionEnd?.(event);
1032
+ queue.push({
1033
+ type: 'compaction_end',
1034
+ auto: event.auto,
1035
+ markerMessageId: event.markerMessageId,
1036
+ summaryMessageId: event.summaryMessageId,
1037
+ status: event.status,
1038
+ error: event.error,
1039
+ });
1040
+ },
1041
+ };
1042
+ const done = (async () => {
1043
+ try {
1044
+ params.session.history.push(createUserHistoryMessage(params.input));
1045
+ const text = await this.runOnce(params.session, proxy, params.signal);
1046
+ queue.push({ type: 'status', status: { type: 'done', message: '' } });
1047
+ queue.close();
1048
+ return { text, session: params.session };
1049
+ }
1050
+ catch (error) {
1051
+ const err = error instanceof Error ? error : new Error(String(error));
1052
+ callbacks?.onError?.(err);
1053
+ queue.push({ type: 'status', status: { type: 'error', message: err.message } });
1054
+ queue.fail(err);
1055
+ throw err;
1056
+ }
1057
+ })();
1058
+ return { events: queue, done };
1059
+ }
1060
+ }
1061
+ //# sourceMappingURL=agent.js.map