@schlenkr/ragents 0.1.6 → 0.1.8
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/apps/server/src/chat-handler.ts +2 -0
- package/apps/server/src/config.ts +6 -0
- package/apps/server/src/host-api.json +1 -0
- package/apps/server/src/layout.ts +0 -15
- package/apps/server/src/plugin-support/agent-tool.ts +2 -3
- package/apps/server/src/plugin-support/plugin-folder.ts +2 -1
- package/apps/server/src/provider.ts +34 -11
- package/apps/server/src/ragents/actor-chat-history.ts +1 -0
- package/apps/server/src/ragents/engine.ts +23 -24
- package/apps/server/src/ragents/journal-chat-events.ts +2 -0
- package/apps/server/src/ragents/run-owner.ts +3 -2
- package/apps/server/src/ragents/session.ts +3 -3
- package/apps/server/src/run-preparation.ts +14 -18
- package/apps/server/src/run-transfer.ts +65 -4
- package/apps/server/src/settings.ts +2 -4
- package/apps/web/dist/assets/{highlighted-body-KPVGNVTW-B9qYBARS.js → highlighted-body-KPVGNVTW-xXXhTDbh.js} +1 -1
- package/apps/web/dist/assets/{main-BCjvD2Hh.js → main-C7JeZFjs.js} +1 -1
- package/apps/web/dist/assets/{mermaid-HWGCJPDP-CvYYFK_n.js → mermaid-HWGCJPDP-DfsU5ofC.js} +69 -69
- package/apps/web/dist/assets/{runPanel-CG7kRMJh.js → runPanel-ZT6TPDLM.js} +1 -1
- package/apps/web/dist/help/guide-clients.html +13 -4
- package/apps/web/dist/help/guide-clients.md +14 -4
- package/apps/web/dist/help/guide-distributed.html +8 -3
- package/apps/web/dist/help/guide-distributed.md +8 -3
- package/apps/web/dist/help/guide-runtime.html +18 -12
- package/apps/web/dist/help/guide-runtime.md +19 -12
- package/apps/web/dist/help/mini-app.css +23 -3
- package/apps/web/dist/host-web.json +4 -4
- package/apps/web/dist/index.html +2 -2
- package/apps/web/dist/run-panel.html +2 -2
- package/apps/web/src/SessionList.tsx +16 -8
- package/apps/web/src/StartTiles.tsx +1 -1
- package/apps/web/src/api.ts +2 -0
- package/apps/web/src/panel/PanelPage.tsx +1 -1
- package/apps/web/src/panel/RunLine.tsx +4 -2
- package/apps/web/src/panel/contract.ts +2 -0
- package/bundles/ragents.activity/ragents-bundle.json +1 -1
- package/bundles/ragents.actor-programs/ragents-bundle.json +1 -1
- package/bundles/ragents.ask/ragents-bundle.json +1 -1
- package/bundles/ragents.browser/ragents-bundle.json +1 -1
- package/bundles/ragents.documents/ragents-bundle.json +1 -1
- package/bundles/ragents.lsp-fsharp/ragents-bundle.json +1 -1
- package/bundles/ragents.lsp-roslyn/ragents-bundle.json +1 -1
- package/bundles/ragents.lsp-typescript/ragents-bundle.json +1 -1
- package/bundles/ragents.model-relay/ragents-bundle.json +1 -1
- package/bundles/ragents.orchestration/ragents-bundle.json +1 -1
- package/bundles/ragents.overseer/ragents-bundle.json +1 -1
- package/bundles/ragents.processes/ragents-bundle.json +1 -1
- package/bundles/ragents.product/ragents-bundle.json +1 -1
- package/bundles/ragents.profile-distribution/ragents-bundle.json +1 -1
- package/bundles/ragents.reference/ragents-bundle.json +2 -2
- package/bundles/ragents.reference/run-scripts/learning-afternoon/tests/program.test.ts +1 -1
- package/bundles/ragents.reference/run-scripts/word-game/tests/program.test.ts +3 -3
- package/bundles/ragents.todo/ragents-bundle.json +1 -1
- package/bundles/ragents.transcript/ragents-bundle.json +1 -1
- package/bundles/ragents.watch/ragents-bundle.json +1 -1
- package/bundles/ragents.workspace/ragents-bundle.json +1 -1
- package/package.json +2 -2
- package/packages/agent/dist/core/agent-settings.d.ts +2 -2
- package/packages/agent/dist/core/compaction/compaction.d.ts +16 -50
- package/packages/agent/dist/core/context-log.d.ts +25 -0
- package/packages/agent/dist/core/messages.d.ts +3 -10
- package/packages/agent/dist/core/tool-definition.d.ts +17 -0
- package/packages/agent/dist/core/tools/bash.d.ts +2 -7
- package/packages/agent/dist/core/tools/edit.d.ts +1 -1
- package/packages/agent/dist/core/tools/read.d.ts +1 -1
- package/packages/agent/dist/core/tools/write.d.ts +1 -1
- package/packages/agent/dist/index.d.ts +7 -8
- package/packages/agent/dist/loop/agent.d.ts +2 -6
- package/packages/agent/package.json +1 -1
- package/packages/agent/src/core/agent-settings.ts +2 -2
- package/packages/agent/src/core/compaction/compaction.ts +49 -175
- package/packages/agent/src/core/context-log.ts +49 -0
- package/packages/agent/src/core/messages.ts +5 -11
- package/packages/agent/src/core/tool-definition.ts +29 -0
- package/packages/agent/src/core/tools/bash.ts +2 -9
- package/packages/agent/src/core/tools/edit.ts +2 -11
- package/packages/agent/src/core/tools/read.ts +3 -18
- package/packages/agent/src/core/tools/write.ts +1 -5
- package/packages/agent/src/index.ts +28 -14
- package/packages/agent/src/loop/agent-loop.ts +1 -2
- package/packages/agent/src/loop/agent.ts +8 -2
- package/packages/ai/dist/api/ai-sdk.d.ts +2 -0
- package/packages/ai/dist/types.d.ts +2 -0
- package/packages/ai/src/api/ai-sdk-messages.ts +15 -10
- package/packages/ai/src/api/ai-sdk.ts +11 -0
- package/packages/ai/src/api/transform-messages.ts +1 -1
- package/packages/ai/src/types.ts +2 -0
- package/packages/ai/src/utils/retry.ts +14 -0
- package/packages/ragents/src/agents/actor-input.ts +15 -0
- package/packages/ragents/src/agents/model-context.ts +360 -0
- package/packages/ragents/src/agents/plugins.ts +5 -0
- package/packages/ragents/src/agents/scheduler.ts +3 -7
- package/packages/ragents/src/agents/tools.ts +2 -2
- package/packages/ragents/src/agents/toolset.ts +47 -20
- package/packages/ragents/src/agents/workspaces.ts +0 -6
- package/packages/ragents/src/domain/event-semantics.ts +51 -0
- package/packages/ragents/src/domain/event-validation.ts +166 -0
- package/packages/ragents/src/domain/events.ts +72 -0
- package/packages/ragents/src/domain/model.ts +4 -2
- package/packages/ragents/src/domain/projection.ts +9 -0
- package/packages/ragents/src/domain/tool-result-text.ts +9 -0
- package/packages/ragents/src/drivers/agent-hooks.ts +25 -38
- package/packages/ragents/src/drivers/agent-runtime.ts +118 -933
- package/packages/ragents/src/drivers/agent-turn.ts +735 -0
- package/packages/ragents/src/drivers/agent.ts +9 -7
- package/packages/ragents/src/drivers/skill-preload.ts +53 -63
- package/packages/ragents/src/drivers/types.ts +21 -5
- package/packages/ragents/src/index.ts +8 -6
- package/packages/ragents/src/plugin-host.ts +60 -13
- package/packages/ragents/src/plugin-types.ts +7 -2
- package/packages/ragents/src/runtime/decisions/actors.ts +10 -0
- package/packages/ragents/src/runtime/decisions/turns.ts +66 -7
- package/packages/ragents/src/runtime/journal-storage.ts +7 -4
- package/packages/ragents/src/runtime/journal.ts +35 -0
- package/packages/ragents/src/runtime/orchestration.ts +140 -9
- package/packages/workspace-executor/src/sandbox-tools.ts +10 -11
- package/plugins/ragents.reference/run-scripts/learning-afternoon/tests/program.test.ts +1 -1
- package/plugins/ragents.reference/run-scripts/word-game/tests/program.test.ts +3 -3
- package/scripts/agent/journal.ts +5 -3
- package/scripts/package/publish-package.ts +4 -3
- package/packages/agent/dist/core/agent-session-runtime.d.ts +0 -33
- package/packages/agent/dist/core/agent-session-services.d.ts +0 -35
- package/packages/agent/dist/core/agent-session.d.ts +0 -234
- package/packages/agent/dist/core/auth-guidance.d.ts +0 -4
- package/packages/agent/dist/core/defaults.d.ts +0 -3
- package/packages/agent/dist/core/extensions/index.d.ts +0 -11
- package/packages/agent/dist/core/extensions/loader.d.ts +0 -12
- package/packages/agent/dist/core/extensions/runner.d.ts +0 -39
- package/packages/agent/dist/core/extensions/types.d.ts +0 -156
- package/packages/agent/dist/core/extensions/wrapper.d.ts +0 -12
- package/packages/agent/dist/core/resource-loader.d.ts +0 -39
- package/packages/agent/dist/core/sdk.d.ts +0 -38
- package/packages/agent/dist/core/session-manager.d.ts +0 -189
- package/packages/agent/dist/core/source-info.d.ts +0 -16
- package/packages/agent/dist/core/system-prompt.d.ts +0 -21
- package/packages/agent/src/core/agent-session-runtime.ts +0 -94
- package/packages/agent/src/core/agent-session-services.ts +0 -74
- package/packages/agent/src/core/agent-session.ts +0 -1108
- package/packages/agent/src/core/auth-guidance.ts +0 -14
- package/packages/agent/src/core/defaults.ts +0 -3
- package/packages/agent/src/core/extensions/index.ts +0 -38
- package/packages/agent/src/core/extensions/loader.ts +0 -125
- package/packages/agent/src/core/extensions/runner.ts +0 -211
- package/packages/agent/src/core/extensions/types.ts +0 -197
- package/packages/agent/src/core/extensions/wrapper.ts +0 -24
- package/packages/agent/src/core/resource-loader.ts +0 -142
- package/packages/agent/src/core/sdk.ts +0 -123
- package/packages/agent/src/core/session-manager.ts +0 -743
- package/packages/agent/src/core/source-info.ts +0 -28
- package/packages/agent/src/core/system-prompt.ts +0 -108
|
@@ -1,20 +1,10 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Context compaction for long sessions.
|
|
3
|
-
*
|
|
4
|
-
* Pure functions for compaction logic. The session manager handles I/O,
|
|
5
|
-
* and after compaction the session is reloaded.
|
|
6
|
-
*/
|
|
1
|
+
/** Context compaction over the context log of one agent; pure functions, the caller persists the result. */
|
|
7
2
|
|
|
8
3
|
import type { AgentMessage, StreamFn, ThinkingLevel } from "../../loop/index.ts";
|
|
9
4
|
import type { AssistantMessage, Context, Model, SimpleStreamOptions, Usage } from "@ragents/ai";
|
|
10
5
|
import { completeSimple } from "@ragents/ai";
|
|
6
|
+
import { type ContextCompaction, type ContextLogEntry, contextEntryMessages, contextMessages } from "../context-log.ts";
|
|
11
7
|
import { convertToLlm } from "../messages.ts";
|
|
12
|
-
import {
|
|
13
|
-
buildSessionContext,
|
|
14
|
-
type CompactionEntry,
|
|
15
|
-
type SessionEntry,
|
|
16
|
-
sessionEntryToContextMessages,
|
|
17
|
-
} from "../session-manager.ts";
|
|
18
8
|
import {
|
|
19
9
|
computeFileLists,
|
|
20
10
|
createFileOps,
|
|
@@ -26,72 +16,29 @@ import {
|
|
|
26
16
|
collectUserAttachments,
|
|
27
17
|
} from "./utils.ts";
|
|
28
18
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
// ============================================================================
|
|
32
|
-
|
|
33
|
-
/** Details stored in CompactionEntry.details for file tracking */
|
|
34
|
-
export interface CompactionDetails {
|
|
35
|
-
readFiles: string[];
|
|
36
|
-
modifiedFiles: string[];
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Extract file operations from messages and previous compaction entries.
|
|
41
|
-
*/
|
|
42
|
-
function extractFileOperations(
|
|
43
|
-
messages: AgentMessage[],
|
|
44
|
-
entries: SessionEntry[],
|
|
45
|
-
prevCompactionIndex: number,
|
|
46
|
-
): FileOperations {
|
|
19
|
+
/** Earlier compaction lists plus the file operations of the summarized messages. */
|
|
20
|
+
function extractFileOperations(messages: AgentMessage[], previous: ContextCompaction | undefined): FileOperations {
|
|
47
21
|
const fileOps = createFileOps();
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (prevCompactionIndex >= 0) {
|
|
51
|
-
const prevCompaction = entries[prevCompactionIndex] as CompactionEntry;
|
|
52
|
-
if (!prevCompaction.fromHook && prevCompaction.details) {
|
|
53
|
-
// fromHook field kept for session file compatibility
|
|
54
|
-
const details = prevCompaction.details as CompactionDetails;
|
|
55
|
-
if (Array.isArray(details.readFiles)) {
|
|
56
|
-
for (const f of details.readFiles) fileOps.read.add(f);
|
|
57
|
-
}
|
|
58
|
-
if (Array.isArray(details.modifiedFiles)) {
|
|
59
|
-
for (const f of details.modifiedFiles) fileOps.edited.add(f);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Extract from tool calls in messages
|
|
22
|
+
for (const file of previous?.readFiles ?? []) fileOps.read.add(file);
|
|
23
|
+
for (const file of previous?.modifiedFiles ?? []) fileOps.edited.add(file);
|
|
65
24
|
for (const msg of messages) {
|
|
66
25
|
extractFileOpsFromMessage(msg, fileOps);
|
|
67
26
|
}
|
|
68
|
-
|
|
69
27
|
return fileOps;
|
|
70
28
|
}
|
|
71
29
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
// ============================================================================
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Extract AgentMessage from an entry if it produces one.
|
|
78
|
-
* Returns undefined for entries that don't contribute to LLM context.
|
|
79
|
-
*/
|
|
80
|
-
function getMessageFromEntryForCompaction(entry: SessionEntry): AgentMessage | undefined {
|
|
81
|
-
if (entry.type === "compaction") {
|
|
82
|
-
return undefined;
|
|
83
|
-
}
|
|
84
|
-
return sessionEntryToContextMessages(entry)[0];
|
|
30
|
+
function messageOf(entry: ContextLogEntry): AgentMessage | undefined {
|
|
31
|
+
return entry.kind === "message" ? entry.message : undefined;
|
|
85
32
|
}
|
|
86
33
|
|
|
87
|
-
/** Result
|
|
88
|
-
export interface CompactionResult
|
|
34
|
+
/** Result of compact(); the caller records it as a compaction entry. */
|
|
35
|
+
export interface CompactionResult {
|
|
89
36
|
summary: string;
|
|
90
|
-
|
|
37
|
+
firstKeptId: string;
|
|
91
38
|
tokensBefore: number;
|
|
92
39
|
estimatedTokensAfter?: number;
|
|
93
|
-
|
|
94
|
-
|
|
40
|
+
readFiles: string[];
|
|
41
|
+
modifiedFiles: string[];
|
|
95
42
|
}
|
|
96
43
|
|
|
97
44
|
// ============================================================================
|
|
@@ -283,38 +230,24 @@ function isTurnStartMessage(message: AgentMessage): boolean {
|
|
|
283
230
|
return false;
|
|
284
231
|
}
|
|
285
232
|
|
|
286
|
-
function isTurnStartEntry(entry:
|
|
287
|
-
|
|
288
|
-
return false;
|
|
289
|
-
}
|
|
290
|
-
return sessionEntryToContextMessages(entry).some(isTurnStartMessage);
|
|
233
|
+
function isTurnStartEntry(entry: ContextLogEntry): boolean {
|
|
234
|
+
return entry.kind === "message" && isTurnStartMessage(entry.message);
|
|
291
235
|
}
|
|
292
236
|
|
|
293
|
-
/**
|
|
294
|
-
|
|
295
|
-
* Never cut at tool results (they must follow their tool call).
|
|
296
|
-
* When we cut at an assistant message with tool calls, its tool results follow it
|
|
297
|
-
* and will be kept.
|
|
298
|
-
*/
|
|
299
|
-
function findValidCutPoints(entries: SessionEntry[], startIndex: number, endIndex: number): number[] {
|
|
237
|
+
/** User-like or assistant messages, never a tool result, since it must follow its call. */
|
|
238
|
+
function findValidCutPoints(entries: ContextLogEntry[], startIndex: number, endIndex: number): number[] {
|
|
300
239
|
const cutPoints: number[] = [];
|
|
301
240
|
for (let i = startIndex; i < endIndex; i++) {
|
|
302
241
|
const entry = entries[i];
|
|
303
|
-
if (entry.
|
|
304
|
-
continue;
|
|
305
|
-
}
|
|
306
|
-
if (sessionEntryToContextMessages(entry).some(isCutPointMessage)) {
|
|
242
|
+
if (entry.kind === "message" && isCutPointMessage(entry.message)) {
|
|
307
243
|
cutPoints.push(i);
|
|
308
244
|
}
|
|
309
245
|
}
|
|
310
246
|
return cutPoints;
|
|
311
247
|
}
|
|
312
248
|
|
|
313
|
-
/**
|
|
314
|
-
|
|
315
|
-
* Returns -1 if no turn start found before the index.
|
|
316
|
-
*/
|
|
317
|
-
export function findTurnStartIndex(entries: SessionEntry[], entryIndex: number, startIndex: number): number {
|
|
249
|
+
/** The user-like message that starts the turn containing the given index, or -1. */
|
|
250
|
+
export function findTurnStartIndex(entries: ContextLogEntry[], entryIndex: number, startIndex: number): number {
|
|
318
251
|
for (let i = entryIndex; i >= startIndex; i--) {
|
|
319
252
|
if (isTurnStartEntry(entries[i])) {
|
|
320
253
|
return i;
|
|
@@ -332,24 +265,9 @@ export interface CutPointResult {
|
|
|
332
265
|
isSplitTurn: boolean;
|
|
333
266
|
}
|
|
334
267
|
|
|
335
|
-
/**
|
|
336
|
-
* Find the cut point in session entries that keeps approximately `keepRecentTokens`.
|
|
337
|
-
*
|
|
338
|
-
* Algorithm: Walk backwards from newest, accumulating estimated message sizes.
|
|
339
|
-
* Stop when we've accumulated >= keepRecentTokens. Cut at that point.
|
|
340
|
-
*
|
|
341
|
-
* Can cut at user OR assistant messages (never tool results). When cutting at an
|
|
342
|
-
* assistant message with tool calls, its tool results come after and will be kept.
|
|
343
|
-
*
|
|
344
|
-
* Returns CutPointResult with:
|
|
345
|
-
* - firstKeptEntryIndex: the entry index to start keeping from
|
|
346
|
-
* - turnStartIndex: if cutting mid-turn, the user message that started that turn
|
|
347
|
-
* - isSplitTurn: whether we're cutting in the middle of a turn
|
|
348
|
-
*
|
|
349
|
-
* Only considers entries between `startIndex` and `endIndex` (exclusive).
|
|
350
|
-
*/
|
|
268
|
+
/** The valid cut point that keeps about `keepRecentTokens` of the entries between `startIndex` and `endIndex`, counted from the newest. */
|
|
351
269
|
export function findCutPoint(
|
|
352
|
-
entries:
|
|
270
|
+
entries: ContextLogEntry[],
|
|
353
271
|
startIndex: number,
|
|
354
272
|
endIndex: number,
|
|
355
273
|
keepRecentTokens: number,
|
|
@@ -360,22 +278,15 @@ export function findCutPoint(
|
|
|
360
278
|
return { firstKeptEntryIndex: startIndex, turnStartIndex: -1, isSplitTurn: false };
|
|
361
279
|
}
|
|
362
280
|
|
|
363
|
-
// Walk backwards from newest, accumulating estimated message sizes
|
|
364
281
|
let accumulatedTokens = 0;
|
|
365
|
-
let cutIndex = cutPoints[0];
|
|
282
|
+
let cutIndex = cutPoints[0];
|
|
366
283
|
|
|
367
284
|
for (let i = endIndex - 1; i >= startIndex; i--) {
|
|
368
|
-
const
|
|
369
|
-
const messageTokens = sessionEntryToContextMessages(entry).reduce(
|
|
370
|
-
(sum, message) => sum + estimateTokens(message),
|
|
371
|
-
0,
|
|
372
|
-
);
|
|
285
|
+
const messageTokens = contextEntryMessages(entries[i]).reduce((sum, message) => sum + estimateTokens(message), 0);
|
|
373
286
|
if (messageTokens === 0) continue;
|
|
374
287
|
accumulatedTokens += messageTokens;
|
|
375
288
|
|
|
376
|
-
// Check if we've exceeded the budget
|
|
377
289
|
if (accumulatedTokens >= keepRecentTokens) {
|
|
378
|
-
// Find the closest valid cut point at or after this entry
|
|
379
290
|
for (let c = 0; c < cutPoints.length; c++) {
|
|
380
291
|
if (cutPoints[c] >= i) {
|
|
381
292
|
cutIndex = cutPoints[c];
|
|
@@ -386,17 +297,6 @@ export function findCutPoint(
|
|
|
386
297
|
}
|
|
387
298
|
}
|
|
388
299
|
|
|
389
|
-
// Scan backwards from cutIndex to include adjacent metadata entries that do not affect context.
|
|
390
|
-
while (cutIndex > startIndex) {
|
|
391
|
-
const prevEntry = entries[cutIndex - 1];
|
|
392
|
-
// Stop at compaction boundaries or context-visible entries.
|
|
393
|
-
if (prevEntry.type === "compaction" || sessionEntryToContextMessages(prevEntry).length > 0) {
|
|
394
|
-
break;
|
|
395
|
-
}
|
|
396
|
-
cutIndex--;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
// Determine if this is a split turn
|
|
400
300
|
const cutEntry = entries[cutIndex];
|
|
401
301
|
const startsTurn = isTurnStartEntry(cutEntry);
|
|
402
302
|
const turnStartIndex = startsTurn ? -1 : findTurnStartIndex(entries, cutIndex, startIndex);
|
|
@@ -583,12 +483,12 @@ export async function generateSummary(
|
|
|
583
483
|
}
|
|
584
484
|
|
|
585
485
|
// ============================================================================
|
|
586
|
-
// Compaction Preparation
|
|
486
|
+
// Compaction Preparation
|
|
587
487
|
// ============================================================================
|
|
588
488
|
|
|
589
489
|
export interface CompactionPreparation {
|
|
590
|
-
/**
|
|
591
|
-
|
|
490
|
+
/** Id of the first entry to keep */
|
|
491
|
+
firstKeptId: string;
|
|
592
492
|
/** Messages that will be summarized and discarded */
|
|
593
493
|
messagesToSummarize: AgentMessage[];
|
|
594
494
|
/** Messages that will be turned into turn prefix summary (if splitting) */
|
|
@@ -600,61 +500,54 @@ export interface CompactionPreparation {
|
|
|
600
500
|
previousSummary?: string;
|
|
601
501
|
/** File operations extracted from messagesToSummarize */
|
|
602
502
|
fileOps: FileOperations;
|
|
603
|
-
/** Compaction settions from settings.jsonl */
|
|
604
503
|
settings: CompactionSettings;
|
|
605
504
|
}
|
|
606
505
|
|
|
506
|
+
/** Prepares a compaction of the whole context log; undefined when there is nothing to summarize. */
|
|
607
507
|
export function prepareCompaction(
|
|
608
|
-
|
|
508
|
+
entries: ContextLogEntry[],
|
|
609
509
|
settings: CompactionSettings,
|
|
610
510
|
): CompactionPreparation | undefined {
|
|
611
|
-
if (
|
|
511
|
+
if (entries.length > 0 && entries[entries.length - 1].kind === "compaction") {
|
|
612
512
|
return undefined;
|
|
613
513
|
}
|
|
614
514
|
|
|
615
515
|
let prevCompactionIndex = -1;
|
|
616
|
-
for (let i =
|
|
617
|
-
if (
|
|
516
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
517
|
+
if (entries[i].kind === "compaction") {
|
|
618
518
|
prevCompactionIndex = i;
|
|
619
519
|
break;
|
|
620
520
|
}
|
|
621
521
|
}
|
|
622
522
|
|
|
623
|
-
|
|
523
|
+
const previous = prevCompactionIndex >= 0 ? (entries[prevCompactionIndex] as ContextCompaction) : undefined;
|
|
624
524
|
let boundaryStart = 0;
|
|
625
|
-
if (
|
|
626
|
-
const
|
|
627
|
-
previousSummary = prevCompaction.summary;
|
|
628
|
-
const firstKeptEntryIndex = pathEntries.findIndex((entry) => entry.id === prevCompaction.firstKeptEntryId);
|
|
525
|
+
if (previous) {
|
|
526
|
+
const firstKeptEntryIndex = entries.findIndex((entry) => entry.id === previous.firstKeptId);
|
|
629
527
|
boundaryStart = firstKeptEntryIndex >= 0 ? firstKeptEntryIndex : prevCompactionIndex + 1;
|
|
630
528
|
}
|
|
631
|
-
const boundaryEnd =
|
|
529
|
+
const boundaryEnd = entries.length;
|
|
632
530
|
|
|
633
|
-
const tokensBefore = estimateContextTokens(
|
|
531
|
+
const tokensBefore = estimateContextTokens(contextMessages(entries)).tokens;
|
|
634
532
|
|
|
635
|
-
const cutPoint = findCutPoint(
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
if (!firstKeptEntry?.id) {
|
|
640
|
-
return undefined; // Session needs migration
|
|
533
|
+
const cutPoint = findCutPoint(entries, boundaryStart, boundaryEnd, settings.keepRecentTokens);
|
|
534
|
+
const firstKeptEntry = entries[cutPoint.firstKeptEntryIndex];
|
|
535
|
+
if (!firstKeptEntry) {
|
|
536
|
+
return undefined;
|
|
641
537
|
}
|
|
642
|
-
const firstKeptEntryId = firstKeptEntry.id;
|
|
643
538
|
|
|
644
539
|
const historyEnd = cutPoint.isSplitTurn ? cutPoint.turnStartIndex : cutPoint.firstKeptEntryIndex;
|
|
645
540
|
|
|
646
|
-
// Messages to summarize (will be discarded after summary)
|
|
647
541
|
const messagesToSummarize: AgentMessage[] = [];
|
|
648
542
|
for (let i = boundaryStart; i < historyEnd; i++) {
|
|
649
|
-
const msg =
|
|
543
|
+
const msg = messageOf(entries[i]);
|
|
650
544
|
if (msg) messagesToSummarize.push(msg);
|
|
651
545
|
}
|
|
652
546
|
|
|
653
|
-
// Messages for turn prefix summary (if splitting a turn)
|
|
654
547
|
const turnPrefixMessages: AgentMessage[] = [];
|
|
655
548
|
if (cutPoint.isSplitTurn) {
|
|
656
549
|
for (let i = cutPoint.turnStartIndex; i < cutPoint.firstKeptEntryIndex; i++) {
|
|
657
|
-
const msg =
|
|
550
|
+
const msg = messageOf(entries[i]);
|
|
658
551
|
if (msg) turnPrefixMessages.push(msg);
|
|
659
552
|
}
|
|
660
553
|
}
|
|
@@ -663,10 +556,7 @@ export function prepareCompaction(
|
|
|
663
556
|
return undefined;
|
|
664
557
|
}
|
|
665
558
|
|
|
666
|
-
|
|
667
|
-
const fileOps = extractFileOperations(messagesToSummarize, pathEntries, prevCompactionIndex);
|
|
668
|
-
|
|
669
|
-
// Also extract file ops from turn prefix if splitting
|
|
559
|
+
const fileOps = extractFileOperations(messagesToSummarize, previous);
|
|
670
560
|
if (cutPoint.isSplitTurn) {
|
|
671
561
|
for (const msg of turnPrefixMessages) {
|
|
672
562
|
extractFileOpsFromMessage(msg, fileOps);
|
|
@@ -674,12 +564,12 @@ export function prepareCompaction(
|
|
|
674
564
|
}
|
|
675
565
|
|
|
676
566
|
return {
|
|
677
|
-
|
|
567
|
+
firstKeptId: firstKeptEntry.id,
|
|
678
568
|
messagesToSummarize,
|
|
679
569
|
turnPrefixMessages,
|
|
680
570
|
isSplitTurn: cutPoint.isSplitTurn,
|
|
681
571
|
tokensBefore,
|
|
682
|
-
previousSummary,
|
|
572
|
+
previousSummary: previous?.summary,
|
|
683
573
|
fileOps,
|
|
684
574
|
settings,
|
|
685
575
|
};
|
|
@@ -704,13 +594,7 @@ Summarize the prefix to provide context for the retained suffix:
|
|
|
704
594
|
|
|
705
595
|
Be concise. Focus on what's needed to understand the kept suffix.`;
|
|
706
596
|
|
|
707
|
-
/**
|
|
708
|
-
* Generate summaries for compaction using prepared data.
|
|
709
|
-
* Returns CompactionResult - SessionManager adds uuid/parentUuid when saving.
|
|
710
|
-
*
|
|
711
|
-
* @param preparation - Pre-calculated preparation from prepareCompaction()
|
|
712
|
-
* @param customInstructions - Optional custom focus for the summary
|
|
713
|
-
*/
|
|
597
|
+
/** Summarizes a prepared compaction; the caller records the result. */
|
|
714
598
|
export async function compact(
|
|
715
599
|
preparation: CompactionPreparation,
|
|
716
600
|
model: Model<any>,
|
|
@@ -723,7 +607,7 @@ export async function compact(
|
|
|
723
607
|
env?: Record<string, string>,
|
|
724
608
|
): Promise<CompactionResult> {
|
|
725
609
|
const {
|
|
726
|
-
|
|
610
|
+
firstKeptId,
|
|
727
611
|
messagesToSummarize,
|
|
728
612
|
turnPrefixMessages,
|
|
729
613
|
isSplitTurn,
|
|
@@ -783,20 +667,10 @@ export async function compact(
|
|
|
783
667
|
);
|
|
784
668
|
}
|
|
785
669
|
|
|
786
|
-
// Compute file lists and append to summary
|
|
787
670
|
const { readFiles, modifiedFiles } = computeFileLists(fileOps);
|
|
788
671
|
summary += formatFileOperations(readFiles, modifiedFiles);
|
|
789
672
|
|
|
790
|
-
|
|
791
|
-
throw new Error("First kept entry has no UUID - session may need migration");
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
return {
|
|
795
|
-
summary,
|
|
796
|
-
firstKeptEntryId,
|
|
797
|
-
tokensBefore,
|
|
798
|
-
details: { readFiles, modifiedFiles } as CompactionDetails,
|
|
799
|
-
};
|
|
673
|
+
return { summary, firstKeptId, tokensBefore, readFiles, modifiedFiles };
|
|
800
674
|
}
|
|
801
675
|
|
|
802
676
|
/**
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { AgentMessage } from "../loop/index.ts";
|
|
2
|
+
import { createCompactionSummaryMessage } from "./messages.ts";
|
|
3
|
+
|
|
4
|
+
/** A compaction replaces every entry before its first kept entry with its summary. */
|
|
5
|
+
export interface ContextCompaction {
|
|
6
|
+
kind: "compaction";
|
|
7
|
+
id: string;
|
|
8
|
+
summary: string;
|
|
9
|
+
firstKeptId: string;
|
|
10
|
+
tokensBefore: number;
|
|
11
|
+
timestamp: number;
|
|
12
|
+
readFiles: string[];
|
|
13
|
+
modifiedFiles: string[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** One entry of an agent's model context in the order the conversation produced it. */
|
|
17
|
+
export type ContextLogEntry = { kind: "message"; id: string; message: AgentMessage } | ContextCompaction;
|
|
18
|
+
|
|
19
|
+
export function latestCompaction(entries: readonly ContextLogEntry[]): ContextCompaction | undefined {
|
|
20
|
+
for (let index = entries.length - 1; index >= 0; index--) {
|
|
21
|
+
const entry = entries[index];
|
|
22
|
+
if (entry.kind === "compaction") return entry;
|
|
23
|
+
}
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** The entries the model sees: the latest compaction, the entries it keeps, and everything after it. */
|
|
28
|
+
export function activeContextEntries(entries: readonly ContextLogEntry[]): ContextLogEntry[] {
|
|
29
|
+
const compaction = latestCompaction(entries);
|
|
30
|
+
if (!compaction) return [...entries];
|
|
31
|
+
const compactionIndex = entries.indexOf(compaction);
|
|
32
|
+
const firstKeptIndex = entries.findIndex((entry, index) => index < compactionIndex && entry.id === compaction.firstKeptId);
|
|
33
|
+
return [
|
|
34
|
+
compaction,
|
|
35
|
+
...(firstKeptIndex < 0 ? [] : entries.slice(firstKeptIndex, compactionIndex)),
|
|
36
|
+
...entries.slice(compactionIndex + 1),
|
|
37
|
+
];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function contextEntryMessages(entry: ContextLogEntry): AgentMessage[] {
|
|
41
|
+
return entry.kind === "message"
|
|
42
|
+
? [entry.message]
|
|
43
|
+
: [createCompactionSummaryMessage(entry.summary, entry.tokensBefore, entry.timestamp)];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The messages of the model context, compaction-aware; the order is the order of the log. */
|
|
47
|
+
export function contextMessages(entries: readonly ContextLogEntry[]): AgentMessage[] {
|
|
48
|
+
return activeContextEntries(entries).flatMap(contextEntryMessages);
|
|
49
|
+
}
|
|
@@ -11,7 +11,7 @@ export const COMPACTION_SUMMARY_PREFIX = `The conversation history before this p
|
|
|
11
11
|
export const COMPACTION_SUMMARY_SUFFIX = `
|
|
12
12
|
</summary>`;
|
|
13
13
|
|
|
14
|
-
/**
|
|
14
|
+
/** A hidden note that a hook adds to one model call; it is not part of the context. */
|
|
15
15
|
export interface CustomMessage<T = unknown> {
|
|
16
16
|
role: "custom";
|
|
17
17
|
customType: string;
|
|
@@ -39,24 +39,17 @@ declare module "../loop/types.ts" {
|
|
|
39
39
|
export function createCompactionSummaryMessage(
|
|
40
40
|
summary: string,
|
|
41
41
|
tokensBefore: number,
|
|
42
|
-
timestamp:
|
|
42
|
+
timestamp: number,
|
|
43
43
|
): CompactionSummaryMessage {
|
|
44
44
|
return {
|
|
45
45
|
role: "compactionSummary",
|
|
46
46
|
summary: summary,
|
|
47
47
|
tokensBefore,
|
|
48
|
-
timestamp
|
|
48
|
+
timestamp,
|
|
49
49
|
};
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
/**
|
|
53
|
-
* Transform AgentMessages (including custom types) to LLM-compatible Messages.
|
|
54
|
-
*
|
|
55
|
-
* This is used by:
|
|
56
|
-
* - Agent's transormToLlm option (for prompt calls and queued messages)
|
|
57
|
-
* - Compaction's generateSummary (for summarization)
|
|
58
|
-
* - Custom extensions and tools
|
|
59
|
-
*/
|
|
52
|
+
/** Transform AgentMessages, including hook notes and compaction summaries, to LLM messages. */
|
|
60
53
|
export function convertToLlm(messages: AgentMessage[]): Message[] {
|
|
61
54
|
return messages
|
|
62
55
|
.map((m): Message | undefined => {
|
|
@@ -67,6 +60,7 @@ export function convertToLlm(messages: AgentMessage[]): Message[] {
|
|
|
67
60
|
role: "user",
|
|
68
61
|
content,
|
|
69
62
|
timestamp: m.timestamp,
|
|
63
|
+
transient: true,
|
|
70
64
|
};
|
|
71
65
|
}
|
|
72
66
|
case "compactionSummary":
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** The form of the read, write, edit and bash tools; the host runs them in its workspace executor. */
|
|
2
|
+
|
|
3
|
+
import type { AgentToolResult, AgentToolUpdateCallback, ToolExecutionMode } from "../loop/index.ts";
|
|
4
|
+
import type { Static, TSchema } from "typebox";
|
|
5
|
+
|
|
6
|
+
export type { AgentToolResult, AgentToolUpdateCallback, ToolExecutionMode };
|
|
7
|
+
|
|
8
|
+
export interface ToolDefinition<TParams extends TSchema = TSchema, TDetails = unknown> {
|
|
9
|
+
name: string;
|
|
10
|
+
label: string;
|
|
11
|
+
description: string;
|
|
12
|
+
parameters: TParams;
|
|
13
|
+
/** Optional compatibility shim to prepare raw tool call arguments before schema validation. */
|
|
14
|
+
prepareArguments?: (args: unknown) => Static<TParams>;
|
|
15
|
+
executionMode?: ToolExecutionMode;
|
|
16
|
+
execute(
|
|
17
|
+
toolCallId: string,
|
|
18
|
+
params: Static<TParams>,
|
|
19
|
+
signal: AbortSignal | undefined,
|
|
20
|
+
onUpdate: AgentToolUpdateCallback<TDetails> | undefined,
|
|
21
|
+
): Promise<AgentToolResult<TDetails>>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Preserve parameter inference for standalone tool definitions. */
|
|
25
|
+
export function defineTool<TParams extends TSchema, TDetails = unknown>(
|
|
26
|
+
tool: ToolDefinition<TParams, TDetails>,
|
|
27
|
+
): ToolDefinition<TParams, TDetails> {
|
|
28
|
+
return tool;
|
|
29
|
+
}
|
|
@@ -5,7 +5,7 @@ import { Type } from "typebox";
|
|
|
5
5
|
import { waitForChildProcess } from "../../utils/child-process.ts";
|
|
6
6
|
import { resolvePath } from "../../utils/paths.ts";
|
|
7
7
|
import { getShellConfig, killProcessTree } from "../../utils/shell.ts";
|
|
8
|
-
import type { ToolDefinition } from "../
|
|
8
|
+
import type { ToolDefinition } from "../tool-definition.ts";
|
|
9
9
|
import { OutputAccumulator } from "./output-accumulator.ts";
|
|
10
10
|
import { DEFAULT_MAX_LINES, formatSize, type TruncationResult } from "./truncate.ts";
|
|
11
11
|
|
|
@@ -64,12 +64,7 @@ export interface BashOperations {
|
|
|
64
64
|
) => Promise<{ exitCode: number | null }>;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
/**
|
|
68
|
-
* Create bash operations using the built-in local shell execution backend.
|
|
69
|
-
*
|
|
70
|
-
* This is useful for extensions that intercept user_bash and still want the
|
|
71
|
-
* standard local shell behavior while wrapping or rewriting commands.
|
|
72
|
-
*/
|
|
67
|
+
/** Bash operations on the built-in local shell. */
|
|
73
68
|
export function createLocalBashOperations(options?: { shellPath?: string }): BashOperations {
|
|
74
69
|
return {
|
|
75
70
|
exec: async (command, cwd, { onData, signal, timeout, env }) => {
|
|
@@ -168,14 +163,12 @@ export function createBashToolDefinition(
|
|
|
168
163
|
name: "bash",
|
|
169
164
|
label: "bash",
|
|
170
165
|
description: `Execute a bash command in the working directory, or in the folder given as cwd. Returns stdout and stderr; a nonzero exit code is reported at the end of the result (for example grep without a match), not as a tool error. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${BASH_MAX_BYTES / 1024}KB (whichever is hit first), and lines longer than ${BASH_MAX_LINE_CHARS} characters are shortened. If anything was cut, the full output is saved to a temp file. Optionally provide a timeout in seconds.`,
|
|
171
|
-
promptSnippet: "Execute bash commands (ls, grep, find, etc.)",
|
|
172
166
|
parameters: bashSchema,
|
|
173
167
|
async execute(
|
|
174
168
|
_toolCallId,
|
|
175
169
|
{ command, timeout, cwd: folder }: { command: string; timeout?: number; cwd?: string },
|
|
176
170
|
signal?: AbortSignal,
|
|
177
171
|
onUpdate?,
|
|
178
|
-
_ctx?,
|
|
179
172
|
) {
|
|
180
173
|
const resolvedCommand = commandPrefix ? `${commandPrefix}\n${command}` : command;
|
|
181
174
|
const spawnContext = resolveSpawnContext(resolvedCommand, folder === undefined ? cwd : resolvePath(folder, cwd), spawnHook);
|
|
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { constants } from "fs";
|
|
3
3
|
import { access as fsAccess, readFile as fsReadFile, writeFile as fsWriteFile } from "fs/promises";
|
|
4
4
|
import { type Static, Type } from "typebox";
|
|
5
|
-
import type { ToolDefinition } from "../
|
|
5
|
+
import type { ToolDefinition } from "../tool-definition.ts";
|
|
6
6
|
import { applyEditsToNormalizedContent, detectLineEnding, type Edit, generateDiffString, generateUnifiedPatch, normalizeToLF, restoreLineEndings, stripBom } from "./edit-diff.ts";
|
|
7
7
|
import { withFileMutationQueue } from "./file-mutation-queue.ts";
|
|
8
8
|
import { resolveToCwd } from "./path-utils.ts";
|
|
@@ -130,18 +130,9 @@ export function createEditToolDefinition(
|
|
|
130
130
|
label: "edit",
|
|
131
131
|
description:
|
|
132
132
|
"Edit a single file using exact text replacement. Read the file first; the edit is rejected if the file changed since that read. Every edits[].oldText must match a unique, non-overlapping region of the original file, or be anchored with occurrence, nearLine or replaceAll. If two changes affect the same block or nearby lines, merge them into one edit instead of emitting overlapping edits. Do not include large unchanged regions just to connect distant changes.",
|
|
133
|
-
promptSnippet:
|
|
134
|
-
"Make precise file edits with exact text replacement, including multiple disjoint edits in one call",
|
|
135
|
-
promptGuidelines: [
|
|
136
|
-
"Use edit for precise changes (edits[].oldText must match exactly)",
|
|
137
|
-
"When changing multiple separate locations in one file, use one edit call with multiple entries in edits[] instead of multiple edit calls",
|
|
138
|
-
"Each edits[].oldText is matched against the original file, not after earlier edits are applied. Do not emit overlapping or nested edits. Merge nearby changes into one edit.",
|
|
139
|
-
"Keep edits[].oldText as small as possible while still being unique in the file. Do not pad with large unchanged regions.",
|
|
140
|
-
"If oldText is not unique, the error lists every occurrence with its line number. Pick one with occurrence (1-based) or nearLine instead of padding oldText with more context, and use replaceAll only when every occurrence must change.",
|
|
141
|
-
],
|
|
142
133
|
parameters: editSchema,
|
|
143
134
|
prepareArguments: prepareEditArguments,
|
|
144
|
-
async execute(_toolCallId, input: EditToolInput, signal?: AbortSignal
|
|
135
|
+
async execute(_toolCallId, input: EditToolInput, signal?: AbortSignal) {
|
|
145
136
|
const { path, edits } = validateEditInput(input);
|
|
146
137
|
const absolutePath = resolveToCwd(path, cwd);
|
|
147
138
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import type {
|
|
2
|
+
import type { ImageContent, TextContent } from "@ragents/ai";
|
|
3
3
|
import { constants } from "fs";
|
|
4
4
|
import { access as fsAccess, readFile as fsReadFile } from "fs/promises";
|
|
5
5
|
import { Type } from "typebox";
|
|
6
6
|
import { processImage } from "../../utils/image-process.ts";
|
|
7
7
|
import { detectSupportedImageMimeTypeFromFile } from "../../utils/mime.ts";
|
|
8
|
-
import type { ToolDefinition } from "../
|
|
8
|
+
import type { ToolDefinition } from "../tool-definition.ts";
|
|
9
9
|
import { BASH_MAX_LINE_CHARS } from "./bash.ts";
|
|
10
10
|
import { resolveReadPathAsync } from "./path-utils.ts";
|
|
11
11
|
import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, type TruncationResult, truncateHead } from "./truncate.ts";
|
|
@@ -48,13 +48,6 @@ export interface ReadToolOptions {
|
|
|
48
48
|
operations?: ReadOperations;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
function getNonVisionImageNote(model: Model<Api> | undefined): string | undefined {
|
|
52
|
-
if (!model || model.input.includes("image")) {
|
|
53
|
-
return undefined;
|
|
54
|
-
}
|
|
55
|
-
return "[Current model does not support images. The image will be omitted from this request.]";
|
|
56
|
-
}
|
|
57
|
-
|
|
58
51
|
export function createReadToolDefinition(
|
|
59
52
|
cwd: string,
|
|
60
53
|
options?: ReadToolOptions,
|
|
@@ -65,15 +58,11 @@ export function createReadToolDefinition(
|
|
|
65
58
|
name: "read",
|
|
66
59
|
label: "read",
|
|
67
60
|
description: `Read the contents of a file. Supports text files and images (jpg, png, gif, webp, bmp). Images are sent as attachments. For text files, output is truncated to ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Use offset/limit for large files. When you need the full file, continue with offset until complete.`,
|
|
68
|
-
promptSnippet: "Read file contents",
|
|
69
|
-
promptGuidelines: ["Use read to examine files instead of cat or sed."],
|
|
70
61
|
parameters: readSchema,
|
|
71
62
|
async execute(
|
|
72
63
|
_toolCallId,
|
|
73
64
|
{ path, offset, limit }: { path: string; offset?: number; limit?: number },
|
|
74
65
|
signal?: AbortSignal,
|
|
75
|
-
_onUpdate?,
|
|
76
|
-
ctx?,
|
|
77
66
|
) {
|
|
78
67
|
return new Promise<{ content: (TextContent | ImageContent)[]; details: ReadToolDetails | undefined }>(
|
|
79
68
|
(resolve, reject) => {
|
|
@@ -100,18 +89,14 @@ export function createReadToolDefinition(
|
|
|
100
89
|
const contentHash = createHash("sha256").update(buffer).digest("hex");
|
|
101
90
|
let content: (TextContent | ImageContent)[];
|
|
102
91
|
let details: ReadToolDetails | undefined;
|
|
103
|
-
const nonVisionImageNote = getNonVisionImageNote(ctx?.model);
|
|
104
92
|
if (mimeType) {
|
|
105
93
|
// Read image as binary.
|
|
106
94
|
const processed = await processImage(buffer, mimeType, { autoResizeImages });
|
|
107
95
|
if (!processed.ok) {
|
|
108
|
-
|
|
109
|
-
if (nonVisionImageNote) textNote += `\n${nonVisionImageNote}`;
|
|
110
|
-
content = [{ type: "text", text: textNote }];
|
|
96
|
+
content = [{ type: "text", text: `Read image file [${mimeType}]\n${processed.message}` }];
|
|
111
97
|
} else {
|
|
112
98
|
let textNote = `Read image file [${processed.mimeType}]`;
|
|
113
99
|
if (processed.hints.length > 0) textNote += `\n${processed.hints.join("\n")}`;
|
|
114
|
-
if (nonVisionImageNote) textNote += `\n${nonVisionImageNote}`;
|
|
115
100
|
content = [
|
|
116
101
|
{ type: "text", text: textNote },
|
|
117
102
|
{ type: "image", data: processed.data, mimeType: processed.mimeType },
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mkdir as fsMkdir, writeFile as fsWriteFile } from "fs/promises";
|
|
2
2
|
import { dirname } from "path";
|
|
3
3
|
import { Type } from "typebox";
|
|
4
|
-
import type { ToolDefinition } from "../
|
|
4
|
+
import type { ToolDefinition } from "../tool-definition.ts";
|
|
5
5
|
import { withFileMutationQueue } from "./file-mutation-queue.ts";
|
|
6
6
|
import { resolveToCwd } from "./path-utils.ts";
|
|
7
7
|
|
|
@@ -42,15 +42,11 @@ export function createWriteToolDefinition(
|
|
|
42
42
|
label: "write",
|
|
43
43
|
description:
|
|
44
44
|
"Write content to a file. Creates the file if it doesn't exist, overwrites if it does. Automatically creates parent directories.",
|
|
45
|
-
promptSnippet: "Create or overwrite files",
|
|
46
|
-
promptGuidelines: ["Use write only for new files or complete rewrites."],
|
|
47
45
|
parameters: writeSchema,
|
|
48
46
|
async execute(
|
|
49
47
|
_toolCallId,
|
|
50
48
|
{ path, content }: { path: string; content: string },
|
|
51
49
|
signal?: AbortSignal,
|
|
52
|
-
_onUpdate?,
|
|
53
|
-
_ctx?,
|
|
54
50
|
) {
|
|
55
51
|
const absolutePath = resolveToCwd(path, cwd);
|
|
56
52
|
const dir = dirname(absolutePath);
|