klaus-agent 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/package.json +2 -1
  2. package/src/approval/approval.ts +0 -108
  3. package/src/approval/types.ts +0 -26
  4. package/src/background/task-manager.ts +0 -112
  5. package/src/background/tools.ts +0 -84
  6. package/src/background/types.ts +0 -29
  7. package/src/checkpoint/checkpoint-manager.ts +0 -60
  8. package/src/checkpoint/dmail.ts +0 -35
  9. package/src/checkpoint/types.ts +0 -16
  10. package/src/compaction/compaction.ts +0 -119
  11. package/src/compaction/summarizer.ts +0 -83
  12. package/src/compaction/types.ts +0 -29
  13. package/src/core/agent-loop.ts +0 -427
  14. package/src/core/agent.ts +0 -430
  15. package/src/extensions/runner.ts +0 -138
  16. package/src/extensions/types.ts +0 -177
  17. package/src/index.ts +0 -221
  18. package/src/injection/history-normalizer.ts +0 -44
  19. package/src/injection/injection-manager.ts +0 -34
  20. package/src/injection/types.ts +0 -12
  21. package/src/llm/provider.ts +0 -254
  22. package/src/llm/types.ts +0 -146
  23. package/src/multi-agent/labor-market.ts +0 -54
  24. package/src/multi-agent/task-executor.ts +0 -49
  25. package/src/multi-agent/task-tool.ts +0 -58
  26. package/src/multi-agent/types.ts +0 -10
  27. package/src/session/session-context-builder.ts +0 -65
  28. package/src/session/session-manager.ts +0 -258
  29. package/src/session/types.ts +0 -93
  30. package/src/skills/discovery.ts +0 -32
  31. package/src/skills/loader.ts +0 -54
  32. package/src/skills/skill-tool.ts +0 -50
  33. package/src/skills/types.ts +0 -18
  34. package/src/tools/executor.ts +0 -196
  35. package/src/tools/mcp-adapter.ts +0 -185
  36. package/src/tools/types.ts +0 -64
  37. package/src/types.ts +0 -96
  38. package/src/utils/id.ts +0 -8
  39. package/src/utils/jsonl.ts +0 -19
  40. package/src/wire/types.ts +0 -14
  41. package/src/wire/wire.ts +0 -79
  42. package/tsconfig.json +0 -19
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "klaus-agent",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Universal agent framework",
5
5
  "type": "module",
6
+ "files": ["dist", "README.md"],
6
7
  "main": "dist/index.js",
7
8
  "types": "dist/index.d.ts",
8
9
  "exports": {
@@ -1,108 +0,0 @@
1
- // Queue-based approval system
2
-
3
- import { generateId } from "../utils/id.js";
4
- import type { Approval, ApprovalConfig, ApprovalRequest, ApprovalResponse } from "./types.js";
5
-
6
- interface PendingRequest {
7
- request: ApprovalRequest;
8
- resolve: (approved: boolean) => void;
9
- }
10
-
11
- export class ApprovalImpl implements Approval {
12
- private _yolo: boolean;
13
- private _autoApproveActions: Set<string>;
14
- private _pending = new Map<string, PendingRequest>();
15
- private _queue: ApprovalRequest[] = [];
16
- private _waiters: ((req: ApprovalRequest) => void)[] = [];
17
-
18
- constructor(config?: ApprovalConfig) {
19
- this._yolo = config?.yolo ?? false;
20
- this._autoApproveActions = new Set(config?.autoApproveActions ?? []);
21
- }
22
-
23
- async request(sender: string, action: string, description: string, toolCallId: string): Promise<boolean> {
24
- if (this._yolo) return true;
25
- if (this._autoApproveActions.has(action)) return true;
26
-
27
- const req: ApprovalRequest = {
28
- id: generateId(),
29
- toolCallId,
30
- sender,
31
- action,
32
- description,
33
- };
34
-
35
- return new Promise<boolean>((resolve) => {
36
- this._pending.set(req.id, { request: req, resolve });
37
-
38
- // Deliver to waiter or queue
39
- const waiter = this._waiters.shift();
40
- if (waiter) {
41
- waiter(req);
42
- } else {
43
- this._queue.push(req);
44
- }
45
- });
46
- }
47
-
48
- async fetchRequest(): Promise<ApprovalRequest> {
49
- const queued = this._queue.shift();
50
- if (queued) return queued;
51
-
52
- return new Promise<ApprovalRequest>((resolve, reject) => {
53
- const waiter = (req: ApprovalRequest) => resolve(req);
54
- (waiter as any)._reject = reject;
55
- this._waiters.push(waiter);
56
- });
57
- }
58
-
59
- /** Cancel all pending waiters (e.g., on agent dispose) */
60
- cancelPendingWaiters(): void {
61
- for (const waiter of this._waiters) {
62
- const reject = (waiter as any)._reject;
63
- if (reject) reject(new Error("Approval cancelled"));
64
- }
65
- this._waiters = [];
66
- }
67
-
68
- resolve(requestId: string, response: ApprovalResponse): void {
69
- const pending = this._pending.get(requestId);
70
- if (!pending) return;
71
-
72
- this._pending.delete(requestId);
73
-
74
- if (response === "approve_for_session") {
75
- this._autoApproveActions.add(pending.request.action);
76
- pending.resolve(true);
77
- } else {
78
- pending.resolve(response === "approve");
79
- }
80
- }
81
-
82
- setYolo(yolo: boolean): void {
83
- this._yolo = yolo;
84
- }
85
-
86
- isYolo(): boolean {
87
- return this._yolo;
88
- }
89
-
90
- get autoApproveActions(): Set<string> {
91
- return this._autoApproveActions;
92
- }
93
-
94
- share(): Approval {
95
- // Shared state (yolo, autoApproveActions), independent queue
96
- // Use a shared state object so changes propagate bidirectionally
97
- const shared = new ApprovalImpl();
98
- shared._autoApproveActions = this._autoApproveActions; // same reference
99
- // Share yolo via getter/setter delegation
100
- const parent = this;
101
- Object.defineProperty(shared, '_yolo', {
102
- get() { return parent._yolo; },
103
- set(v: boolean) { parent._yolo = v; },
104
- configurable: true,
105
- });
106
- return shared;
107
- }
108
- }
@@ -1,26 +0,0 @@
1
- // Approval system types
2
-
3
- export type ApprovalResponse = "approve" | "approve_for_session" | "reject";
4
-
5
- export interface ApprovalRequest {
6
- id: string;
7
- toolCallId: string;
8
- sender: string;
9
- action: string;
10
- description: string;
11
- }
12
-
13
- export interface ApprovalConfig {
14
- yolo?: boolean;
15
- autoApproveActions?: string[];
16
- }
17
-
18
- export interface Approval {
19
- request(sender: string, action: string, description: string, toolCallId: string): Promise<boolean>;
20
- fetchRequest(): Promise<ApprovalRequest>;
21
- resolve(requestId: string, response: ApprovalResponse): void;
22
- setYolo(yolo: boolean): void;
23
- isYolo(): boolean;
24
- readonly autoApproveActions: Set<string>;
25
- share(): Approval;
26
- }
@@ -1,112 +0,0 @@
1
- // Background task manager — in-process async task execution
2
-
3
- import { generateId } from "../utils/id.js";
4
- import type { BackgroundTaskStatus, BackgroundTaskInfo, BackgroundTaskHandle, BackgroundTaskEvent } from "./types.js";
5
-
6
- interface InternalTask {
7
- id: string;
8
- name: string;
9
- status: BackgroundTaskStatus;
10
- createdAt: number;
11
- startedAt?: number;
12
- completedAt?: number;
13
- result?: unknown;
14
- error?: Error;
15
- abortController: AbortController;
16
- }
17
-
18
- export class BackgroundTaskManager {
19
- private _tasks = new Map<string, InternalTask>();
20
- private _onEvent?: (event: BackgroundTaskEvent) => void;
21
-
22
- constructor(onEvent?: (event: BackgroundTaskEvent) => void) {
23
- this._onEvent = onEvent;
24
- }
25
-
26
- spawn<T>(name: string, fn: (signal: AbortSignal) => Promise<T>): BackgroundTaskHandle<T> {
27
- const id = generateId();
28
- const abortController = new AbortController();
29
- const now = Date.now();
30
-
31
- const task: InternalTask = {
32
- id,
33
- name,
34
- status: "running",
35
- createdAt: now,
36
- startedAt: now,
37
- abortController,
38
- };
39
-
40
- this._tasks.set(id, task);
41
- this._onEvent?.({ type: "task_started", task: this._toInfo(task) });
42
-
43
- fn(abortController.signal).then(
44
- (result) => {
45
- task.status = "completed";
46
- task.completedAt = Date.now();
47
- task.result = result;
48
- try { this._onEvent?.({ type: "task_completed", task: this._toInfo(task), result }); } catch {}
49
- },
50
- (err) => {
51
- task.status = "failed";
52
- task.completedAt = Date.now();
53
- task.error = err instanceof Error ? err : new Error(String(err));
54
- try { this._onEvent?.({ type: "task_failed", task: this._toInfo(task), error: task.error.message }); } catch {}
55
- },
56
- );
57
-
58
- return this._toHandle<T>(task);
59
- }
60
-
61
- get(id: string): BackgroundTaskHandle | undefined {
62
- const task = this._tasks.get(id);
63
- return task ? this._toHandle(task) : undefined;
64
- }
65
-
66
- list(): BackgroundTaskInfo[] {
67
- return [...this._tasks.values()].map((t) => this._toInfo(t));
68
- }
69
-
70
- abort(id: string): boolean {
71
- const task = this._tasks.get(id);
72
- if (!task || task.status !== "running") return false;
73
- task.abortController.abort();
74
- return true;
75
- }
76
-
77
- abortAll(): void {
78
- for (const task of this._tasks.values()) {
79
- if (task.status === "running") {
80
- task.abortController.abort();
81
- }
82
- }
83
- }
84
-
85
- dispose(): void {
86
- this.abortAll();
87
- this._tasks.clear();
88
- }
89
-
90
- private _toInfo(task: InternalTask): BackgroundTaskInfo {
91
- return {
92
- id: task.id,
93
- name: task.name,
94
- status: task.status,
95
- createdAt: task.createdAt,
96
- startedAt: task.startedAt,
97
- completedAt: task.completedAt,
98
- error: task.error?.message,
99
- };
100
- }
101
-
102
- private _toHandle<T>(task: InternalTask): BackgroundTaskHandle<T> {
103
- return {
104
- get id() { return task.id; },
105
- get name() { return task.name; },
106
- get status() { return task.status; },
107
- get result() { return task.result as T | undefined; },
108
- get error() { return task.error; },
109
- abort: () => task.abortController.abort(),
110
- };
111
- }
112
- }
@@ -1,84 +0,0 @@
1
- // Built-in background task tools — allows LLM to manage background tasks
2
-
3
- import { Type } from "@sinclair/typebox";
4
- import type { AgentTool, AgentToolResult, ToolExecutionContext } from "../tools/types.js";
5
- import type { BackgroundTaskManager } from "./task-manager.js";
6
- import type { TaskFactory } from "./types.js";
7
-
8
- export function createBackgroundTaskTools(
9
- manager: BackgroundTaskManager,
10
- factories?: Record<string, TaskFactory>,
11
- ): AgentTool[] {
12
- const tools: AgentTool[] = [
13
- {
14
- name: "check_task_status",
15
- label: "Check Task Status",
16
- description: "Check the status of background tasks. If no task_id is provided, returns all tasks.",
17
- parameters: Type.Object({
18
- task_id: Type.Optional(Type.String({ description: "Task ID to check. Omit to list all tasks." })),
19
- }),
20
- async execute(_toolCallId: string, params: { task_id?: string }): Promise<AgentToolResult> {
21
- if (params.task_id) {
22
- const handle = manager.get(params.task_id);
23
- if (!handle) {
24
- return { content: [{ type: "text", text: `No task found with ID: ${params.task_id}` }] };
25
- }
26
- return { content: [{ type: "text", text: JSON.stringify({ id: handle.id, name: handle.name, status: handle.status }, null, 2) }] };
27
- }
28
- const tasks = manager.list();
29
- if (tasks.length === 0) {
30
- return { content: [{ type: "text", text: "No background tasks." }] };
31
- }
32
- return { content: [{ type: "text", text: JSON.stringify(tasks, null, 2) }] };
33
- },
34
- },
35
- {
36
- name: "get_task_result",
37
- label: "Get Task Result",
38
- description: "Get the result of a completed background task.",
39
- parameters: Type.Object({
40
- task_id: Type.String({ description: "Task ID to get the result for." }),
41
- }),
42
- async execute(_toolCallId: string, params: { task_id: string }): Promise<AgentToolResult> {
43
- const handle = manager.get(params.task_id);
44
- if (!handle) {
45
- return { content: [{ type: "text", text: `No task found with ID: ${params.task_id}` }] };
46
- }
47
- if (handle.status === "running") {
48
- return { content: [{ type: "text", text: `Task "${handle.name}" is still running.` }] };
49
- }
50
- if (handle.status === "failed") {
51
- return { content: [{ type: "text", text: `Task "${handle.name}" failed: ${handle.error?.message ?? "unknown error"}` }] };
52
- }
53
- const resultText = handle.result !== undefined ? JSON.stringify(handle.result, null, 2) : "(no result)";
54
- return { content: [{ type: "text", text: resultText }] };
55
- },
56
- },
57
- ];
58
-
59
- if (factories && Object.keys(factories).length > 0) {
60
- const names = Object.keys(factories);
61
- const description = `Start a background task. Available tasks:\n${names.map((n) => `- ${n}`).join("\n")}`;
62
-
63
- tools.push({
64
- name: "start_background_task",
65
- label: "Start Background Task",
66
- description,
67
- parameters: Type.Object({
68
- task_name: Type.String({ description: "Name of the task to start." }),
69
- args: Type.Optional(Type.Unknown({ description: "Arguments to pass to the task." })),
70
- }),
71
- async execute(_toolCallId: string, params: { task_name: string; args?: unknown }): Promise<AgentToolResult> {
72
- const factory = factories[params.task_name];
73
- if (!factory) {
74
- const available = Object.keys(factories).join(", ");
75
- return { content: [{ type: "text", text: `Unknown task: "${params.task_name}". Available: ${available}` }] };
76
- }
77
- const handle = manager.spawn(params.task_name, (signal) => factory(params.args, signal));
78
- return { content: [{ type: "text", text: `Task started: ${handle.name} (ID: ${handle.id})` }] };
79
- },
80
- });
81
- }
82
-
83
- return tools;
84
- }
@@ -1,29 +0,0 @@
1
- // Background task types
2
-
3
- export type BackgroundTaskStatus = "pending" | "running" | "completed" | "failed";
4
-
5
- export interface BackgroundTaskInfo {
6
- id: string;
7
- name: string;
8
- status: BackgroundTaskStatus;
9
- createdAt: number;
10
- startedAt?: number;
11
- completedAt?: number;
12
- error?: string;
13
- }
14
-
15
- export interface BackgroundTaskHandle<T = unknown> {
16
- readonly id: string;
17
- readonly name: string;
18
- readonly status: BackgroundTaskStatus;
19
- readonly result?: T;
20
- readonly error?: Error;
21
- abort(): void;
22
- }
23
-
24
- export type BackgroundTaskEvent =
25
- | { type: "task_started"; task: BackgroundTaskInfo }
26
- | { type: "task_completed"; task: BackgroundTaskInfo; result: unknown }
27
- | { type: "task_failed"; task: BackgroundTaskInfo; error: string };
28
-
29
- export type TaskFactory = (args: unknown, signal: AbortSignal) => Promise<unknown>;
@@ -1,60 +0,0 @@
1
- // Checkpoint manager — creates checkpoints in session tree, handles D-Mail revert
2
-
3
- import type { SessionManager } from "../session/session-manager.js";
4
- import type { CheckpointInfo } from "./types.js";
5
- import { DenwaRenji } from "./dmail.js";
6
-
7
- export class CheckpointManager {
8
- private _checkpoints: CheckpointInfo[] = [];
9
- private _nextId = 0; // Global counter, never resets after D-Mail
10
- private _denwaRenji = new DenwaRenji();
11
-
12
- constructor(private _session: SessionManager) {}
13
-
14
- get denwaRenji(): DenwaRenji {
15
- return this._denwaRenji;
16
- }
17
-
18
- async checkpoint(): Promise<CheckpointInfo> {
19
- const checkpointId = this._nextId++;
20
- const entryId = await this._session.appendCheckpoint(checkpointId);
21
-
22
- const info: CheckpointInfo = { checkpointId, entryId };
23
- this._checkpoints.push(info);
24
- this._denwaRenji.setCheckpointCount(this._checkpoints.length);
25
-
26
- return info;
27
- }
28
-
29
- getCheckpoint(checkpointId: number): CheckpointInfo | undefined {
30
- return this._checkpoints.find((cp) => cp.checkpointId === checkpointId);
31
- }
32
-
33
- getAllCheckpoints(): CheckpointInfo[] {
34
- return [...this._checkpoints];
35
- }
36
-
37
- /**
38
- * Handle D-Mail: branch from the target checkpoint and inject the message.
39
- * Returns the D-Mail content if one was pending, null otherwise.
40
- */
41
- async handleDMail(): Promise<string | null> {
42
- const dmail = this._denwaRenji.fetchPendingDMail();
43
- if (!dmail) return null;
44
-
45
- const checkpoint = this._checkpoints.find((cp) => cp.checkpointId === dmail.checkpointId);
46
- if (!checkpoint) {
47
- throw new Error(`Checkpoint ${dmail.checkpointId} not found`);
48
- }
49
-
50
- // Branch session tree back to checkpoint entry
51
- this._session.branch(checkpoint.entryId);
52
-
53
- // Remove checkpoints after the target (keep target itself)
54
- const targetIdx = this._checkpoints.indexOf(checkpoint);
55
- this._checkpoints.length = targetIdx + 1;
56
- this._denwaRenji.setCheckpointCount(this._checkpoints.length);
57
-
58
- return dmail.message;
59
- }
60
- }
@@ -1,35 +0,0 @@
1
- // DenwaRenji — D-Mail state machine
2
-
3
- import type { DMail } from "./types.js";
4
-
5
- export class DenwaRenji {
6
- private _pendingDMail: DMail | null = null;
7
- private _checkpointCount = 0;
8
-
9
- sendDMail(message: string, checkpointId: number): void {
10
- if (checkpointId < 0 || checkpointId >= this._checkpointCount) {
11
- throw new Error(
12
- `Invalid checkpoint ID ${checkpointId}. Valid range: 0-${this._checkpointCount - 1}`,
13
- );
14
- }
15
- this._pendingDMail = { message, checkpointId };
16
- }
17
-
18
- setCheckpointCount(n: number): void {
19
- this._checkpointCount = n;
20
- }
21
-
22
- getCheckpointCount(): number {
23
- return this._checkpointCount;
24
- }
25
-
26
- fetchPendingDMail(): DMail | null {
27
- const dmail = this._pendingDMail;
28
- this._pendingDMail = null;
29
- return dmail;
30
- }
31
-
32
- hasPendingDMail(): boolean {
33
- return this._pendingDMail !== null;
34
- }
35
- }
@@ -1,16 +0,0 @@
1
- // Checkpoint and D-Mail types
2
-
3
- export interface CheckpointConfig {
4
- enabled?: boolean;
5
- enableDMail?: boolean;
6
- }
7
-
8
- export interface DMail {
9
- message: string;
10
- checkpointId: number;
11
- }
12
-
13
- export interface CheckpointInfo {
14
- checkpointId: number;
15
- entryId: string;
16
- }
@@ -1,119 +0,0 @@
1
- // Compaction logic — token estimation, cut point detection, shouldCompact
2
-
3
- import type { AgentMessage, Message } from "../types.js";
4
- import type { CutPointResult } from "./types.js";
5
-
6
- const CHARS_PER_TOKEN = 4;
7
-
8
- export function estimateTokens(messages: AgentMessage[]): number {
9
- let total = 0;
10
- for (const msg of messages) {
11
- total += estimateMessageTokens(msg);
12
- }
13
- return total;
14
- }
15
-
16
- function estimateMessageTokens(msg: AgentMessage): number {
17
- if (!msg || typeof msg !== "object" || !("role" in msg)) return 0;
18
- const m = msg as Message;
19
-
20
- if (m.role === "user") {
21
- if (typeof m.content === "string") return Math.ceil(m.content.length / CHARS_PER_TOKEN);
22
- return m.content.reduce((sum, block) => {
23
- if (block.type === "text") return sum + Math.ceil(block.text.length / CHARS_PER_TOKEN);
24
- return sum + 1000; // image estimate
25
- }, 0);
26
- }
27
-
28
- if (m.role === "assistant") {
29
- return m.content.reduce((sum, block) => {
30
- if (block.type === "text") return sum + Math.ceil(block.text.length / CHARS_PER_TOKEN);
31
- if (block.type === "thinking") return sum + Math.ceil(block.thinking.length / CHARS_PER_TOKEN);
32
- if (block.type === "tool_call") return sum + Math.ceil(JSON.stringify(block.input).length / CHARS_PER_TOKEN) + 20;
33
- return sum;
34
- }, 0);
35
- }
36
-
37
- if (m.role === "tool_result") {
38
- if (typeof m.content === "string") return Math.ceil(m.content.length / CHARS_PER_TOKEN);
39
- return m.content.reduce((sum, block) => {
40
- if (block.type === "text") return sum + Math.ceil(block.text.length / CHARS_PER_TOKEN);
41
- return sum + 1000;
42
- }, 0);
43
- }
44
-
45
- return 0;
46
- }
47
-
48
- export function shouldCompact(
49
- contextTokens: number,
50
- maxContextTokens: number,
51
- reserveTokens: number,
52
- ): boolean {
53
- return contextTokens > maxContextTokens - reserveTokens;
54
- }
55
-
56
- export function findCutPoint(
57
- messages: AgentMessage[],
58
- keepRecentTokens: number,
59
- ): CutPointResult {
60
- if (messages.length === 0) {
61
- return { firstKeptIndex: 0, isSplitTurn: false };
62
- }
63
-
64
- // Walk backwards, accumulating tokens until we reach keepRecentTokens
65
- let accumulated = 0;
66
- let cutIndex = messages.length;
67
-
68
- for (let i = messages.length - 1; i >= 0; i--) {
69
- const tokens = estimateMessageTokens(messages[i]);
70
- accumulated += tokens;
71
-
72
- if (accumulated >= keepRecentTokens) {
73
- cutIndex = i + 1;
74
- break;
75
- }
76
- }
77
-
78
- // Ensure we don't cut at a tool_result (must follow its tool_call)
79
- while (cutIndex < messages.length) {
80
- const msg = messages[cutIndex];
81
- if (msg && typeof msg === "object" && "role" in msg && (msg as Message).role === "tool_result") {
82
- cutIndex++;
83
- } else {
84
- break;
85
- }
86
- }
87
-
88
- // Don't cut if nothing to discard
89
- if (cutIndex <= 1) {
90
- return { firstKeptIndex: 0, isSplitTurn: false };
91
- }
92
-
93
- const isSplitTurn = cutIndex > 0 && cutIndex < messages.length &&
94
- messages[cutIndex - 1] && typeof messages[cutIndex - 1] === "object" &&
95
- "role" in messages[cutIndex - 1]! && (messages[cutIndex - 1] as Message).role === "assistant";
96
-
97
- return { firstKeptIndex: cutIndex, isSplitTurn };
98
- }
99
-
100
- export function messagesToText(messages: AgentMessage[]): string {
101
- const parts: string[] = [];
102
- for (const msg of messages) {
103
- if (!msg || typeof msg !== "object" || !("role" in msg)) continue;
104
- const m = msg as Message;
105
-
106
- if (m.role === "user") {
107
- const text = typeof m.content === "string" ? m.content : m.content.filter((b) => b.type === "text").map((b) => (b as { text: string }).text).join("\n");
108
- parts.push(`User: ${text}`);
109
- } else if (m.role === "assistant") {
110
- const text = m.content.filter((b) => b.type === "text").map((b) => (b as { text: string }).text).join("\n");
111
- const toolCalls = m.content.filter((b) => b.type === "tool_call").map((b) => (b as { name: string }).name);
112
- parts.push(`Assistant: ${text}${toolCalls.length ? ` [tools: ${toolCalls.join(", ")}]` : ""}`);
113
- } else if (m.role === "tool_result") {
114
- const text = typeof m.content === "string" ? m.content : m.content.filter((b) => b.type === "text").map((b) => (b as { text: string }).text).join("\n");
115
- parts.push(`Tool result: ${text.slice(0, 500)}`);
116
- }
117
- }
118
- return parts.join("\n");
119
- }
@@ -1,83 +0,0 @@
1
- // LLM-based summarization for compaction
2
-
3
- import type { LLMProvider, LLMRequestOptions, AssistantMessage } from "../llm/types.js";
4
- import type { AgentMessage } from "../types.js";
5
- import type { CompactionSummarizer, CompactionInput } from "./types.js";
6
- import { messagesToText } from "./compaction.js";
7
-
8
- const SUMMARIZE_PROMPT = `You are a conversation summarizer. Summarize the following conversation history concisely, preserving:
9
- - Key decisions and outcomes
10
- - Important context and facts established
11
- - Current state of any ongoing tasks
12
- - Tool calls made and their results (briefly)
13
-
14
- Be concise but thorough. Use bullet points. Do not include greetings or filler.`;
15
-
16
- const UPDATE_PROMPT = `You are a conversation summarizer. You have a previous summary and new conversation to incorporate.
17
-
18
- Previous summary:
19
- {previous_summary}
20
-
21
- Merge the new conversation into the summary, preserving all important context. Remove outdated information that has been superseded.`;
22
-
23
- export class LLMSummarizer implements CompactionSummarizer {
24
- constructor(
25
- private provider: LLMProvider,
26
- private modelId: string,
27
- ) {}
28
-
29
- async summarize(messages: CompactionInput[], previousSummary?: string): Promise<string> {
30
- const conversationText = messages.map((m) => `${m.role}: ${m.content}`).join("\n");
31
-
32
- const systemPrompt = previousSummary
33
- ? UPDATE_PROMPT.replace("{previous_summary}", previousSummary)
34
- : SUMMARIZE_PROMPT;
35
-
36
- const options: LLMRequestOptions = {
37
- model: this.modelId,
38
- systemPrompt,
39
- messages: [{ role: "user", content: `Summarize this conversation:\n\n${conversationText}` }],
40
- maxTokens: 2048,
41
- };
42
-
43
- let result = "";
44
- for await (const event of this.provider.stream(options)) {
45
- if (event.type === "text") {
46
- result += event.text;
47
- } else if (event.type === "done") {
48
- const textBlocks = event.message.content.filter((b) => b.type === "text");
49
- if (textBlocks.length > 0) {
50
- result = textBlocks.map((b) => (b as { text: string }).text).join("");
51
- }
52
- }
53
- }
54
-
55
- return result;
56
- }
57
- }
58
-
59
- export function agentMessagesToCompactionInput(messages: AgentMessage[]): CompactionInput[] {
60
- const results: CompactionInput[] = [];
61
- for (const msg of messages) {
62
- if (!msg || typeof msg !== "object" || !("role" in msg)) continue;
63
- const m = msg as any;
64
-
65
- if (m.role === "user") {
66
- const text = typeof m.content === "string"
67
- ? m.content
68
- : (m.content ?? []).filter((b: any) => b.type === "text").map((b: any) => b.text).join("\n");
69
- results.push({ role: "user", content: text });
70
- } else if (m.role === "assistant") {
71
- const text = (m.content ?? []).filter((b: any) => b.type === "text").map((b: any) => b.text).join("\n");
72
- const toolCalls = (m.content ?? []).filter((b: any) => b.type === "tool_call").map((b: any) => b.name);
73
- const suffix = toolCalls.length ? ` [tools: ${toolCalls.join(", ")}]` : "";
74
- results.push({ role: "assistant", content: text + suffix });
75
- } else if (m.role === "tool_result") {
76
- const text = typeof m.content === "string"
77
- ? m.content
78
- : (m.content ?? []).filter((b: any) => b.type === "text").map((b: any) => b.text).join("\n");
79
- results.push({ role: "tool_result", content: text.slice(0, 500) });
80
- }
81
- }
82
- return results;
83
- }