@archships/dim-agent-sdk 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +115 -4
- package/dist/{agent-core → dim-agent-sdk/src/agent-core}/Agent.d.ts +7 -1
- package/dist/{agent-core → dim-agent-sdk/src/agent-core}/LoopRunner.d.ts +6 -0
- package/dist/{agent-core → dim-agent-sdk/src/agent-core}/Session.d.ts +26 -1
- package/dist/{agent-core → dim-agent-sdk/src/agent-core}/ToolExecutor.d.ts +11 -1
- package/dist/{agent-core → dim-agent-sdk/src/agent-core}/agent-types.d.ts +5 -2
- package/dist/dim-agent-sdk/src/agent-core/approvals.d.ts +25 -0
- package/dist/dim-agent-sdk/src/agent-core/compaction.d.ts +22 -0
- package/dist/{agent-core → dim-agent-sdk/src/agent-core}/errors.d.ts +1 -0
- package/dist/{agent-core → dim-agent-sdk/src/agent-core}/index.d.ts +7 -0
- package/dist/dim-agent-sdk/src/agent-core/notifications.d.ts +25 -0
- package/dist/dim-agent-sdk/src/agent-core/plugin-state.d.ts +24 -0
- package/dist/{agent-core → dim-agent-sdk/src/agent-core}/session-state.d.ts +4 -1
- package/dist/dim-agent-sdk/src/agent-core/session-status.d.ts +4 -0
- package/dist/dim-agent-sdk/src/agent-core/subagent.d.ts +14 -0
- package/dist/dim-agent-sdk/src/context/compaction.d.ts +1 -0
- package/dist/dim-agent-sdk/src/context/index.d.ts +4 -0
- package/dist/dim-agent-sdk/src/contracts/compaction.d.ts +52 -0
- package/dist/{contracts → dim-agent-sdk/src/contracts}/event.d.ts +8 -1
- package/dist/{contracts → dim-agent-sdk/src/contracts}/index.d.ts +2 -0
- package/dist/dim-agent-sdk/src/contracts/plugin-state.d.ts +11 -0
- package/dist/dim-agent-sdk/src/contracts/state.d.ts +29 -0
- package/dist/{contracts → dim-agent-sdk/src/contracts}/tool.d.ts +6 -0
- package/dist/{index.d.ts → dim-agent-sdk/src/index.d.ts} +2 -1
- package/dist/{persistence → dim-agent-sdk/src/persistence}/SnapshotCodec.d.ts +3 -1
- package/dist/dim-agent-sdk/src/plugin-host/HookPipeline.d.ts +30 -0
- package/dist/dim-agent-sdk/src/plugin-host/PluginHost.d.ts +58 -0
- package/dist/dim-agent-sdk/src/plugin-host/helpers.d.ts +6 -0
- package/dist/dim-agent-sdk/src/plugin-host/index.d.ts +4 -0
- package/dist/dim-agent-sdk/src/plugin-host/types.d.ts +1 -0
- package/dist/{services → dim-agent-sdk/src/services}/PermissionGateway.d.ts +3 -0
- package/dist/{services → dim-agent-sdk/src/services}/types.d.ts +3 -1
- package/dist/dim-plugin-api/src/index.d.ts +602 -0
- package/dist/index.js +1194 -166
- package/package.json +12 -12
- package/dist/context/index.d.ts +0 -3
- package/dist/contracts/state.d.ts +0 -14
- package/dist/plugin-host/HookPipeline.d.ts +0 -7
- package/dist/plugin-host/PluginHost.d.ts +0 -36
- package/dist/plugin-host/helpers.d.ts +0 -3
- package/dist/plugin-host/index.d.ts +0 -4
- package/dist/plugin-host/types.d.ts +0 -1
- /package/dist/{agent-core → dim-agent-sdk/src/agent-core}/MessageFactory.d.ts +0 -0
- /package/dist/{agent-core → dim-agent-sdk/src/agent-core}/ModelTurnCollector.d.ts +0 -0
- /package/dist/{agent-core → dim-agent-sdk/src/agent-core}/TerminationPolicy.d.ts +0 -0
- /package/dist/{agent-core → dim-agent-sdk/src/agent-core}/createModel.d.ts +0 -0
- /package/dist/{agent-core → dim-agent-sdk/src/agent-core}/tool-call.d.ts +0 -0
- /package/dist/{context → dim-agent-sdk/src/context}/AutoContextManager.d.ts +0 -0
- /package/dist/{context → dim-agent-sdk/src/context}/types.d.ts +0 -0
- /package/dist/{contracts → dim-agent-sdk/src/contracts}/common.d.ts +0 -0
- /package/dist/{contracts → dim-agent-sdk/src/contracts}/content-normalize.d.ts +0 -0
- /package/dist/{contracts → dim-agent-sdk/src/contracts}/content.d.ts +0 -0
- /package/dist/{contracts → dim-agent-sdk/src/contracts}/message.d.ts +0 -0
- /package/dist/{contracts → dim-agent-sdk/src/contracts}/model.d.ts +0 -0
- /package/dist/{contracts → dim-agent-sdk/src/contracts}/tool-normalize.d.ts +0 -0
- /package/dist/{persistence → dim-agent-sdk/src/persistence}/FileStateStore.d.ts +0 -0
- /package/dist/{persistence → dim-agent-sdk/src/persistence}/InMemoryStateStore.d.ts +0 -0
- /package/dist/{persistence → dim-agent-sdk/src/persistence}/index.d.ts +0 -0
- /package/dist/{persistence → dim-agent-sdk/src/persistence}/store.d.ts +0 -0
- /package/dist/{providers → dim-agent-sdk/src/providers}/anthropic/adapter.d.ts +0 -0
- /package/dist/{providers → dim-agent-sdk/src/providers}/anthropic/mapper.d.ts +0 -0
- /package/dist/{providers → dim-agent-sdk/src/providers}/gemini/adapter.d.ts +0 -0
- /package/dist/{providers → dim-agent-sdk/src/providers}/gemini/mapper.d.ts +0 -0
- /package/dist/{providers → dim-agent-sdk/src/providers}/index.d.ts +0 -0
- /package/dist/{providers → dim-agent-sdk/src/providers}/openai/adapter.d.ts +0 -0
- /package/dist/{providers → dim-agent-sdk/src/providers}/openai/mapper.d.ts +0 -0
- /package/dist/{providers → dim-agent-sdk/src/providers}/openai-responses/adapter.d.ts +0 -0
- /package/dist/{providers → dim-agent-sdk/src/providers}/openai-responses/mapper.d.ts +0 -0
- /package/dist/{providers → dim-agent-sdk/src/providers}/shared/http-error.d.ts +0 -0
- /package/dist/{providers → dim-agent-sdk/src/providers}/shared/provider-state.d.ts +0 -0
- /package/dist/{providers → dim-agent-sdk/src/providers}/shared/reasoning.d.ts +0 -0
- /package/dist/{providers → dim-agent-sdk/src/providers}/shared/tool-call.d.ts +0 -0
- /package/dist/{providers → dim-agent-sdk/src/providers}/shared/usage.d.ts +0 -0
- /package/dist/{services → dim-agent-sdk/src/services}/ExecGateway.d.ts +0 -0
- /package/dist/{services → dim-agent-sdk/src/services}/FileSystemGateway.d.ts +0 -0
- /package/dist/{services → dim-agent-sdk/src/services}/GitGateway.d.ts +0 -0
- /package/dist/{services → dim-agent-sdk/src/services}/ModelGateway.d.ts +0 -0
- /package/dist/{services → dim-agent-sdk/src/services}/NetworkGateway.d.ts +0 -0
- /package/dist/{services → dim-agent-sdk/src/services}/activity.d.ts +0 -0
- /package/dist/{services → dim-agent-sdk/src/services}/index.d.ts +0 -0
- /package/dist/{services → dim-agent-sdk/src/services}/permissions.d.ts +0 -0
- /package/dist/{tools → dim-agent-sdk/src/tools}/BaseTool.d.ts +0 -0
- /package/dist/{tools → dim-agent-sdk/src/tools}/ToolRegistry.d.ts +0 -0
- /package/dist/{tools → dim-agent-sdk/src/tools}/builtins/EditTool.d.ts +0 -0
- /package/dist/{tools → dim-agent-sdk/src/tools}/builtins/ExecTool.d.ts +0 -0
- /package/dist/{tools → dim-agent-sdk/src/tools}/builtins/ReadTool.d.ts +0 -0
- /package/dist/{tools → dim-agent-sdk/src/tools}/builtins/WriteTool.d.ts +0 -0
- /package/dist/{tools → dim-agent-sdk/src/tools}/builtins/index.d.ts +0 -0
- /package/dist/{tools → dim-agent-sdk/src/tools}/builtins/utils.d.ts +0 -0
- /package/dist/{tools → dim-agent-sdk/src/tools}/index.d.ts +0 -0
- /package/dist/{tools → dim-agent-sdk/src/tools}/result.d.ts +0 -0
- /package/dist/{utils → dim-agent-sdk/src/utils}/guards.d.ts +0 -0
- /package/dist/{utils → dim-agent-sdk/src/utils}/id.d.ts +0 -0
- /package/dist/{utils → dim-agent-sdk/src/utils}/json.d.ts +0 -0
- /package/dist/{utils → dim-agent-sdk/src/utils}/usage.d.ts +0 -0
package/dist/index.js
CHANGED
|
@@ -208,27 +208,60 @@ function isHookControlResult(value) {
|
|
|
208
208
|
const candidate = value;
|
|
209
209
|
return (candidate.type === "continue" || candidate.type === "replace" || candidate.type === "stop") && "payload" in candidate;
|
|
210
210
|
}
|
|
211
|
+
function isToolBeforeExecuteControlResult(value) {
|
|
212
|
+
if (!value || typeof value !== "object")
|
|
213
|
+
return false;
|
|
214
|
+
const candidate = value;
|
|
215
|
+
return candidate.type === "allow" || candidate.type === "deny" || candidate.type === "ask" || candidate.type === "replaceToolCall" || candidate.type === "provideResult";
|
|
216
|
+
}
|
|
217
|
+
function isNotificationControlResult(value) {
|
|
218
|
+
if (!value || typeof value !== "object")
|
|
219
|
+
return false;
|
|
220
|
+
const candidate = value;
|
|
221
|
+
return candidate.type === "continue" || candidate.type === "replace" || candidate.type === "suppress";
|
|
222
|
+
}
|
|
223
|
+
function isRunStopControlResult(value) {
|
|
224
|
+
if (!value || typeof value !== "object")
|
|
225
|
+
return false;
|
|
226
|
+
const candidate = value;
|
|
227
|
+
return candidate.type === "continue" || candidate.type === "finalize";
|
|
228
|
+
}
|
|
211
229
|
|
|
212
230
|
// src/plugin-host/HookPipeline.ts
|
|
213
231
|
class HookPipeline {
|
|
214
232
|
registrations = [];
|
|
215
|
-
|
|
233
|
+
nextOrder = 0;
|
|
234
|
+
register(pluginId, pluginPriority, hooks, services) {
|
|
216
235
|
if (!hooks)
|
|
217
236
|
return;
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
237
|
+
for (const entry of hooks) {
|
|
238
|
+
if (!entry.middleware?.length && !entry.observers?.length)
|
|
239
|
+
continue;
|
|
240
|
+
if (entry.descriptor.name === "model.event" && entry.middleware && entry.middleware.length > 0)
|
|
241
|
+
throw new Error(`Plugin ${pluginId} cannot register middleware for model.event`);
|
|
242
|
+
if (entry.descriptor.mode === "async" && entry.middleware && entry.middleware.length > 0)
|
|
243
|
+
throw new Error(`Plugin ${pluginId} cannot register async middleware for ${entry.descriptor.name}`);
|
|
244
|
+
this.registrations.push({
|
|
245
|
+
pluginId,
|
|
246
|
+
order: this.nextOrder++,
|
|
247
|
+
priority: entry.descriptor.priority ?? pluginPriority,
|
|
248
|
+
descriptor: entry.descriptor,
|
|
249
|
+
services,
|
|
250
|
+
middleware: [...entry.middleware ?? []],
|
|
251
|
+
observers: [...entry.observers ?? []]
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
this.registrations.sort((left, right) => {
|
|
255
|
+
if (left.priority !== right.priority)
|
|
256
|
+
return right.priority - left.priority;
|
|
257
|
+
return left.order - right.order;
|
|
258
|
+
});
|
|
222
259
|
}
|
|
223
260
|
async runMiddleware(name, payload, context) {
|
|
224
261
|
let current = payload;
|
|
225
|
-
for (const registration of this.
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
const result = await middleware({
|
|
229
|
-
payload: current,
|
|
230
|
-
context: { ...context, metadata: { ...context.metadata, pluginId: registration.pluginId } }
|
|
231
|
-
});
|
|
262
|
+
for (const registration of this.matchingRegistrations(name, current)) {
|
|
263
|
+
for (const middleware of registration.middleware) {
|
|
264
|
+
const result = await this.runMiddlewareHandler(registration, middleware, current, context);
|
|
232
265
|
if (result === undefined)
|
|
233
266
|
continue;
|
|
234
267
|
if (isHookControlResult(result)) {
|
|
@@ -237,22 +270,191 @@ class HookPipeline {
|
|
|
237
270
|
return current;
|
|
238
271
|
continue;
|
|
239
272
|
}
|
|
273
|
+
if (isNotificationControlResult(result) || isRunStopControlResult(result) || isToolBeforeExecuteControlResult(result))
|
|
274
|
+
throw new Error(`Hook ${registration.descriptor.name} returned an unsupported control result`);
|
|
240
275
|
current = result;
|
|
241
276
|
}
|
|
242
277
|
}
|
|
243
278
|
return current;
|
|
244
279
|
}
|
|
280
|
+
async runToolBeforeExecute(payload, context) {
|
|
281
|
+
let current = payload;
|
|
282
|
+
for (const registration of this.matchingRegistrations("tool.beforeExecute", current)) {
|
|
283
|
+
for (const middleware of registration.middleware) {
|
|
284
|
+
const result = await this.runMiddlewareHandler(registration, middleware, current, context);
|
|
285
|
+
if (result === undefined)
|
|
286
|
+
continue;
|
|
287
|
+
if (isHookControlResult(result)) {
|
|
288
|
+
current = result.payload;
|
|
289
|
+
if (result.type === "stop")
|
|
290
|
+
return { payload: current };
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
if (isToolBeforeExecuteControlResult(result)) {
|
|
294
|
+
if (result.type === "replaceToolCall") {
|
|
295
|
+
current = { ...current, toolCall: result.toolCall };
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
if (result.type === "ask" && result.toolCall)
|
|
299
|
+
current = { ...current, toolCall: result.toolCall };
|
|
300
|
+
return { payload: current, control: result };
|
|
301
|
+
}
|
|
302
|
+
current = result;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return { payload: current };
|
|
306
|
+
}
|
|
307
|
+
async runNotifyMessage(payload, context) {
|
|
308
|
+
let current = payload;
|
|
309
|
+
for (const registration of this.matchingRegistrations("notify.message", current)) {
|
|
310
|
+
for (const middleware of registration.middleware) {
|
|
311
|
+
const result = await this.runMiddlewareHandler(registration, middleware, current, context);
|
|
312
|
+
if (result === undefined)
|
|
313
|
+
continue;
|
|
314
|
+
if (isHookControlResult(result)) {
|
|
315
|
+
current = result.payload;
|
|
316
|
+
if (result.type === "stop")
|
|
317
|
+
return { payload: current };
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
if (isNotificationControlResult(result)) {
|
|
321
|
+
if (result.type === "replace") {
|
|
322
|
+
current = { ...current, notification: result.notification };
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
if (result.type === "suppress")
|
|
326
|
+
return { payload: current, suppressed: true };
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
current = result;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return { payload: current };
|
|
333
|
+
}
|
|
334
|
+
async runRunStop(payload, context) {
|
|
335
|
+
let current = payload;
|
|
336
|
+
for (const registration of this.matchingRegistrations("run.stop", current)) {
|
|
337
|
+
for (const middleware of registration.middleware) {
|
|
338
|
+
const result = await this.runMiddlewareHandler(registration, middleware, current, context);
|
|
339
|
+
if (result === undefined)
|
|
340
|
+
continue;
|
|
341
|
+
if (isHookControlResult(result)) {
|
|
342
|
+
current = result.payload;
|
|
343
|
+
if (result.type === "stop")
|
|
344
|
+
return { payload: current };
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
if (isRunStopControlResult(result))
|
|
348
|
+
return { payload: current, control: result };
|
|
349
|
+
current = result;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return { payload: current };
|
|
353
|
+
}
|
|
245
354
|
async runObservers(name, payload, context) {
|
|
246
|
-
for (const registration of this.
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
}
|
|
355
|
+
for (const registration of this.matchingRegistrations(name, payload)) {
|
|
356
|
+
for (const observer of registration.observers) {
|
|
357
|
+
const task = this.runObserverHandler(registration, observer, payload, context);
|
|
358
|
+
if (registration.descriptor.mode === "async") {
|
|
359
|
+
task.catch(() => {});
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
await task.catch(() => {});
|
|
253
363
|
}
|
|
254
364
|
}
|
|
255
365
|
}
|
|
366
|
+
matchingRegistrations(name, payload) {
|
|
367
|
+
return this.registrations.filter((registration) => {
|
|
368
|
+
return registration.descriptor.name === name && matchesHookDescriptor(registration.descriptor, payload);
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
async runMiddlewareHandler(registration, middleware, payload, context) {
|
|
372
|
+
const task = Promise.resolve(middleware({
|
|
373
|
+
payload,
|
|
374
|
+
context: this.withHandlerMetadata(context, registration)
|
|
375
|
+
}));
|
|
376
|
+
return await withTimeout(task, registration.descriptor.timeoutMs, () => {
|
|
377
|
+
return new Error(`Hook ${registration.descriptor.name} for plugin ${registration.pluginId} timed out after ${registration.descriptor.timeoutMs}ms`);
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
async runObserverHandler(registration, observer, payload, context) {
|
|
381
|
+
const task = Promise.resolve(observer({
|
|
382
|
+
payload,
|
|
383
|
+
context: this.withHandlerMetadata(context, registration)
|
|
384
|
+
}));
|
|
385
|
+
await withTimeout(task, registration.descriptor.timeoutMs, () => {
|
|
386
|
+
return new Error(`Hook ${registration.descriptor.name} for plugin ${registration.pluginId} timed out after ${registration.descriptor.timeoutMs}ms`);
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
withHandlerMetadata(context, registration) {
|
|
390
|
+
return {
|
|
391
|
+
...context,
|
|
392
|
+
services: registration.services ?? context.services,
|
|
393
|
+
status: context.status ? structuredClone(context.status) : undefined,
|
|
394
|
+
metadata: {
|
|
395
|
+
...context.metadata,
|
|
396
|
+
pluginId: registration.pluginId,
|
|
397
|
+
hookName: registration.descriptor.name
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
function matchesHookDescriptor(descriptor, payload) {
|
|
403
|
+
const matcher = descriptor.when;
|
|
404
|
+
if (!matcher)
|
|
405
|
+
return true;
|
|
406
|
+
if (matcher.toolName && !matchesValue(matcher.toolName, readToolName(payload)))
|
|
407
|
+
return false;
|
|
408
|
+
if (matcher.notificationType && !matchesValue(matcher.notificationType, readNotificationType(payload)))
|
|
409
|
+
return false;
|
|
410
|
+
if (matcher.stopReason && !matchesValue(matcher.stopReason, readStopReason(payload)))
|
|
411
|
+
return false;
|
|
412
|
+
if (matcher.source && !matchesValue(matcher.source, readSource(payload)))
|
|
413
|
+
return false;
|
|
414
|
+
return true;
|
|
415
|
+
}
|
|
416
|
+
function readToolName(payload) {
|
|
417
|
+
if ("toolCall" in payload)
|
|
418
|
+
return payload.toolCall.function.name;
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
function readNotificationType(payload) {
|
|
422
|
+
if ("notification" in payload)
|
|
423
|
+
return payload.notification.type;
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
function readStopReason(payload) {
|
|
427
|
+
if ("stopReason" in payload)
|
|
428
|
+
return payload.stopReason;
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
function readSource(payload) {
|
|
432
|
+
if ("notification" in payload)
|
|
433
|
+
return payload.notification.source;
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
function matchesValue(expected, actual) {
|
|
437
|
+
if (!actual)
|
|
438
|
+
return false;
|
|
439
|
+
if (Array.isArray(expected))
|
|
440
|
+
return expected.includes(actual);
|
|
441
|
+
return expected === actual;
|
|
442
|
+
}
|
|
443
|
+
async function withTimeout(promise, timeoutMs, createError) {
|
|
444
|
+
if (!timeoutMs || timeoutMs <= 0)
|
|
445
|
+
return await promise;
|
|
446
|
+
let timeoutHandle;
|
|
447
|
+
try {
|
|
448
|
+
return await Promise.race([
|
|
449
|
+
promise,
|
|
450
|
+
new Promise((_, reject) => {
|
|
451
|
+
timeoutHandle = setTimeout(() => reject(createError()), timeoutMs);
|
|
452
|
+
})
|
|
453
|
+
]);
|
|
454
|
+
} finally {
|
|
455
|
+
if (timeoutHandle)
|
|
456
|
+
clearTimeout(timeoutHandle);
|
|
457
|
+
}
|
|
256
458
|
}
|
|
257
459
|
|
|
258
460
|
// src/plugin-host/PluginHost.ts
|
|
@@ -261,6 +463,7 @@ class PluginHost {
|
|
|
261
463
|
contextContributors = [];
|
|
262
464
|
promptContributors = [];
|
|
263
465
|
tools = [];
|
|
466
|
+
sessionControllerFactories = [];
|
|
264
467
|
services;
|
|
265
468
|
cwd;
|
|
266
469
|
agentId;
|
|
@@ -278,7 +481,7 @@ class PluginHost {
|
|
|
278
481
|
manifest: plugin.manifest,
|
|
279
482
|
services: scopedServices
|
|
280
483
|
});
|
|
281
|
-
this.pipeline.register(plugin.manifest.id, priority, setup?.hooks);
|
|
484
|
+
this.pipeline.register(plugin.manifest.id, priority, setup?.hooks, scopedServices);
|
|
282
485
|
for (const tool of setup?.tools ?? []) {
|
|
283
486
|
this.tools.push({
|
|
284
487
|
pluginId: plugin.manifest.id,
|
|
@@ -289,6 +492,13 @@ class PluginHost {
|
|
|
289
492
|
this.contextContributors.push({ pluginId: plugin.manifest.id, contributor });
|
|
290
493
|
for (const contributor of setup?.promptContributors ?? [])
|
|
291
494
|
this.promptContributors.push({ pluginId: plugin.manifest.id, contributor });
|
|
495
|
+
if (setup?.createSessionController) {
|
|
496
|
+
this.sessionControllerFactories.push({
|
|
497
|
+
pluginId: plugin.manifest.id,
|
|
498
|
+
factory: setup.createSessionController,
|
|
499
|
+
services: scopedServices
|
|
500
|
+
});
|
|
501
|
+
}
|
|
292
502
|
}
|
|
293
503
|
}
|
|
294
504
|
pluginTools() {
|
|
@@ -297,9 +507,32 @@ class PluginHost {
|
|
|
297
507
|
recentFiles(limit = 5) {
|
|
298
508
|
return this.tracker?.recentFiles(limit) ?? [];
|
|
299
509
|
}
|
|
510
|
+
createSessionControllers(input) {
|
|
511
|
+
const controllers = new Map;
|
|
512
|
+
for (const entry of this.sessionControllerFactories) {
|
|
513
|
+
controllers.set(entry.pluginId, entry.factory({
|
|
514
|
+
sessionId: input.sessionId,
|
|
515
|
+
metadata: input.metadata ? structuredClone(input.metadata) : undefined,
|
|
516
|
+
getStatus: input.getStatus,
|
|
517
|
+
pluginState: entry.services.pluginState,
|
|
518
|
+
save: input.save,
|
|
519
|
+
isRunning: input.isRunning
|
|
520
|
+
}));
|
|
521
|
+
}
|
|
522
|
+
return controllers;
|
|
523
|
+
}
|
|
300
524
|
async runMiddleware(name, payload, context = {}) {
|
|
301
525
|
return this.pipeline.runMiddleware(name, payload, this.createRuntimeContext(context));
|
|
302
526
|
}
|
|
527
|
+
async runToolBeforeExecute(payload, context = {}) {
|
|
528
|
+
return this.pipeline.runToolBeforeExecute(payload, this.createRuntimeContext(context));
|
|
529
|
+
}
|
|
530
|
+
async runNotifyMessage(payload, context = {}) {
|
|
531
|
+
return this.pipeline.runNotifyMessage(payload, this.createRuntimeContext(context));
|
|
532
|
+
}
|
|
533
|
+
async runRunStop(payload, context = {}) {
|
|
534
|
+
return this.pipeline.runRunStop(payload, this.createRuntimeContext(context));
|
|
535
|
+
}
|
|
303
536
|
async runObservers(name, payload, context = {}) {
|
|
304
537
|
await this.pipeline.runObservers(name, payload, this.createRuntimeContext(context));
|
|
305
538
|
}
|
|
@@ -332,6 +565,7 @@ class PluginHost {
|
|
|
332
565
|
sessionId: context.sessionId,
|
|
333
566
|
requestId: context.requestId,
|
|
334
567
|
iteration: context.iteration,
|
|
568
|
+
status: context.status ? structuredClone(context.status) : undefined,
|
|
335
569
|
metadata: context.metadata,
|
|
336
570
|
services: context.services ?? this.services
|
|
337
571
|
};
|
|
@@ -347,6 +581,10 @@ function wrapTool(pluginId, tool, services) {
|
|
|
347
581
|
...context.metadata,
|
|
348
582
|
pluginId
|
|
349
583
|
},
|
|
584
|
+
emitEvent: (event) => context.emitEvent?.({
|
|
585
|
+
...event,
|
|
586
|
+
pluginId: event.pluginId ?? pluginId
|
|
587
|
+
}),
|
|
350
588
|
services
|
|
351
589
|
});
|
|
352
590
|
}
|
|
@@ -678,14 +916,17 @@ function canWriteFs(permissions) {
|
|
|
678
916
|
// src/services/PermissionGateway.ts
|
|
679
917
|
class PermissionGateway {
|
|
680
918
|
permissions;
|
|
919
|
+
compactionOwnerPluginId;
|
|
681
920
|
constructor(options = {}) {
|
|
682
921
|
this.permissions = normalizePermissions(options.permissions);
|
|
922
|
+
this.compactionOwnerPluginId = options.compactionOwnerPluginId;
|
|
683
923
|
}
|
|
684
924
|
get agentPermissions() {
|
|
685
925
|
return { ...this.permissions };
|
|
686
926
|
}
|
|
687
927
|
scopeServices(services, pluginId, requested) {
|
|
688
928
|
const effective = intersectPermissions(this.permissions, requested);
|
|
929
|
+
const pluginState = services.pluginState;
|
|
689
930
|
return {
|
|
690
931
|
fileSystem: {
|
|
691
932
|
readText: (path3, options) => {
|
|
@@ -751,9 +992,33 @@ class PermissionGateway {
|
|
|
751
992
|
throw new PermissionDeniedError(`Plugin ${pluginId} is not allowed to call the model gateway`);
|
|
752
993
|
return services.model.stream(request);
|
|
753
994
|
}
|
|
995
|
+
},
|
|
996
|
+
compaction: {
|
|
997
|
+
getState: (sessionId) => services.compaction.getState(sessionId),
|
|
998
|
+
apply: (update) => {
|
|
999
|
+
this.ensureCompactionOwner(pluginId);
|
|
1000
|
+
return services.compaction.apply(update);
|
|
1001
|
+
},
|
|
1002
|
+
clear: (sessionId) => {
|
|
1003
|
+
this.ensureCompactionOwner(pluginId);
|
|
1004
|
+
return services.compaction.clear(sessionId);
|
|
1005
|
+
}
|
|
1006
|
+
},
|
|
1007
|
+
pluginState: {
|
|
1008
|
+
get: (sessionId) => pluginState.getForPlugin(sessionId, pluginId),
|
|
1009
|
+
replace: (sessionId, entry) => pluginState.replaceForPlugin(sessionId, pluginId, entry),
|
|
1010
|
+
clear: (sessionId) => pluginState.clearForPlugin(sessionId, pluginId)
|
|
754
1011
|
}
|
|
755
1012
|
};
|
|
756
1013
|
}
|
|
1014
|
+
ensureCompactionOwner(pluginId) {
|
|
1015
|
+
if (this.compactionOwnerPluginId === pluginId)
|
|
1016
|
+
return;
|
|
1017
|
+
const message = this.compactionOwnerPluginId ? `Plugin ${pluginId} is not the configured compaction owner (${this.compactionOwnerPluginId})` : `Plugin ${pluginId} cannot modify canonical compaction without compaction.ownerPluginId`;
|
|
1018
|
+
const error = new Error(message);
|
|
1019
|
+
error.code = "compaction_owner_required";
|
|
1020
|
+
throw error;
|
|
1021
|
+
}
|
|
757
1022
|
}
|
|
758
1023
|
function intersectPermissions(agent, requested) {
|
|
759
1024
|
const normalized = requested ?? {};
|
|
@@ -1070,6 +1335,294 @@ class WriteTool extends BaseTool {
|
|
|
1070
1335
|
});
|
|
1071
1336
|
}
|
|
1072
1337
|
}
|
|
1338
|
+
// src/agent-core/approvals.ts
|
|
1339
|
+
class DefaultApprovalManager {
|
|
1340
|
+
handler;
|
|
1341
|
+
notifications;
|
|
1342
|
+
constructor(options = {}) {
|
|
1343
|
+
this.handler = options.handler;
|
|
1344
|
+
this.notifications = options.notifications;
|
|
1345
|
+
}
|
|
1346
|
+
async requestApproval(request, context) {
|
|
1347
|
+
const normalizedRequest = {
|
|
1348
|
+
...request,
|
|
1349
|
+
id: request.id ?? createId("approval")
|
|
1350
|
+
};
|
|
1351
|
+
await this.notifications?.emit({
|
|
1352
|
+
type: "approval.requested",
|
|
1353
|
+
source: "approval",
|
|
1354
|
+
level: "warning",
|
|
1355
|
+
message: normalizedRequest.message,
|
|
1356
|
+
metadata: {
|
|
1357
|
+
approvalId: normalizedRequest.id,
|
|
1358
|
+
toolName: normalizedRequest.toolCall.function.name
|
|
1359
|
+
}
|
|
1360
|
+
}, context);
|
|
1361
|
+
if (!this.handler) {
|
|
1362
|
+
const denied = {
|
|
1363
|
+
type: "denied",
|
|
1364
|
+
reason: "No approval handler configured"
|
|
1365
|
+
};
|
|
1366
|
+
await this.notifications?.emit({
|
|
1367
|
+
type: "approval.denied",
|
|
1368
|
+
source: "approval",
|
|
1369
|
+
level: "warning",
|
|
1370
|
+
message: denied.reason,
|
|
1371
|
+
metadata: {
|
|
1372
|
+
approvalId: normalizedRequest.id,
|
|
1373
|
+
toolName: normalizedRequest.toolCall.function.name
|
|
1374
|
+
}
|
|
1375
|
+
}, context);
|
|
1376
|
+
return denied;
|
|
1377
|
+
}
|
|
1378
|
+
const decision = await this.handler(normalizedRequest);
|
|
1379
|
+
await this.notifications?.emit({
|
|
1380
|
+
type: decision.type === "approved" ? "approval.approved" : "approval.denied",
|
|
1381
|
+
source: "approval",
|
|
1382
|
+
level: decision.type === "approved" ? "info" : "warning",
|
|
1383
|
+
message: decision.type === "approved" ? `Approved tool call: ${normalizedRequest.toolCall.function.name}` : decision.reason ?? `Denied tool call: ${normalizedRequest.toolCall.function.name}`,
|
|
1384
|
+
metadata: {
|
|
1385
|
+
approvalId: normalizedRequest.id,
|
|
1386
|
+
toolName: normalizedRequest.toolCall.function.name
|
|
1387
|
+
}
|
|
1388
|
+
}, context);
|
|
1389
|
+
return decision;
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
// src/agent-core/session-state.ts
|
|
1394
|
+
function touchRuntimeSessionState(state) {
|
|
1395
|
+
state.updatedAt = Date.now();
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
// src/agent-core/compaction.ts
|
|
1399
|
+
function createEmptyCompactionState() {
|
|
1400
|
+
return {
|
|
1401
|
+
cursor: 0,
|
|
1402
|
+
systemSegments: [],
|
|
1403
|
+
checkpoints: []
|
|
1404
|
+
};
|
|
1405
|
+
}
|
|
1406
|
+
function cloneCompactionState(state) {
|
|
1407
|
+
if (!state)
|
|
1408
|
+
return createEmptyCompactionState();
|
|
1409
|
+
return structuredClone({
|
|
1410
|
+
cursor: state.cursor,
|
|
1411
|
+
systemSegments: [...state.systemSegments],
|
|
1412
|
+
checkpoints: state.checkpoints.map((checkpoint) => ({
|
|
1413
|
+
...checkpoint,
|
|
1414
|
+
systemSegments: [...checkpoint.systemSegments],
|
|
1415
|
+
metadata: checkpoint.metadata ? structuredClone(checkpoint.metadata) : undefined
|
|
1416
|
+
}))
|
|
1417
|
+
});
|
|
1418
|
+
}
|
|
1419
|
+
function normalizeCompactionState(state, messages) {
|
|
1420
|
+
const current = cloneCompactionState(state);
|
|
1421
|
+
return {
|
|
1422
|
+
cursor: clampCursor(current.cursor, messages.length),
|
|
1423
|
+
systemSegments: current.systemSegments.filter(Boolean),
|
|
1424
|
+
checkpoints: current.checkpoints.map((checkpoint) => ({
|
|
1425
|
+
...checkpoint,
|
|
1426
|
+
cursor: clampCursor(checkpoint.cursor, messages.length),
|
|
1427
|
+
systemSegments: checkpoint.systemSegments.filter(Boolean)
|
|
1428
|
+
}))
|
|
1429
|
+
};
|
|
1430
|
+
}
|
|
1431
|
+
function applyCompactionUpdate(state, update) {
|
|
1432
|
+
const nextCursor = clampCursor(update.cursor, state.messages.length);
|
|
1433
|
+
const nextSystemSegments = [...update.systemSegments ?? state.compaction.systemSegments].filter(Boolean);
|
|
1434
|
+
const nextCheckpoint = {
|
|
1435
|
+
id: createId("compact"),
|
|
1436
|
+
cursor: nextCursor,
|
|
1437
|
+
systemSegments: nextSystemSegments,
|
|
1438
|
+
summary: update.summary,
|
|
1439
|
+
reason: update.reason ?? "manual",
|
|
1440
|
+
createdAt: Date.now(),
|
|
1441
|
+
metadata: update.metadata ? structuredClone(update.metadata) : undefined
|
|
1442
|
+
};
|
|
1443
|
+
state.compaction = {
|
|
1444
|
+
cursor: nextCursor,
|
|
1445
|
+
systemSegments: nextSystemSegments,
|
|
1446
|
+
checkpoints: [...state.compaction.checkpoints, nextCheckpoint]
|
|
1447
|
+
};
|
|
1448
|
+
touchRuntimeSessionState(state);
|
|
1449
|
+
return cloneCompactionState(state.compaction);
|
|
1450
|
+
}
|
|
1451
|
+
function clearCompactionState(state) {
|
|
1452
|
+
state.compaction = createEmptyCompactionState();
|
|
1453
|
+
touchRuntimeSessionState(state);
|
|
1454
|
+
return cloneCompactionState(state.compaction);
|
|
1455
|
+
}
|
|
1456
|
+
function projectMessagesForRequest(messages, compaction) {
|
|
1457
|
+
const leadingSystemMessages = readLeadingSystemMessages(messages);
|
|
1458
|
+
const startIndex = Math.max(compaction.cursor, leadingSystemMessages.length);
|
|
1459
|
+
return [
|
|
1460
|
+
...leadingSystemMessages,
|
|
1461
|
+
...messages.slice(startIndex)
|
|
1462
|
+
];
|
|
1463
|
+
}
|
|
1464
|
+
function buildCompactionPromptSegments(compaction) {
|
|
1465
|
+
return compaction.systemSegments.filter(Boolean);
|
|
1466
|
+
}
|
|
1467
|
+
function calculateThresholdTokens(options) {
|
|
1468
|
+
if (options.triggerTokens !== undefined)
|
|
1469
|
+
return options.triggerTokens;
|
|
1470
|
+
const ratio = options.triggerRatio ?? 0.8;
|
|
1471
|
+
return Math.max(1, Math.floor(options.maxInputTokens * ratio));
|
|
1472
|
+
}
|
|
1473
|
+
async function estimateCompactionBudget(options, input) {
|
|
1474
|
+
const estimatedInputTokens = options.estimator ? await options.estimator(input) : estimateByHeuristic(input);
|
|
1475
|
+
return {
|
|
1476
|
+
estimatedInputTokens,
|
|
1477
|
+
thresholdTokens: calculateThresholdTokens(options),
|
|
1478
|
+
maxInputTokens: options.maxInputTokens
|
|
1479
|
+
};
|
|
1480
|
+
}
|
|
1481
|
+
function isCompactionRequired(options, budget) {
|
|
1482
|
+
if (!options || options.enabled === false || !budget)
|
|
1483
|
+
return false;
|
|
1484
|
+
return budget.estimatedInputTokens >= budget.thresholdTokens;
|
|
1485
|
+
}
|
|
1486
|
+
function didCompactionStateChange(before, after) {
|
|
1487
|
+
return before.cursor !== after.cursor || JSON.stringify(before.systemSegments) !== JSON.stringify(after.systemSegments) || before.checkpoints.length !== after.checkpoints.length;
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
class DefaultCompactionService {
|
|
1491
|
+
sessions = new Map;
|
|
1492
|
+
registerSession(state) {
|
|
1493
|
+
state.compaction = normalizeCompactionState(state.compaction, state.messages);
|
|
1494
|
+
this.sessions.set(state.id, state);
|
|
1495
|
+
}
|
|
1496
|
+
unregisterSession(sessionId) {
|
|
1497
|
+
this.sessions.delete(sessionId);
|
|
1498
|
+
}
|
|
1499
|
+
async getState(sessionId) {
|
|
1500
|
+
return cloneCompactionState(this.requireSession(sessionId).compaction);
|
|
1501
|
+
}
|
|
1502
|
+
async apply(update) {
|
|
1503
|
+
return applyCompactionUpdate(this.requireSession(update.sessionId), update);
|
|
1504
|
+
}
|
|
1505
|
+
async clear(sessionId) {
|
|
1506
|
+
return clearCompactionState(this.requireSession(sessionId));
|
|
1507
|
+
}
|
|
1508
|
+
requireSession(sessionId) {
|
|
1509
|
+
const state = this.sessions.get(sessionId);
|
|
1510
|
+
if (!state)
|
|
1511
|
+
throw new Error(`Unknown session for compaction: ${sessionId}`);
|
|
1512
|
+
return state;
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
function clampCursor(cursor, max) {
|
|
1516
|
+
if (!Number.isFinite(cursor))
|
|
1517
|
+
return 0;
|
|
1518
|
+
return Math.max(0, Math.min(Math.floor(cursor), max));
|
|
1519
|
+
}
|
|
1520
|
+
function readLeadingSystemMessages(messages) {
|
|
1521
|
+
const systemMessages = [];
|
|
1522
|
+
for (const message of messages) {
|
|
1523
|
+
if (message.role !== "system")
|
|
1524
|
+
break;
|
|
1525
|
+
systemMessages.push(message);
|
|
1526
|
+
}
|
|
1527
|
+
return systemMessages;
|
|
1528
|
+
}
|
|
1529
|
+
function estimateByHeuristic(input) {
|
|
1530
|
+
const messageChars = input.messages.reduce((total, message) => {
|
|
1531
|
+
const contentChars = JSON.stringify(message.content).length;
|
|
1532
|
+
const metadataChars = message.metadata ? JSON.stringify(message.metadata).length : 0;
|
|
1533
|
+
return total + message.role.length + contentChars + metadataChars;
|
|
1534
|
+
}, 0);
|
|
1535
|
+
const toolChars = input.tools ? JSON.stringify(input.tools).length : 0;
|
|
1536
|
+
const modelChars = `${input.model.provider}:${input.model.modelId}`.length;
|
|
1537
|
+
return Math.max(1, Math.ceil((messageChars + toolChars + modelChars) / 4));
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
// src/agent-core/plugin-state.ts
|
|
1541
|
+
var APP_PLUGIN_NAMESPACE = "__agent__";
|
|
1542
|
+
function clonePluginSessionStateEntry(entry) {
|
|
1543
|
+
if (!entry)
|
|
1544
|
+
return null;
|
|
1545
|
+
return structuredClone({
|
|
1546
|
+
version: entry.version,
|
|
1547
|
+
data: structuredClone(entry.data),
|
|
1548
|
+
updatedAt: entry.updatedAt
|
|
1549
|
+
});
|
|
1550
|
+
}
|
|
1551
|
+
function clonePluginStateMap(state) {
|
|
1552
|
+
if (!state)
|
|
1553
|
+
return {};
|
|
1554
|
+
return Object.fromEntries(Object.entries(state).map(([pluginId, entry]) => [pluginId, clonePluginSessionStateEntry(entry)]));
|
|
1555
|
+
}
|
|
1556
|
+
function normalizePluginStateMap(state) {
|
|
1557
|
+
if (!state)
|
|
1558
|
+
return {};
|
|
1559
|
+
return Object.fromEntries(Object.entries(state).map(([pluginId, entry]) => [pluginId, normalizePluginSessionStateEntry(entry, `Plugin session state for ${pluginId}`)]));
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
class DefaultPluginStateService {
|
|
1563
|
+
sessions = new Map;
|
|
1564
|
+
registerSession(state) {
|
|
1565
|
+
state.pluginState = normalizePluginStateMap(state.pluginState);
|
|
1566
|
+
this.sessions.set(state.id, state);
|
|
1567
|
+
}
|
|
1568
|
+
unregisterSession(sessionId) {
|
|
1569
|
+
this.sessions.delete(sessionId);
|
|
1570
|
+
}
|
|
1571
|
+
async get(sessionId) {
|
|
1572
|
+
return this.getForPlugin(sessionId, APP_PLUGIN_NAMESPACE);
|
|
1573
|
+
}
|
|
1574
|
+
async replace(sessionId, entry) {
|
|
1575
|
+
return this.replaceForPlugin(sessionId, APP_PLUGIN_NAMESPACE, entry);
|
|
1576
|
+
}
|
|
1577
|
+
async clear(sessionId) {
|
|
1578
|
+
await this.clearForPlugin(sessionId, APP_PLUGIN_NAMESPACE);
|
|
1579
|
+
}
|
|
1580
|
+
async getForPlugin(sessionId, pluginId) {
|
|
1581
|
+
const state = this.requireSession(sessionId);
|
|
1582
|
+
return clonePluginSessionStateEntry(state.pluginState[pluginId]);
|
|
1583
|
+
}
|
|
1584
|
+
async replaceForPlugin(sessionId, pluginId, entry) {
|
|
1585
|
+
const state = this.requireSession(sessionId);
|
|
1586
|
+
const nextEntry = normalizePluginSessionStateEntry(entry, `Plugin session state for ${pluginId}`);
|
|
1587
|
+
state.pluginState = {
|
|
1588
|
+
...state.pluginState,
|
|
1589
|
+
[pluginId]: nextEntry
|
|
1590
|
+
};
|
|
1591
|
+
touchRuntimeSessionState(state);
|
|
1592
|
+
return clonePluginSessionStateEntry(nextEntry);
|
|
1593
|
+
}
|
|
1594
|
+
async clearForPlugin(sessionId, pluginId) {
|
|
1595
|
+
const state = this.requireSession(sessionId);
|
|
1596
|
+
if (!(pluginId in state.pluginState))
|
|
1597
|
+
return;
|
|
1598
|
+
const nextState = { ...state.pluginState };
|
|
1599
|
+
delete nextState[pluginId];
|
|
1600
|
+
state.pluginState = nextState;
|
|
1601
|
+
touchRuntimeSessionState(state);
|
|
1602
|
+
}
|
|
1603
|
+
async list(sessionId) {
|
|
1604
|
+
return clonePluginStateMap(this.requireSession(sessionId).pluginState);
|
|
1605
|
+
}
|
|
1606
|
+
requireSession(sessionId) {
|
|
1607
|
+
const state = this.sessions.get(sessionId);
|
|
1608
|
+
if (!state)
|
|
1609
|
+
throw new Error(`Unknown session for plugin state: ${sessionId}`);
|
|
1610
|
+
return state;
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
function normalizePluginSessionStateEntry(entry, label) {
|
|
1614
|
+
if (!Number.isFinite(entry.version))
|
|
1615
|
+
throw new TypeError(`${label} version must be a finite number`);
|
|
1616
|
+
if (!Number.isFinite(entry.updatedAt))
|
|
1617
|
+
throw new TypeError(`${label} updatedAt must be a finite number`);
|
|
1618
|
+
assertJsonSafeObject(entry.data, `${label} data`);
|
|
1619
|
+
return {
|
|
1620
|
+
version: Math.floor(entry.version),
|
|
1621
|
+
data: structuredClone(entry.data),
|
|
1622
|
+
updatedAt: entry.updatedAt
|
|
1623
|
+
};
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1073
1626
|
// src/agent-core/MessageFactory.ts
|
|
1074
1627
|
class DefaultMessageFactory {
|
|
1075
1628
|
createSystemMessage(input) {
|
|
@@ -1114,6 +1667,46 @@ class DefaultMessageFactory {
|
|
|
1114
1667
|
}
|
|
1115
1668
|
}
|
|
1116
1669
|
|
|
1670
|
+
// src/agent-core/notifications.ts
|
|
1671
|
+
class DefaultNotificationBus {
|
|
1672
|
+
pluginHost;
|
|
1673
|
+
services;
|
|
1674
|
+
constructor(options) {
|
|
1675
|
+
this.pluginHost = options.pluginHost;
|
|
1676
|
+
this.services = options.services;
|
|
1677
|
+
}
|
|
1678
|
+
async emit(notification, context = {}) {
|
|
1679
|
+
const normalized = {
|
|
1680
|
+
id: notification.id ?? createId("note"),
|
|
1681
|
+
level: notification.level ?? "info",
|
|
1682
|
+
...notification
|
|
1683
|
+
};
|
|
1684
|
+
if (!this.pluginHost)
|
|
1685
|
+
return normalized;
|
|
1686
|
+
const result = await this.pluginHost.runNotifyMessage({ notification: normalized }, {
|
|
1687
|
+
sessionId: context.sessionId,
|
|
1688
|
+
requestId: context.requestId,
|
|
1689
|
+
iteration: context.iteration,
|
|
1690
|
+
cwd: context.cwd,
|
|
1691
|
+
status: context.status,
|
|
1692
|
+
metadata: context.metadata,
|
|
1693
|
+
services: this.services
|
|
1694
|
+
});
|
|
1695
|
+
if (result.suppressed)
|
|
1696
|
+
return;
|
|
1697
|
+
await this.pluginHost.runObservers("notify.message", result.payload, {
|
|
1698
|
+
sessionId: context.sessionId,
|
|
1699
|
+
requestId: context.requestId,
|
|
1700
|
+
iteration: context.iteration,
|
|
1701
|
+
cwd: context.cwd,
|
|
1702
|
+
status: context.status,
|
|
1703
|
+
metadata: context.metadata,
|
|
1704
|
+
services: this.services
|
|
1705
|
+
});
|
|
1706
|
+
return result.payload.notification;
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1117
1710
|
// src/persistence/SnapshotCodec.ts
|
|
1118
1711
|
class JsonSnapshotCodec {
|
|
1119
1712
|
encode(input) {
|
|
@@ -1124,6 +1717,8 @@ class JsonSnapshotCodec {
|
|
|
1124
1717
|
cwd: input.cwd,
|
|
1125
1718
|
messages: input.messages,
|
|
1126
1719
|
usage: input.usage,
|
|
1720
|
+
compaction: input.compaction,
|
|
1721
|
+
pluginState: input.pluginState,
|
|
1127
1722
|
createdAt: input.createdAt,
|
|
1128
1723
|
updatedAt: input.updatedAt,
|
|
1129
1724
|
metadata: input.metadata
|
|
@@ -1199,10 +1794,19 @@ function finalizeToolCall(draft) {
|
|
|
1199
1794
|
// src/agent-core/errors.ts
|
|
1200
1795
|
class SessionExecutionError extends Error {
|
|
1201
1796
|
payload;
|
|
1797
|
+
payloadSummary;
|
|
1202
1798
|
constructor(payload) {
|
|
1203
1799
|
super(payload.message);
|
|
1204
1800
|
this.name = "SessionExecutionError";
|
|
1205
1801
|
this.payload = payload;
|
|
1802
|
+
this.payloadSummary = stringifyPayload(payload);
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
function stringifyPayload(payload) {
|
|
1806
|
+
try {
|
|
1807
|
+
return JSON.stringify(payload, null, 2);
|
|
1808
|
+
} catch {
|
|
1809
|
+
return String(payload);
|
|
1206
1810
|
}
|
|
1207
1811
|
}
|
|
1208
1812
|
|
|
@@ -1297,9 +1901,19 @@ class DefaultModelTurnCollector {
|
|
|
1297
1901
|
}
|
|
1298
1902
|
}
|
|
1299
1903
|
|
|
1300
|
-
// src/agent-core/session-
|
|
1301
|
-
function
|
|
1302
|
-
|
|
1904
|
+
// src/agent-core/session-status.ts
|
|
1905
|
+
function createSessionStatus(state) {
|
|
1906
|
+
return {
|
|
1907
|
+
sessionId: state.id,
|
|
1908
|
+
model: { ...state.modelRef },
|
|
1909
|
+
cwd: state.cwd,
|
|
1910
|
+
usage: { ...state.usage },
|
|
1911
|
+
compaction: cloneCompactionState(state.compaction),
|
|
1912
|
+
messageCount: state.messages.length,
|
|
1913
|
+
createdAt: state.createdAt,
|
|
1914
|
+
updatedAt: state.updatedAt,
|
|
1915
|
+
metadata: state.metadata ? structuredClone(state.metadata) : undefined
|
|
1916
|
+
};
|
|
1303
1917
|
}
|
|
1304
1918
|
|
|
1305
1919
|
// src/agent-core/LoopRunner.ts
|
|
@@ -1310,6 +1924,7 @@ class LoopRunner {
|
|
|
1310
1924
|
terminationPolicy;
|
|
1311
1925
|
contextManager;
|
|
1312
1926
|
pluginHost;
|
|
1927
|
+
notificationBus;
|
|
1313
1928
|
constructor(dependencies) {
|
|
1314
1929
|
this.messageFactory = dependencies.messageFactory;
|
|
1315
1930
|
this.turnCollector = dependencies.turnCollector;
|
|
@@ -1317,36 +1932,44 @@ class LoopRunner {
|
|
|
1317
1932
|
this.terminationPolicy = dependencies.terminationPolicy;
|
|
1318
1933
|
this.contextManager = dependencies.contextManager;
|
|
1319
1934
|
this.pluginHost = dependencies.pluginHost;
|
|
1935
|
+
this.notificationBus = dependencies.notificationBus;
|
|
1320
1936
|
}
|
|
1321
1937
|
async* run(state, input, options = {}) {
|
|
1322
|
-
const startPayload = await this.pluginHost?.runMiddleware("
|
|
1938
|
+
const startPayload = await this.pluginHost?.runMiddleware("run.start", {
|
|
1323
1939
|
sessionId: state.id,
|
|
1324
1940
|
input
|
|
1325
|
-
},
|
|
1326
|
-
sessionId: state.id,
|
|
1327
|
-
cwd: state.cwd,
|
|
1328
|
-
metadata: state.metadata
|
|
1329
|
-
});
|
|
1941
|
+
}, this.createHookContext(state));
|
|
1330
1942
|
const initialInput = startPayload?.input ?? input;
|
|
1943
|
+
await this.pluginHost?.runObservers("run.start", {
|
|
1944
|
+
sessionId: state.id,
|
|
1945
|
+
input: initialInput
|
|
1946
|
+
}, this.createHookContext(state));
|
|
1331
1947
|
const userMessage = this.messageFactory.createUserMessage(initialInput);
|
|
1332
1948
|
state.messages.push(userMessage);
|
|
1333
1949
|
touchRuntimeSessionState(state);
|
|
1950
|
+
await this.notificationBus.emit({
|
|
1951
|
+
type: "run.start",
|
|
1952
|
+
source: "runtime",
|
|
1953
|
+
message: `Run started for session ${state.id}`,
|
|
1954
|
+
metadata: {
|
|
1955
|
+
sessionId: state.id
|
|
1956
|
+
}
|
|
1957
|
+
}, {
|
|
1958
|
+
...this.createHookContext(state)
|
|
1959
|
+
});
|
|
1334
1960
|
let lastRequestId;
|
|
1961
|
+
let lastIteration = 0;
|
|
1962
|
+
let pendingContextSegments = [];
|
|
1335
1963
|
try {
|
|
1336
1964
|
for (let iteration = 0;iteration < state.maxIterations; iteration += 1) {
|
|
1965
|
+
lastIteration = iteration;
|
|
1337
1966
|
const requestId = createId("req");
|
|
1338
1967
|
lastRequestId = requestId;
|
|
1339
1968
|
const turnStart = await this.pluginHost?.runMiddleware("turn.start", {
|
|
1340
1969
|
requestId,
|
|
1341
1970
|
iteration,
|
|
1342
1971
|
messages: [...state.messages]
|
|
1343
|
-
}, {
|
|
1344
|
-
sessionId: state.id,
|
|
1345
|
-
requestId,
|
|
1346
|
-
iteration,
|
|
1347
|
-
cwd: state.cwd,
|
|
1348
|
-
metadata: state.metadata
|
|
1349
|
-
});
|
|
1972
|
+
}, this.createHookContext(state, { requestId, iteration }));
|
|
1350
1973
|
const contextItems = await this.contextManager.resolve({
|
|
1351
1974
|
sessionId: state.id,
|
|
1352
1975
|
input: initialInput,
|
|
@@ -1357,13 +1980,7 @@ class LoopRunner {
|
|
|
1357
1980
|
contextItems,
|
|
1358
1981
|
input: initialInput,
|
|
1359
1982
|
messages: [...state.messages]
|
|
1360
|
-
}, {
|
|
1361
|
-
sessionId: state.id,
|
|
1362
|
-
requestId,
|
|
1363
|
-
iteration,
|
|
1364
|
-
cwd: state.cwd,
|
|
1365
|
-
metadata: state.metadata
|
|
1366
|
-
});
|
|
1983
|
+
}, this.createHookContext(state, { requestId, iteration }));
|
|
1367
1984
|
const effectiveContext = resolvedContext?.contextItems ?? contextItems;
|
|
1368
1985
|
const pluginPromptSegments = await this.pluginHost?.collectPromptSegments({
|
|
1369
1986
|
sessionId: state.id,
|
|
@@ -1373,37 +1990,31 @@ class LoopRunner {
|
|
|
1373
1990
|
cwd: state.cwd
|
|
1374
1991
|
}) ?? [];
|
|
1375
1992
|
const promptSegments = [
|
|
1993
|
+
...pendingContextSegments,
|
|
1376
1994
|
...this.contextManager.format(effectiveContext),
|
|
1377
1995
|
...pluginPromptSegments
|
|
1378
1996
|
];
|
|
1997
|
+
pendingContextSegments = [];
|
|
1379
1998
|
const resolvedPrompt = await this.pluginHost?.runMiddleware("prompt.resolve", {
|
|
1380
1999
|
promptSegments,
|
|
1381
2000
|
contextItems: effectiveContext,
|
|
1382
2001
|
messages: [...state.messages]
|
|
1383
|
-
}, {
|
|
1384
|
-
sessionId: state.id,
|
|
1385
|
-
requestId,
|
|
1386
|
-
iteration,
|
|
1387
|
-
cwd: state.cwd,
|
|
1388
|
-
metadata: state.metadata
|
|
1389
|
-
});
|
|
2002
|
+
}, this.createHookContext(state, { requestId, iteration }));
|
|
1390
2003
|
const effectivePromptSegments = resolvedPrompt?.promptSegments ?? promptSegments;
|
|
1391
2004
|
const toolDefinitions = state.tools?.definitions() ?? [];
|
|
1392
2005
|
const resolvedToolset = await this.pluginHost?.runMiddleware("toolset.resolve", {
|
|
1393
2006
|
tools: toolDefinitions
|
|
1394
|
-
}, {
|
|
1395
|
-
|
|
2007
|
+
}, this.createHookContext(state, { requestId, iteration }));
|
|
2008
|
+
const effectiveTools = resolvedToolset?.tools ?? toolDefinitions;
|
|
2009
|
+
const requestMessages = await this.resolveRequestMessages({
|
|
2010
|
+
state,
|
|
1396
2011
|
requestId,
|
|
1397
2012
|
iteration,
|
|
1398
|
-
|
|
1399
|
-
|
|
2013
|
+
baseMessages: [...turnStart?.messages ?? state.messages],
|
|
2014
|
+
runtimePromptSegments: effectivePromptSegments,
|
|
2015
|
+
contextItems: effectiveContext,
|
|
2016
|
+
tools: effectiveTools
|
|
1400
2017
|
});
|
|
1401
|
-
const effectiveTools = resolvedToolset?.tools ?? toolDefinitions;
|
|
1402
|
-
const requestMessages = [...turnStart?.messages ?? state.messages];
|
|
1403
|
-
if (effectivePromptSegments.length > 0)
|
|
1404
|
-
requestMessages.unshift(this.messageFactory.createSystemMessage(effectivePromptSegments.join(`
|
|
1405
|
-
|
|
1406
|
-
`)));
|
|
1407
2018
|
const request = {
|
|
1408
2019
|
requestId,
|
|
1409
2020
|
model: state.modelRef,
|
|
@@ -1414,25 +2025,30 @@ class LoopRunner {
|
|
|
1414
2025
|
};
|
|
1415
2026
|
const resolvedRequest = await this.pluginHost?.runMiddleware("model.request", {
|
|
1416
2027
|
request
|
|
1417
|
-
}, {
|
|
1418
|
-
sessionId: state.id,
|
|
1419
|
-
requestId,
|
|
1420
|
-
iteration,
|
|
1421
|
-
cwd: state.cwd,
|
|
1422
|
-
metadata: state.metadata
|
|
1423
|
-
});
|
|
2028
|
+
}, this.createHookContext(state, { requestId, iteration }));
|
|
1424
2029
|
const effectiveRequest = resolvedRequest?.request ?? request;
|
|
1425
2030
|
const turn = yield* this.turnCollector.collect({
|
|
1426
2031
|
sessionId: state.id,
|
|
1427
2032
|
requestId,
|
|
1428
2033
|
events: state.model.stream(effectiveRequest),
|
|
1429
2034
|
onEvent: async (event) => {
|
|
2035
|
+
if (event.type === "error") {
|
|
2036
|
+
await this.notificationBus.emit({
|
|
2037
|
+
type: "provider.error",
|
|
2038
|
+
source: "provider",
|
|
2039
|
+
level: "error",
|
|
2040
|
+
message: event.error.message,
|
|
2041
|
+
metadata: {
|
|
2042
|
+
requestId,
|
|
2043
|
+
code: event.error.code,
|
|
2044
|
+
status: event.error.status
|
|
2045
|
+
}
|
|
2046
|
+
}, {
|
|
2047
|
+
...this.createHookContext(state, { requestId, iteration })
|
|
2048
|
+
});
|
|
2049
|
+
}
|
|
1430
2050
|
await this.pluginHost?.runObservers("model.event", { event }, {
|
|
1431
|
-
|
|
1432
|
-
requestId,
|
|
1433
|
-
iteration,
|
|
1434
|
-
cwd: state.cwd,
|
|
1435
|
-
metadata: state.metadata
|
|
2051
|
+
...this.createHookContext(state, { requestId, iteration })
|
|
1436
2052
|
});
|
|
1437
2053
|
}
|
|
1438
2054
|
});
|
|
@@ -1445,53 +2061,85 @@ class LoopRunner {
|
|
|
1445
2061
|
});
|
|
1446
2062
|
const assistantPayload = await this.pluginHost?.runMiddleware("assistant.message", {
|
|
1447
2063
|
message: assistantDraft
|
|
1448
|
-
}, {
|
|
1449
|
-
|
|
1450
|
-
requestId,
|
|
1451
|
-
iteration,
|
|
1452
|
-
cwd: state.cwd,
|
|
1453
|
-
metadata: state.metadata
|
|
1454
|
-
});
|
|
1455
|
-
const assistantMessage = assistantPayload?.message ?? assistantDraft;
|
|
2064
|
+
}, this.createHookContext(state, { requestId, iteration }));
|
|
2065
|
+
let assistantMessage = assistantPayload?.message ?? assistantDraft;
|
|
1456
2066
|
state.messages.push(assistantMessage);
|
|
1457
2067
|
state.usage = addUsage(state.usage, turn.usage);
|
|
1458
2068
|
touchRuntimeSessionState(state);
|
|
1459
2069
|
const baseDecision = this.terminationPolicy.afterAssistantTurn(turn.toolCalls);
|
|
1460
|
-
const
|
|
2070
|
+
const stopBasePayload = {
|
|
2071
|
+
assistantMessage,
|
|
1461
2072
|
toolCalls: turn.toolCalls,
|
|
1462
2073
|
stopReason: turn.stopReason,
|
|
1463
2074
|
decision: baseDecision
|
|
1464
|
-
}
|
|
1465
|
-
|
|
1466
|
-
requestId,
|
|
1467
|
-
iteration,
|
|
1468
|
-
cwd: state.cwd,
|
|
1469
|
-
metadata: state.metadata
|
|
2075
|
+
};
|
|
2076
|
+
const stopResult = await this.pluginHost?.runRunStop(stopBasePayload, {
|
|
2077
|
+
...this.createHookContext(state, { requestId, iteration })
|
|
1470
2078
|
});
|
|
1471
|
-
const
|
|
1472
|
-
|
|
2079
|
+
const stopPayload = stopResult?.payload ?? stopBasePayload;
|
|
2080
|
+
const stopControl = stopResult?.control;
|
|
2081
|
+
await this.pluginHost?.runObservers("run.stop", {
|
|
2082
|
+
...stopPayload,
|
|
2083
|
+
additionalContext: stopControl?.type === "finalize" ? stopControl.additionalContext ?? stopPayload.additionalContext : stopPayload.additionalContext,
|
|
2084
|
+
finalMessage: stopControl?.type === "finalize" ? stopControl.finalMessage ?? stopPayload.finalMessage : stopPayload.finalMessage
|
|
2085
|
+
}, this.createHookContext(state, { requestId, iteration }));
|
|
2086
|
+
const stopDecision = stopControl?.type === "finalize" ? { type: "done" } : stopPayload.decision;
|
|
2087
|
+
const additionalContext = stopControl?.type === "finalize" ? stopControl.additionalContext : stopPayload.additionalContext;
|
|
2088
|
+
const finalMessage = stopControl?.type === "finalize" ? stopControl.finalMessage ?? stopPayload.finalMessage ?? stopPayload.assistantMessage : stopPayload.finalMessage ?? stopPayload.assistantMessage;
|
|
2089
|
+
if (additionalContext && additionalContext.length > 0 && stopDecision.type === "continue")
|
|
2090
|
+
pendingContextSegments = [...pendingContextSegments, ...additionalContext];
|
|
2091
|
+
if (stopDecision.type === "done") {
|
|
2092
|
+
assistantMessage = finalMessage;
|
|
2093
|
+
state.messages[state.messages.length - 1] = assistantMessage;
|
|
2094
|
+
touchRuntimeSessionState(state);
|
|
2095
|
+
const endPayload = await this.pluginHost?.runMiddleware("run.end", {
|
|
2096
|
+
message: assistantMessage,
|
|
2097
|
+
usage: { ...state.usage }
|
|
2098
|
+
}, this.createHookContext(state, { requestId, iteration }));
|
|
2099
|
+
const completedMessage = endPayload?.message ?? assistantMessage;
|
|
2100
|
+
const completedUsage = endPayload?.usage ?? { ...state.usage };
|
|
2101
|
+
state.messages[state.messages.length - 1] = completedMessage;
|
|
2102
|
+
state.usage = { ...completedUsage };
|
|
2103
|
+
touchRuntimeSessionState(state);
|
|
2104
|
+
await this.notificationBus.emit({
|
|
2105
|
+
type: "run.end",
|
|
2106
|
+
source: "runtime",
|
|
2107
|
+
message: `Run completed for session ${state.id}`,
|
|
2108
|
+
metadata: {
|
|
2109
|
+
sessionId: state.id,
|
|
2110
|
+
requestId
|
|
2111
|
+
}
|
|
2112
|
+
}, {
|
|
2113
|
+
...this.createHookContext(state, { requestId, iteration })
|
|
2114
|
+
});
|
|
1473
2115
|
yield {
|
|
1474
2116
|
type: "done",
|
|
1475
2117
|
sessionId: state.id,
|
|
1476
|
-
message:
|
|
1477
|
-
usage:
|
|
2118
|
+
message: completedMessage,
|
|
2119
|
+
usage: completedUsage
|
|
1478
2120
|
};
|
|
1479
|
-
await this.pluginHost?.runObservers("
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
});
|
|
1486
|
-
await options.onDone?.(assistantMessage);
|
|
1487
|
-
return assistantMessage;
|
|
2121
|
+
await this.pluginHost?.runObservers("run.end", {
|
|
2122
|
+
message: completedMessage,
|
|
2123
|
+
usage: completedUsage
|
|
2124
|
+
}, this.createHookContext(state, { requestId, iteration }));
|
|
2125
|
+
await options.onDone?.(completedMessage);
|
|
2126
|
+
return completedMessage;
|
|
1488
2127
|
}
|
|
1489
2128
|
for (const toolCall of turn.toolCalls) {
|
|
1490
2129
|
yield { type: "tool_call", sessionId: state.id, toolCall };
|
|
2130
|
+
const pluginEvents = [];
|
|
1491
2131
|
const result = await this.toolExecutor.execute(toolCall, {
|
|
1492
2132
|
signal: options.signal,
|
|
1493
|
-
cwd: state.cwd
|
|
2133
|
+
cwd: state.cwd,
|
|
2134
|
+
requestId,
|
|
2135
|
+
iteration,
|
|
2136
|
+
status: createSessionStatus(state),
|
|
2137
|
+
onEvent: async (event) => {
|
|
2138
|
+
pluginEvents.push(event);
|
|
2139
|
+
}
|
|
1494
2140
|
});
|
|
2141
|
+
for (const event of pluginEvents)
|
|
2142
|
+
yield event;
|
|
1495
2143
|
const toolMessage = this.messageFactory.createToolMessage(toolCall, result);
|
|
1496
2144
|
state.messages.push(toolMessage);
|
|
1497
2145
|
touchRuntimeSessionState(state);
|
|
@@ -1500,25 +2148,151 @@ class LoopRunner {
|
|
|
1500
2148
|
}
|
|
1501
2149
|
} catch (error) {
|
|
1502
2150
|
if (error instanceof SessionExecutionError) {
|
|
2151
|
+
await this.notificationBus.emit({
|
|
2152
|
+
type: error.payload.code.startsWith("openai_") || error.payload.code.startsWith("anthropic_") || error.payload.code.startsWith("gemini_") ? "provider.error" : "runtime.error",
|
|
2153
|
+
source: error.payload.code.includes("_http_") || error.payload.code.includes("_request_") ? "provider" : "runtime",
|
|
2154
|
+
level: "error",
|
|
2155
|
+
message: error.payload.message,
|
|
2156
|
+
metadata: {
|
|
2157
|
+
requestId: lastRequestId,
|
|
2158
|
+
code: error.payload.code
|
|
2159
|
+
}
|
|
2160
|
+
}, {
|
|
2161
|
+
...this.createHookContext(state, { requestId: lastRequestId, iteration: lastIteration })
|
|
2162
|
+
});
|
|
1503
2163
|
await this.pluginHost?.runObservers("session.error", { error: error.payload }, {
|
|
1504
|
-
|
|
1505
|
-
requestId: lastRequestId,
|
|
1506
|
-
cwd: state.cwd,
|
|
1507
|
-
metadata: state.metadata
|
|
2164
|
+
...this.createHookContext(state, { requestId: lastRequestId })
|
|
1508
2165
|
});
|
|
1509
2166
|
}
|
|
1510
2167
|
throw error;
|
|
1511
2168
|
}
|
|
1512
2169
|
const payload = this.terminationPolicy.onMaxIterationsExceeded(state.maxIterations, lastRequestId);
|
|
1513
2170
|
yield { type: "error", sessionId: state.id, error: payload };
|
|
2171
|
+
await this.notificationBus.emit({
|
|
2172
|
+
type: "runtime.error",
|
|
2173
|
+
source: "runtime",
|
|
2174
|
+
level: "error",
|
|
2175
|
+
message: payload.message,
|
|
2176
|
+
metadata: {
|
|
2177
|
+
requestId: lastRequestId,
|
|
2178
|
+
code: payload.code
|
|
2179
|
+
}
|
|
2180
|
+
}, {
|
|
2181
|
+
...this.createHookContext(state, { requestId: lastRequestId, iteration: lastIteration })
|
|
2182
|
+
});
|
|
1514
2183
|
await this.pluginHost?.runObservers("session.error", { error: payload }, {
|
|
1515
|
-
|
|
1516
|
-
requestId: lastRequestId,
|
|
1517
|
-
cwd: state.cwd,
|
|
1518
|
-
metadata: state.metadata
|
|
2184
|
+
...this.createHookContext(state, { requestId: lastRequestId })
|
|
1519
2185
|
});
|
|
1520
2186
|
throw new SessionExecutionError(payload);
|
|
1521
2187
|
}
|
|
2188
|
+
async resolveRequestMessages(input) {
|
|
2189
|
+
const initialAttempt = await this.buildProjectedRequestMessages(input.state, input.baseMessages, input.runtimePromptSegments, input.tools);
|
|
2190
|
+
if (!isCompactionRequired(input.state.compactionOptions, initialAttempt.budget))
|
|
2191
|
+
return initialAttempt.messages;
|
|
2192
|
+
const budget = initialAttempt.budget;
|
|
2193
|
+
if (!budget)
|
|
2194
|
+
return initialAttempt.messages;
|
|
2195
|
+
await this.notificationBus.emit({
|
|
2196
|
+
type: "context.compaction.required",
|
|
2197
|
+
source: "runtime",
|
|
2198
|
+
level: "warning",
|
|
2199
|
+
message: `Context compaction required for session ${input.state.id}`,
|
|
2200
|
+
metadata: {
|
|
2201
|
+
requestId: input.requestId,
|
|
2202
|
+
estimatedInputTokens: budget.estimatedInputTokens,
|
|
2203
|
+
thresholdTokens: budget.thresholdTokens,
|
|
2204
|
+
maxInputTokens: budget.maxInputTokens,
|
|
2205
|
+
cursor: input.state.compaction.cursor
|
|
2206
|
+
}
|
|
2207
|
+
}, {
|
|
2208
|
+
...this.createHookContext(input.state, { requestId: input.requestId, iteration: input.iteration })
|
|
2209
|
+
});
|
|
2210
|
+
const beforeState = cloneCompactionState(input.state.compaction);
|
|
2211
|
+
const compactPayload = await this.pluginHost?.runMiddleware("context.compact.before", {
|
|
2212
|
+
sessionId: input.state.id,
|
|
2213
|
+
messages: [...input.state.messages],
|
|
2214
|
+
cursor: beforeState.cursor,
|
|
2215
|
+
systemSegments: [...beforeState.systemSegments],
|
|
2216
|
+
contextItems: input.contextItems,
|
|
2217
|
+
estimatedInputTokens: budget.estimatedInputTokens,
|
|
2218
|
+
thresholdTokens: budget.thresholdTokens,
|
|
2219
|
+
maxInputTokens: budget.maxInputTokens,
|
|
2220
|
+
trigger: "threshold"
|
|
2221
|
+
}, {
|
|
2222
|
+
...this.createHookContext(input.state, { requestId: input.requestId, iteration: input.iteration })
|
|
2223
|
+
});
|
|
2224
|
+
await this.pluginHost?.runObservers("context.compact.before", compactPayload ?? {
|
|
2225
|
+
sessionId: input.state.id,
|
|
2226
|
+
messages: [...input.state.messages],
|
|
2227
|
+
cursor: beforeState.cursor,
|
|
2228
|
+
systemSegments: [...beforeState.systemSegments],
|
|
2229
|
+
contextItems: input.contextItems,
|
|
2230
|
+
estimatedInputTokens: budget.estimatedInputTokens,
|
|
2231
|
+
thresholdTokens: budget.thresholdTokens,
|
|
2232
|
+
maxInputTokens: budget.maxInputTokens,
|
|
2233
|
+
trigger: "threshold"
|
|
2234
|
+
}, {
|
|
2235
|
+
...this.createHookContext(input.state, { requestId: input.requestId, iteration: input.iteration })
|
|
2236
|
+
});
|
|
2237
|
+
input.state.compaction = normalizeCompactionState(input.state.compaction, input.state.messages);
|
|
2238
|
+
const afterState = cloneCompactionState(input.state.compaction);
|
|
2239
|
+
if (didCompactionStateChange(beforeState, afterState)) {
|
|
2240
|
+
const latestCheckpoint = afterState.checkpoints.at(-1);
|
|
2241
|
+
await this.notificationBus.emit({
|
|
2242
|
+
type: "context.compacted",
|
|
2243
|
+
source: "runtime",
|
|
2244
|
+
level: "info",
|
|
2245
|
+
message: `Context compacted for session ${input.state.id}`,
|
|
2246
|
+
metadata: {
|
|
2247
|
+
requestId: input.requestId,
|
|
2248
|
+
cursor: afterState.cursor,
|
|
2249
|
+
checkpointId: latestCheckpoint?.id,
|
|
2250
|
+
reason: latestCheckpoint?.reason
|
|
2251
|
+
}
|
|
2252
|
+
}, {
|
|
2253
|
+
...this.createHookContext(input.state, { requestId: input.requestId, iteration: input.iteration })
|
|
2254
|
+
});
|
|
2255
|
+
}
|
|
2256
|
+
const compactedAttempt = await this.buildProjectedRequestMessages(input.state, input.baseMessages, input.runtimePromptSegments, input.tools);
|
|
2257
|
+
if (!isCompactionRequired(input.state.compactionOptions, compactedAttempt.budget))
|
|
2258
|
+
return compactedAttempt.messages;
|
|
2259
|
+
throw new SessionExecutionError({
|
|
2260
|
+
code: "context_compaction_required",
|
|
2261
|
+
message: `Context compaction required before continuing the run (estimated ${budget.estimatedInputTokens} tokens, threshold ${budget.thresholdTokens})`,
|
|
2262
|
+
requestId: input.requestId
|
|
2263
|
+
});
|
|
2264
|
+
}
|
|
2265
|
+
async buildProjectedRequestMessages(state, baseMessages, runtimePromptSegments, tools) {
|
|
2266
|
+
const projectedMessages = projectMessagesForRequest(baseMessages, state.compaction);
|
|
2267
|
+
const promptSegments = [
|
|
2268
|
+
...buildCompactionPromptSegments(state.compaction),
|
|
2269
|
+
...runtimePromptSegments
|
|
2270
|
+
];
|
|
2271
|
+
const requestMessages = [...projectedMessages];
|
|
2272
|
+
if (promptSegments.length > 0)
|
|
2273
|
+
requestMessages.unshift(this.messageFactory.createSystemMessage(promptSegments.join(`
|
|
2274
|
+
|
|
2275
|
+
`)));
|
|
2276
|
+
const budget = state.compactionOptions && state.compactionOptions.enabled !== false ? await estimateCompactionBudget(state.compactionOptions, {
|
|
2277
|
+
model: state.modelRef,
|
|
2278
|
+
messages: requestMessages,
|
|
2279
|
+
tools
|
|
2280
|
+
}) : undefined;
|
|
2281
|
+
return {
|
|
2282
|
+
messages: requestMessages,
|
|
2283
|
+
budget
|
|
2284
|
+
};
|
|
2285
|
+
}
|
|
2286
|
+
createHookContext(state, input = {}) {
|
|
2287
|
+
return {
|
|
2288
|
+
sessionId: state.id,
|
|
2289
|
+
requestId: input.requestId,
|
|
2290
|
+
iteration: input.iteration,
|
|
2291
|
+
cwd: state.cwd,
|
|
2292
|
+
metadata: state.metadata,
|
|
2293
|
+
status: createSessionStatus(state)
|
|
2294
|
+
};
|
|
2295
|
+
}
|
|
1522
2296
|
}
|
|
1523
2297
|
|
|
1524
2298
|
// src/agent-core/TerminationPolicy.ts
|
|
@@ -1542,52 +2316,117 @@ class DefaultToolExecutor {
|
|
|
1542
2316
|
metadata;
|
|
1543
2317
|
services;
|
|
1544
2318
|
pluginHost;
|
|
2319
|
+
approvalManager;
|
|
2320
|
+
notificationBus;
|
|
1545
2321
|
constructor(options) {
|
|
1546
2322
|
this.sessionId = options.sessionId;
|
|
1547
2323
|
this.tools = options.tools;
|
|
1548
2324
|
this.metadata = options.metadata;
|
|
1549
2325
|
this.services = options.services;
|
|
1550
2326
|
this.pluginHost = options.pluginHost;
|
|
2327
|
+
this.approvalManager = options.approvalManager;
|
|
2328
|
+
this.notificationBus = options.notificationBus;
|
|
1551
2329
|
}
|
|
1552
2330
|
async execute(toolCall, options = {}) {
|
|
1553
|
-
const
|
|
1554
|
-
toolCall
|
|
1555
|
-
}, {
|
|
2331
|
+
const hookContext = {
|
|
1556
2332
|
sessionId: this.sessionId,
|
|
2333
|
+
requestId: options.requestId,
|
|
2334
|
+
iteration: options.iteration,
|
|
1557
2335
|
cwd: options.cwd,
|
|
2336
|
+
status: options.status,
|
|
1558
2337
|
services: this.services,
|
|
1559
2338
|
metadata: this.metadata
|
|
1560
|
-
}
|
|
1561
|
-
const
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
2339
|
+
};
|
|
2340
|
+
const before = await this.pluginHost?.runToolBeforeExecute({
|
|
2341
|
+
toolCall
|
|
2342
|
+
}, hookContext);
|
|
2343
|
+
let currentToolCall = before?.payload.toolCall ?? toolCall;
|
|
1565
2344
|
let result;
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
2345
|
+
if (before?.control) {
|
|
2346
|
+
if (before.control.type === "provideResult") {
|
|
2347
|
+
result = normalizeToolResult(before.control.result);
|
|
2348
|
+
} else if (before.control.type === "deny") {
|
|
2349
|
+
result = normalizeToolResult(before.control.result ?? {
|
|
2350
|
+
...textToolResult(before.control.reason ?? `Tool denied: ${currentToolCall.function.name}`),
|
|
2351
|
+
isError: true
|
|
2352
|
+
});
|
|
2353
|
+
await this.notificationBus.emit({
|
|
2354
|
+
type: "tool.denied",
|
|
2355
|
+
source: "permission",
|
|
2356
|
+
level: "warning",
|
|
2357
|
+
message: before.control.reason ?? `Tool denied: ${currentToolCall.function.name}`,
|
|
2358
|
+
metadata: {
|
|
2359
|
+
toolName: currentToolCall.function.name
|
|
2360
|
+
}
|
|
2361
|
+
}, hookContext);
|
|
2362
|
+
} else if (before.control.type === "ask") {
|
|
2363
|
+
currentToolCall = before.control.toolCall ?? currentToolCall;
|
|
2364
|
+
const approvalRequest = {
|
|
2365
|
+
id: undefined,
|
|
2366
|
+
kind: "tool_execution",
|
|
2367
|
+
toolCall: currentToolCall,
|
|
2368
|
+
message: before.control.request?.message ?? `Approval required to execute tool: ${currentToolCall.function.name}`,
|
|
2369
|
+
metadata: before.control.request?.metadata
|
|
2370
|
+
};
|
|
2371
|
+
const decision = await this.approvalManager.requestApproval(approvalRequest, hookContext);
|
|
2372
|
+
if (decision.type === "denied") {
|
|
2373
|
+
result = normalizeToolResult({
|
|
2374
|
+
...textToolResult(decision.reason ?? `Tool approval denied: ${currentToolCall.function.name}`),
|
|
2375
|
+
isError: true
|
|
2376
|
+
});
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
if (!result) {
|
|
2381
|
+
const tool = this.tools?.get(currentToolCall.function.name);
|
|
2382
|
+
if (!tool) {
|
|
2383
|
+
result = normalizeToolResult({ ...textToolResult(`Tool not found: ${currentToolCall.function.name}`), isError: true });
|
|
2384
|
+
} else {
|
|
2385
|
+
try {
|
|
2386
|
+
result = normalizeToolResult(await tool.execute(currentToolCall.function.arguments, {
|
|
2387
|
+
sessionId: this.sessionId,
|
|
2388
|
+
cwd: options.cwd,
|
|
2389
|
+
signal: options.signal,
|
|
2390
|
+
metadata: this.metadata,
|
|
2391
|
+
services: this.services,
|
|
2392
|
+
emitEvent: async (event) => {
|
|
2393
|
+
const pluginId = event.pluginId;
|
|
2394
|
+
if (!pluginId)
|
|
2395
|
+
throw new Error(`Tool ${currentToolCall.function.name} emitted a plugin event without pluginId`);
|
|
2396
|
+
await options.onEvent?.({
|
|
2397
|
+
type: "plugin_event",
|
|
2398
|
+
sessionId: this.sessionId,
|
|
2399
|
+
pluginId,
|
|
2400
|
+
event: event.event,
|
|
2401
|
+
data: event.data ? structuredClone(event.data) : undefined
|
|
2402
|
+
});
|
|
2403
|
+
}
|
|
2404
|
+
}));
|
|
2405
|
+
} catch (error) {
|
|
2406
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2407
|
+
result = normalizeToolResult({
|
|
2408
|
+
...textToolResult(message),
|
|
2409
|
+
isError: true
|
|
2410
|
+
});
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
1580
2413
|
}
|
|
1581
2414
|
const after = await this.pluginHost?.runMiddleware("tool.afterExecute", {
|
|
1582
2415
|
toolCall: currentToolCall,
|
|
1583
2416
|
result
|
|
1584
|
-
},
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
2417
|
+
}, hookContext);
|
|
2418
|
+
const finalResult = normalizeToolResult(after?.result ?? result);
|
|
2419
|
+
await this.notificationBus.emit({
|
|
2420
|
+
type: finalResult.isError ? "tool.failed" : "tool.completed",
|
|
2421
|
+
source: "tool",
|
|
2422
|
+
level: finalResult.isError ? "error" : "info",
|
|
2423
|
+
message: finalResult.isError ? `Tool failed: ${currentToolCall.function.name}` : `Tool completed: ${currentToolCall.function.name}`,
|
|
2424
|
+
metadata: {
|
|
2425
|
+
toolName: currentToolCall.function.name,
|
|
2426
|
+
isError: finalResult.isError === true
|
|
2427
|
+
}
|
|
2428
|
+
}, hookContext);
|
|
2429
|
+
return finalResult;
|
|
1591
2430
|
}
|
|
1592
2431
|
}
|
|
1593
2432
|
|
|
@@ -1600,6 +2439,12 @@ class Session {
|
|
|
1600
2439
|
pluginHost;
|
|
1601
2440
|
contextManager;
|
|
1602
2441
|
services;
|
|
2442
|
+
approvalManager;
|
|
2443
|
+
notificationBus;
|
|
2444
|
+
compactionService;
|
|
2445
|
+
pluginStateService;
|
|
2446
|
+
pluginControllers;
|
|
2447
|
+
activeRunCount = 0;
|
|
1603
2448
|
constructor(options) {
|
|
1604
2449
|
const now = Date.now();
|
|
1605
2450
|
this.id = options.id ?? createId("session");
|
|
@@ -1608,6 +2453,10 @@ class Session {
|
|
|
1608
2453
|
this.pluginHost = options.pluginHost;
|
|
1609
2454
|
this.contextManager = options.contextManager;
|
|
1610
2455
|
this.services = options.services;
|
|
2456
|
+
this.approvalManager = options.approvalManager;
|
|
2457
|
+
this.notificationBus = options.notificationBus;
|
|
2458
|
+
this.compactionService = options.compactionService;
|
|
2459
|
+
this.pluginStateService = options.pluginStateService;
|
|
1611
2460
|
this.state = {
|
|
1612
2461
|
id: this.id,
|
|
1613
2462
|
model: options.model,
|
|
@@ -1619,9 +2468,21 @@ class Session {
|
|
|
1619
2468
|
reasoning: options.reasoning,
|
|
1620
2469
|
messages: [...options.messages ?? []],
|
|
1621
2470
|
usage: options.usage ? { ...options.usage } : createEmptyUsage(),
|
|
2471
|
+
compaction: cloneCompactionState(options.compaction ?? createEmptyCompactionState()),
|
|
2472
|
+
pluginState: clonePluginStateMap(options.pluginState),
|
|
2473
|
+
compactionOptions: options.compactionOptions,
|
|
1622
2474
|
createdAt: this.createdAt,
|
|
1623
2475
|
updatedAt: options.updatedAt ?? now
|
|
1624
2476
|
};
|
|
2477
|
+
this.compactionService.registerSession(this.state);
|
|
2478
|
+
this.pluginStateService.registerSession(this.state);
|
|
2479
|
+
this.pluginControllers = this.pluginHost?.createSessionControllers({
|
|
2480
|
+
sessionId: this.id,
|
|
2481
|
+
metadata: this.state.metadata,
|
|
2482
|
+
getStatus: () => this.getStatus(),
|
|
2483
|
+
save: () => this.save(),
|
|
2484
|
+
isRunning: () => this.activeRunCount > 0
|
|
2485
|
+
}) ?? new Map;
|
|
1625
2486
|
}
|
|
1626
2487
|
get messages() {
|
|
1627
2488
|
return [...this.state.messages];
|
|
@@ -1629,6 +2490,21 @@ class Session {
|
|
|
1629
2490
|
get usage() {
|
|
1630
2491
|
return { ...this.state.usage };
|
|
1631
2492
|
}
|
|
2493
|
+
getStatus() {
|
|
2494
|
+
return createSessionStatus(this.state);
|
|
2495
|
+
}
|
|
2496
|
+
getCompactionState() {
|
|
2497
|
+
return cloneCompactionState(this.state.compaction);
|
|
2498
|
+
}
|
|
2499
|
+
getPluginState(pluginId) {
|
|
2500
|
+
return clonePluginSessionStateEntry(this.state.pluginState[pluginId]);
|
|
2501
|
+
}
|
|
2502
|
+
listPluginStates() {
|
|
2503
|
+
return clonePluginStateMap(this.state.pluginState);
|
|
2504
|
+
}
|
|
2505
|
+
getPlugin(pluginId) {
|
|
2506
|
+
return this.pluginControllers.get(pluginId) ?? null;
|
|
2507
|
+
}
|
|
1632
2508
|
get updatedAt() {
|
|
1633
2509
|
return this.state.updatedAt;
|
|
1634
2510
|
}
|
|
@@ -1639,20 +2515,76 @@ class Session {
|
|
|
1639
2515
|
this.state.cwd = cwd;
|
|
1640
2516
|
touchRuntimeSessionState(this.state);
|
|
1641
2517
|
}
|
|
2518
|
+
async compact(update) {
|
|
2519
|
+
const beforeState = this.getCompactionState();
|
|
2520
|
+
const estimatedInputTokens = await this.readEstimatedInputTokens();
|
|
2521
|
+
const thresholdTokens = this.state.compactionOptions?.triggerTokens ?? (this.state.compactionOptions ? Math.max(1, Math.floor(this.state.compactionOptions.maxInputTokens * (this.state.compactionOptions.triggerRatio ?? 0.8))) : 0);
|
|
2522
|
+
const maxInputTokens = this.state.compactionOptions?.maxInputTokens ?? 0;
|
|
2523
|
+
const payload = {
|
|
2524
|
+
sessionId: this.id,
|
|
2525
|
+
messages: this.messages,
|
|
2526
|
+
cursor: beforeState.cursor,
|
|
2527
|
+
systemSegments: [...beforeState.systemSegments],
|
|
2528
|
+
contextItems: undefined,
|
|
2529
|
+
estimatedInputTokens,
|
|
2530
|
+
thresholdTokens,
|
|
2531
|
+
maxInputTokens,
|
|
2532
|
+
trigger: "manual"
|
|
2533
|
+
};
|
|
2534
|
+
if (this.pluginHost) {
|
|
2535
|
+
const compactPayload = await this.pluginHost.runMiddleware("context.compact.before", payload, {
|
|
2536
|
+
...this.createHookContext(),
|
|
2537
|
+
services: this.services
|
|
2538
|
+
});
|
|
2539
|
+
await this.pluginHost.runObservers("context.compact.before", compactPayload, {
|
|
2540
|
+
...this.createHookContext(),
|
|
2541
|
+
services: this.services
|
|
2542
|
+
});
|
|
2543
|
+
}
|
|
2544
|
+
await this.compactionService.apply({
|
|
2545
|
+
sessionId: this.id,
|
|
2546
|
+
...update,
|
|
2547
|
+
reason: update.reason ?? "manual"
|
|
2548
|
+
});
|
|
2549
|
+
const afterState = this.getCompactionState();
|
|
2550
|
+
const latestCheckpoint = afterState.checkpoints.at(-1);
|
|
2551
|
+
await this.notificationBus.emit({
|
|
2552
|
+
type: "context.compacted",
|
|
2553
|
+
source: "runtime",
|
|
2554
|
+
level: "info",
|
|
2555
|
+
message: `Context compacted for session ${this.id}`,
|
|
2556
|
+
metadata: {
|
|
2557
|
+
cursor: afterState.cursor,
|
|
2558
|
+
checkpointId: latestCheckpoint?.id,
|
|
2559
|
+
reason: latestCheckpoint?.reason
|
|
2560
|
+
}
|
|
2561
|
+
}, {
|
|
2562
|
+
...this.createHookContext()
|
|
2563
|
+
});
|
|
2564
|
+
}
|
|
1642
2565
|
async save() {
|
|
1643
2566
|
if (!this.stateStore)
|
|
1644
2567
|
return;
|
|
1645
2568
|
let snapshot = this.toSnapshot();
|
|
1646
2569
|
if (this.pluginHost) {
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
cwd: this.state.cwd,
|
|
1650
|
-
metadata: this.state.metadata,
|
|
2570
|
+
snapshot = await this.pluginHost.runMiddleware("snapshot.encode", { snapshot }, {
|
|
2571
|
+
...this.createHookContext(),
|
|
1651
2572
|
services: this.services
|
|
1652
|
-
});
|
|
1653
|
-
snapshot = payload.snapshot;
|
|
2573
|
+
}).then((payload) => payload.snapshot);
|
|
1654
2574
|
}
|
|
1655
2575
|
await this.stateStore.save(snapshot);
|
|
2576
|
+
await this.notificationBus.emit({
|
|
2577
|
+
type: "snapshot.saved",
|
|
2578
|
+
source: "persistence",
|
|
2579
|
+
level: "info",
|
|
2580
|
+
message: `Saved snapshot for session ${this.id}`,
|
|
2581
|
+
metadata: {
|
|
2582
|
+
sessionId: this.id,
|
|
2583
|
+
schemaVersion: snapshot.schemaVersion
|
|
2584
|
+
}
|
|
2585
|
+
}, {
|
|
2586
|
+
...this.createHookContext()
|
|
2587
|
+
});
|
|
1656
2588
|
}
|
|
1657
2589
|
toSnapshot() {
|
|
1658
2590
|
return sessionSnapshotCodec.encode({
|
|
@@ -1661,6 +2593,8 @@ class Session {
|
|
|
1661
2593
|
cwd: this.state.cwd,
|
|
1662
2594
|
messages: this.messages,
|
|
1663
2595
|
usage: this.usage,
|
|
2596
|
+
compaction: this.getCompactionState(),
|
|
2597
|
+
pluginState: this.listPluginStates(),
|
|
1664
2598
|
createdAt: this.createdAt,
|
|
1665
2599
|
updatedAt: this.updatedAt,
|
|
1666
2600
|
metadata: this.state.metadata ? { ...this.state.metadata } : undefined
|
|
@@ -1682,12 +2616,17 @@ class Session {
|
|
|
1682
2616
|
}
|
|
1683
2617
|
async* stream(input, options) {
|
|
1684
2618
|
const runner = this.createLoopRunner();
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
2619
|
+
this.activeRunCount += 1;
|
|
2620
|
+
try {
|
|
2621
|
+
return yield* runner.run(this.state, input, {
|
|
2622
|
+
signal: options?.signal,
|
|
2623
|
+
onDone: async () => {
|
|
2624
|
+
await this.save();
|
|
2625
|
+
}
|
|
2626
|
+
});
|
|
2627
|
+
} finally {
|
|
2628
|
+
this.activeRunCount = Math.max(0, this.activeRunCount - 1);
|
|
2629
|
+
}
|
|
1691
2630
|
}
|
|
1692
2631
|
createLoopRunner() {
|
|
1693
2632
|
return new LoopRunner({
|
|
@@ -1698,13 +2637,43 @@ class Session {
|
|
|
1698
2637
|
tools: this.state.tools,
|
|
1699
2638
|
metadata: this.state.metadata,
|
|
1700
2639
|
services: this.services,
|
|
1701
|
-
pluginHost: this.pluginHost
|
|
2640
|
+
pluginHost: this.pluginHost,
|
|
2641
|
+
approvalManager: this.approvalManager,
|
|
2642
|
+
notificationBus: this.notificationBus
|
|
1702
2643
|
}),
|
|
1703
2644
|
terminationPolicy: new DefaultTerminationPolicy,
|
|
1704
2645
|
contextManager: this.contextManager,
|
|
1705
|
-
pluginHost: this.pluginHost
|
|
2646
|
+
pluginHost: this.pluginHost,
|
|
2647
|
+
notificationBus: this.notificationBus
|
|
1706
2648
|
});
|
|
1707
2649
|
}
|
|
2650
|
+
async readEstimatedInputTokens() {
|
|
2651
|
+
if (!this.state.compactionOptions || this.state.compactionOptions.enabled === false)
|
|
2652
|
+
return 0;
|
|
2653
|
+
const projectedMessages = projectMessagesForRequest(this.state.messages, this.state.compaction);
|
|
2654
|
+
const promptSegments = buildCompactionPromptSegments(this.state.compaction);
|
|
2655
|
+
const requestMessages = [...projectedMessages];
|
|
2656
|
+
if (promptSegments.length > 0)
|
|
2657
|
+
requestMessages.unshift(new DefaultMessageFactory().createSystemMessage(promptSegments.join(`
|
|
2658
|
+
|
|
2659
|
+
`)));
|
|
2660
|
+
const budget = await estimateCompactionBudget(this.state.compactionOptions, {
|
|
2661
|
+
model: this.state.modelRef,
|
|
2662
|
+
messages: requestMessages,
|
|
2663
|
+
tools: this.state.tools?.definitions() ?? []
|
|
2664
|
+
});
|
|
2665
|
+
return budget.estimatedInputTokens;
|
|
2666
|
+
}
|
|
2667
|
+
createHookContext(input = {}) {
|
|
2668
|
+
return {
|
|
2669
|
+
sessionId: this.id,
|
|
2670
|
+
requestId: input.requestId,
|
|
2671
|
+
iteration: input.iteration,
|
|
2672
|
+
cwd: this.state.cwd,
|
|
2673
|
+
metadata: this.state.metadata,
|
|
2674
|
+
status: this.getStatus()
|
|
2675
|
+
};
|
|
2676
|
+
}
|
|
1708
2677
|
}
|
|
1709
2678
|
|
|
1710
2679
|
// src/agent-core/Agent.ts
|
|
@@ -1719,13 +2688,23 @@ class Agent {
|
|
|
1719
2688
|
contextManager;
|
|
1720
2689
|
tracker;
|
|
1721
2690
|
permissionGateway;
|
|
2691
|
+
approvalManager;
|
|
2692
|
+
notificationBus;
|
|
2693
|
+
compactionService;
|
|
2694
|
+
pluginStateService;
|
|
1722
2695
|
constructor(options) {
|
|
1723
2696
|
this.id = createId("agent");
|
|
1724
2697
|
this.options = options;
|
|
1725
2698
|
this.permissions = options.permissions;
|
|
2699
|
+
validateCompactionOwner(options);
|
|
1726
2700
|
this.tracker = new ActivityTracker;
|
|
1727
|
-
this.
|
|
1728
|
-
this.
|
|
2701
|
+
this.compactionService = new DefaultCompactionService;
|
|
2702
|
+
this.pluginStateService = new DefaultPluginStateService;
|
|
2703
|
+
this.services = createServices(options, this.tracker, this.compactionService, this.pluginStateService);
|
|
2704
|
+
this.permissionGateway = new PermissionGateway({
|
|
2705
|
+
permissions: options.permissions,
|
|
2706
|
+
compactionOwnerPluginId: options.compaction?.ownerPluginId
|
|
2707
|
+
});
|
|
1729
2708
|
this.pluginHost = new PluginHost({
|
|
1730
2709
|
agentId: this.id,
|
|
1731
2710
|
cwd: options.cwd,
|
|
@@ -1734,6 +2713,14 @@ class Agent {
|
|
|
1734
2713
|
permissionGateway: this.permissionGateway,
|
|
1735
2714
|
tracker: this.tracker
|
|
1736
2715
|
});
|
|
2716
|
+
this.notificationBus = new DefaultNotificationBus({
|
|
2717
|
+
pluginHost: this.pluginHost,
|
|
2718
|
+
services: this.services
|
|
2719
|
+
});
|
|
2720
|
+
this.approvalManager = new DefaultApprovalManager({
|
|
2721
|
+
handler: options.approvalHandler,
|
|
2722
|
+
notifications: this.notificationBus
|
|
2723
|
+
});
|
|
1737
2724
|
this.contextManager = options.contextManager ?? new AutoContextManager({
|
|
1738
2725
|
services: this.services,
|
|
1739
2726
|
pluginHost: this.pluginHost
|
|
@@ -1758,10 +2745,15 @@ class Agent {
|
|
|
1758
2745
|
cwd: options.cwd ?? this.options.cwd,
|
|
1759
2746
|
metadata: options.metadata ?? this.options.metadata,
|
|
1760
2747
|
reasoning: this.options.reasoning,
|
|
2748
|
+
compactionOptions: this.options.compaction,
|
|
1761
2749
|
messages,
|
|
1762
2750
|
pluginHost: this.pluginHost,
|
|
1763
2751
|
contextManager: this.contextManager,
|
|
1764
|
-
services: this.services
|
|
2752
|
+
services: this.services,
|
|
2753
|
+
approvalManager: this.approvalManager,
|
|
2754
|
+
notificationBus: this.notificationBus,
|
|
2755
|
+
compactionService: this.compactionService,
|
|
2756
|
+
pluginStateService: this.pluginStateService
|
|
1765
2757
|
});
|
|
1766
2758
|
}
|
|
1767
2759
|
async restoreSession(sessionId) {
|
|
@@ -1785,27 +2777,44 @@ class Agent {
|
|
|
1785
2777
|
reasoning: this.options.reasoning,
|
|
1786
2778
|
messages: snapshot.messages,
|
|
1787
2779
|
usage: snapshot.usage,
|
|
2780
|
+
compaction: snapshot.compaction,
|
|
2781
|
+
pluginState: snapshot.pluginState,
|
|
2782
|
+
compactionOptions: this.options.compaction,
|
|
1788
2783
|
createdAt: snapshot.createdAt,
|
|
1789
2784
|
updatedAt: snapshot.updatedAt,
|
|
1790
2785
|
pluginHost: this.pluginHost,
|
|
1791
2786
|
contextManager: this.contextManager,
|
|
1792
|
-
services: this.services
|
|
2787
|
+
services: this.services,
|
|
2788
|
+
approvalManager: this.approvalManager,
|
|
2789
|
+
notificationBus: this.notificationBus,
|
|
2790
|
+
compactionService: this.compactionService,
|
|
2791
|
+
pluginStateService: this.pluginStateService
|
|
1793
2792
|
});
|
|
1794
2793
|
}
|
|
1795
2794
|
}
|
|
1796
2795
|
function createAgent(options) {
|
|
1797
2796
|
return new Agent(options);
|
|
1798
2797
|
}
|
|
1799
|
-
function createServices(options, tracker) {
|
|
2798
|
+
function createServices(options, tracker, compaction, pluginState) {
|
|
1800
2799
|
const exec = new NodeExecGateway({ cwd: options.cwd, tracker });
|
|
1801
2800
|
return {
|
|
1802
2801
|
fileSystem: new NodeFileSystemGateway({ cwd: options.cwd, tracker }),
|
|
1803
2802
|
exec,
|
|
1804
2803
|
git: new DefaultGitGateway({ exec, cwd: options.cwd }),
|
|
1805
2804
|
network: new DefaultNetworkGateway,
|
|
1806
|
-
model: new DefaultModelGateway(options.model)
|
|
2805
|
+
model: new DefaultModelGateway(options.model),
|
|
2806
|
+
compaction,
|
|
2807
|
+
pluginState
|
|
1807
2808
|
};
|
|
1808
2809
|
}
|
|
2810
|
+
function validateCompactionOwner(options) {
|
|
2811
|
+
const ownerPluginId = options.compaction?.ownerPluginId;
|
|
2812
|
+
if (!ownerPluginId)
|
|
2813
|
+
return;
|
|
2814
|
+
const loadedPluginIds = new Set((options.plugins ?? []).map((plugin) => plugin.manifest.id));
|
|
2815
|
+
if (!loadedPluginIds.has(ownerPluginId))
|
|
2816
|
+
throw new Error(`Compaction owner plugin "${ownerPluginId}" is not loaded`);
|
|
2817
|
+
}
|
|
1809
2818
|
function createToolRegistry(input) {
|
|
1810
2819
|
const registry = input.tools instanceof ToolRegistry ? cloneRegistry(input.tools) : new ToolRegistry;
|
|
1811
2820
|
if (input.includeBuiltinTools) {
|
|
@@ -2585,16 +3594,25 @@ function imageDataUrl(block) {
|
|
|
2585
3594
|
return `data:${block.mimeType};base64,${block.data}`;
|
|
2586
3595
|
}
|
|
2587
3596
|
function messagesToOpenAI(messages) {
|
|
2588
|
-
|
|
3597
|
+
const systemSegments = [];
|
|
3598
|
+
const mappedMessages = [];
|
|
3599
|
+
for (const message of messages) {
|
|
3600
|
+
if (message.role === "system") {
|
|
3601
|
+
const text = contentToText(message.content);
|
|
3602
|
+
if (text)
|
|
3603
|
+
systemSegments.push(text);
|
|
3604
|
+
continue;
|
|
3605
|
+
}
|
|
2589
3606
|
if (message.role === "tool") {
|
|
2590
|
-
|
|
3607
|
+
mappedMessages.push({
|
|
2591
3608
|
role: "tool",
|
|
2592
3609
|
tool_call_id: message.toolCallId,
|
|
2593
3610
|
content: message.content.length > 0 ? contentToText(message.content) : JSON.stringify(message.structuredContent ?? {})
|
|
2594
|
-
};
|
|
3611
|
+
});
|
|
3612
|
+
continue;
|
|
2595
3613
|
}
|
|
2596
3614
|
if (message.role === "assistant") {
|
|
2597
|
-
|
|
3615
|
+
mappedMessages.push({
|
|
2598
3616
|
role: "assistant",
|
|
2599
3617
|
content: contentToText(message.content),
|
|
2600
3618
|
tool_calls: message.toolCalls?.map((toolCall) => ({
|
|
@@ -2605,14 +3623,23 @@ function messagesToOpenAI(messages) {
|
|
|
2605
3623
|
arguments: JSON.stringify(toolCall.function.arguments)
|
|
2606
3624
|
}
|
|
2607
3625
|
}))
|
|
2608
|
-
};
|
|
3626
|
+
});
|
|
3627
|
+
continue;
|
|
2609
3628
|
}
|
|
2610
|
-
|
|
3629
|
+
mappedMessages.push({
|
|
2611
3630
|
role: message.role,
|
|
2612
3631
|
content: messageContentToOpenAI(message.content),
|
|
2613
3632
|
...message.role === "user" && message.name ? { name: message.name } : {}
|
|
2614
|
-
};
|
|
2615
|
-
}
|
|
3633
|
+
});
|
|
3634
|
+
}
|
|
3635
|
+
if (systemSegments.length === 0)
|
|
3636
|
+
return mappedMessages;
|
|
3637
|
+
return [{
|
|
3638
|
+
role: "system",
|
|
3639
|
+
content: systemSegments.join(`
|
|
3640
|
+
|
|
3641
|
+
`)
|
|
3642
|
+
}, ...mappedMessages];
|
|
2616
3643
|
}
|
|
2617
3644
|
function normalizeOpenAIToolCalls(value) {
|
|
2618
3645
|
if (!value)
|
|
@@ -2990,6 +4017,7 @@ export {
|
|
|
2990
4017
|
DefaultNetworkGateway,
|
|
2991
4018
|
DefaultModelGateway,
|
|
2992
4019
|
DefaultGitGateway,
|
|
4020
|
+
DefaultCompactionService,
|
|
2993
4021
|
BaseTool,
|
|
2994
4022
|
AutoContextManager,
|
|
2995
4023
|
Agent,
|