@capekai/core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/README.md +12 -0
  2. package/package.json +105 -0
  3. package/src/adapters/ai-sdk.ts +84 -0
  4. package/src/compaction/contracts.ts +82 -0
  5. package/src/compaction/executor.ts +161 -0
  6. package/src/compaction/policy.ts +318 -0
  7. package/src/compaction/recovery.ts +139 -0
  8. package/src/compaction/task.ts +540 -0
  9. package/src/configuration/contracts.ts +58 -0
  10. package/src/configuration/defaults.ts +27 -0
  11. package/src/configuration/runtime.ts +42 -0
  12. package/src/configuration/single-model.ts +75 -0
  13. package/src/context/assembler.ts +112 -0
  14. package/src/context/index.ts +2 -0
  15. package/src/context/sources.ts +119 -0
  16. package/src/context/workspace.ts +63 -0
  17. package/src/core/agent.ts +401 -0
  18. package/src/core/build-tools.ts +139 -0
  19. package/src/core/chat-handler.ts +858 -0
  20. package/src/core/error-handling.ts +18 -0
  21. package/src/core/fork.ts +103 -0
  22. package/src/core/interrupt.ts +192 -0
  23. package/src/core/message-utils.ts +261 -0
  24. package/src/core/model-utils.ts +149 -0
  25. package/src/core/part-utils.ts +88 -0
  26. package/src/core/provider-utils.ts +67 -0
  27. package/src/core/revert.ts +46 -0
  28. package/src/core/step-handlers.ts +157 -0
  29. package/src/core/stream/finalization.ts +65 -0
  30. package/src/core/stream/stream-config.ts +82 -0
  31. package/src/core/stream-handlers.ts +242 -0
  32. package/src/core/structured-output.ts +68 -0
  33. package/src/core/tool-builders/agent-tools.ts +71 -0
  34. package/src/core/tool-builders/external-tools.ts +179 -0
  35. package/src/core/tool-builders/types.ts +16 -0
  36. package/src/core/tool-builders/workspace-tools.ts +293 -0
  37. package/src/core/tool-capabilities.ts +65 -0
  38. package/src/goals/evaluator.ts +171 -0
  39. package/src/goals/index.ts +3 -0
  40. package/src/goals/loop.ts +167 -0
  41. package/src/goals/service.ts +39 -0
  42. package/src/index.ts +10 -0
  43. package/src/internal/ask-authority.ts +29 -0
  44. package/src/internal/composition.ts +44 -0
  45. package/src/internal/configuration.ts +22 -0
  46. package/src/internal/execution.ts +108 -0
  47. package/src/internal/hosts.ts +64 -0
  48. package/src/internal/plugins.ts +71 -0
  49. package/src/internal/providers.ts +32 -0
  50. package/src/internal/sandbox.ts +19 -0
  51. package/src/internal/tools.ts +48 -0
  52. package/src/internal/workspace.ts +25 -0
  53. package/src/kernel/diagnostics.ts +249 -0
  54. package/src/kernel/errors.ts +120 -0
  55. package/src/kernel/events.ts +82 -0
  56. package/src/kernel/index.ts +72 -0
  57. package/src/kernel/kernel.ts +62 -0
  58. package/src/kernel/lifecycle.ts +72 -0
  59. package/src/kernel/plugin.ts +218 -0
  60. package/src/kernel/registry.ts +493 -0
  61. package/src/kernel/scope.ts +776 -0
  62. package/src/kernel/service-key.ts +19 -0
  63. package/src/kernel/types.ts +317 -0
  64. package/src/memory/index.ts +2 -0
  65. package/src/memory/memory-tool.ts +75 -0
  66. package/src/memory/registry.ts +172 -0
  67. package/src/permission/ask-user-api.ts +70 -0
  68. package/src/permission/contracts.ts +135 -0
  69. package/src/permission/permission-request-manager.ts +58 -0
  70. package/src/permission/policy.ts +277 -0
  71. package/src/permission/runtime.ts +612 -0
  72. package/src/plugins/compaction-policy.ts +46 -0
  73. package/src/plugins/compose.ts +171 -0
  74. package/src/plugins/context-sections.ts +246 -0
  75. package/src/plugins/default-agent-driver.ts +14 -0
  76. package/src/plugins/facade-plugins.ts +129 -0
  77. package/src/plugins/goal-domain.ts +82 -0
  78. package/src/plugins/legacy-system-message.ts +152 -0
  79. package/src/plugins/loaded-tools.ts +23 -0
  80. package/src/plugins/memory-domain.ts +264 -0
  81. package/src/plugins/orchestrator-session.ts +29 -0
  82. package/src/plugins/permission-policy.ts +49 -0
  83. package/src/plugins/retry-policy.ts +28 -0
  84. package/src/plugins/scheduler-domain.ts +192 -0
  85. package/src/plugins/service-keys.ts +294 -0
  86. package/src/plugins/session-search-domain.ts +238 -0
  87. package/src/plugins/skills-domain.ts +272 -0
  88. package/src/plugins/subagent-domain.ts +287 -0
  89. package/src/plugins/tool-catalog.ts +78 -0
  90. package/src/plugins/tool-output-policy.ts +52 -0
  91. package/src/plugins/value-plugins.ts +150 -0
  92. package/src/plugins/workflow-domain.ts +198 -0
  93. package/src/plugins/workspace-policy.ts +37 -0
  94. package/src/providers/registry.ts +63 -0
  95. package/src/providers/types.ts +44 -0
  96. package/src/retry/policy.ts +282 -0
  97. package/src/retry/stream-chat.ts +312 -0
  98. package/src/runtime/agent-runtime.ts +83 -0
  99. package/src/runtime/default-agent-driver.ts +23 -0
  100. package/src/runtime/domain-tool-source.ts +156 -0
  101. package/src/runtime/events.ts +61 -0
  102. package/src/runtime/host-dependencies.ts +71 -0
  103. package/src/runtime/host-guidance.ts +22 -0
  104. package/src/runtime/host-layout.ts +23 -0
  105. package/src/runtime/host.ts +129 -0
  106. package/src/runtime/standalone-host.ts +118 -0
  107. package/src/sandbox/controller.ts +204 -0
  108. package/src/sandbox/model.ts +305 -0
  109. package/src/sandbox/provider.ts +53 -0
  110. package/src/sandbox/types.ts +110 -0
  111. package/src/scheduler/host.ts +22 -0
  112. package/src/scheduler/scheduler-tool.ts +172 -0
  113. package/src/session-search/host.ts +56 -0
  114. package/src/session-search/index.ts +23 -0
  115. package/src/session-search/session-search-tool.ts +151 -0
  116. package/src/skills/index.ts +3 -0
  117. package/src/skills/registry.ts +63 -0
  118. package/src/skills/skill-manage-tool.ts +205 -0
  119. package/src/skills/skill-tool.ts +42 -0
  120. package/src/storage/contracts.ts +159 -0
  121. package/src/storage/memory.ts +321 -0
  122. package/src/storage/options.ts +75 -0
  123. package/src/storage/runtime.ts +115 -0
  124. package/src/storage/sqlite-tool-output-artifacts.ts +106 -0
  125. package/src/storage/sqlite.ts +321 -0
  126. package/src/storage/tool-output-artifacts.ts +75 -0
  127. package/src/storage.ts +31 -0
  128. package/src/subagent/child-session.ts +282 -0
  129. package/src/subagent/guidance.ts +8 -0
  130. package/src/subagent/policy.ts +198 -0
  131. package/src/subagent/task-tool.ts +584 -0
  132. package/src/tool-output/contracts.ts +111 -0
  133. package/src/tool-output/policy.ts +410 -0
  134. package/src/tool.ts +1 -0
  135. package/src/tools/executor.ts +258 -0
  136. package/src/tools/install-manifest.ts +40 -0
  137. package/src/tools/llm-api.ts +77 -0
  138. package/src/tools/registry.ts +206 -0
  139. package/src/tools/tool-artifact.ts +182 -0
  140. package/src/tools/tool-source.ts +53 -0
  141. package/src/utils/errors.ts +334 -0
  142. package/src/utils/strip-visualization.ts +50 -0
  143. package/src/workflow/decomposer.ts +139 -0
  144. package/src/workflow/execution.ts +523 -0
  145. package/src/workflow/orchestrator-session.ts +161 -0
  146. package/src/workflow/synthesizer.ts +130 -0
  147. package/src/workspace/contracts.ts +135 -0
  148. package/src/workspace/policy.ts +327 -0
@@ -0,0 +1,106 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import { mkdirSync } from 'node:fs';
3
+ import { dirname } from 'node:path';
4
+ import type {
5
+ ClosableStore,
6
+ ToolOutputArtifact,
7
+ ToolOutputArtifactStore,
8
+ } from './contracts';
9
+ import {
10
+ buildToolOutputArtifactPage,
11
+ createArtifact,
12
+ isToolOutputArtifactId,
13
+ } from './tool-output-artifacts';
14
+
15
+ export type SqliteToolOutputArtifactStore = ToolOutputArtifactStore & ClosableStore;
16
+
17
+ interface ToolOutputArtifactRow {
18
+ id: string;
19
+ session_id: string;
20
+ workspace_id: string | null;
21
+ tool_call_id: string;
22
+ tool_name: string;
23
+ content: string;
24
+ format: ToolOutputArtifact['format'];
25
+ size: number;
26
+ created_at: number;
27
+ }
28
+
29
+ function copy<T>(value: T): T {
30
+ return structuredClone(value);
31
+ }
32
+
33
+ function fromRow(row: ToolOutputArtifactRow): ToolOutputArtifact {
34
+ return {
35
+ id: row.id,
36
+ sessionId: row.session_id,
37
+ ...(row.workspace_id ? { workspaceId: row.workspace_id } : {}),
38
+ toolCallId: row.tool_call_id,
39
+ toolName: row.tool_name,
40
+ content: row.content,
41
+ format: row.format,
42
+ size: row.size,
43
+ createdAt: row.created_at,
44
+ };
45
+ }
46
+
47
+ export function createSqliteToolOutputArtifactStore(options: { path: string }): SqliteToolOutputArtifactStore {
48
+ mkdirSync(dirname(options.path), { recursive: true });
49
+ const db = new Database(options.path, { create: true, strict: true });
50
+ db.exec('PRAGMA journal_mode = WAL');
51
+ db.exec('PRAGMA foreign_keys = ON');
52
+ db.exec('PRAGMA busy_timeout = 5000');
53
+ db.exec(`
54
+ CREATE TABLE IF NOT EXISTS capek_tool_output_artifacts (
55
+ id TEXT PRIMARY KEY,
56
+ session_id TEXT NOT NULL,
57
+ workspace_id TEXT,
58
+ tool_call_id TEXT NOT NULL,
59
+ tool_name TEXT NOT NULL,
60
+ content TEXT NOT NULL,
61
+ format TEXT NOT NULL,
62
+ size INTEGER NOT NULL,
63
+ created_at INTEGER NOT NULL,
64
+ FOREIGN KEY (session_id) REFERENCES capek_sessions(id) ON DELETE CASCADE
65
+ );
66
+ CREATE INDEX IF NOT EXISTS capek_tool_output_artifacts_session_created
67
+ ON capek_tool_output_artifacts(session_id, created_at, id);
68
+ CREATE INDEX IF NOT EXISTS capek_tool_output_artifacts_session_call
69
+ ON capek_tool_output_artifacts(session_id, tool_call_id);
70
+ `);
71
+ let closed = false;
72
+ return {
73
+ async create(input) {
74
+ const artifact = createArtifact(input);
75
+ db.run(
76
+ `INSERT INTO capek_tool_output_artifacts
77
+ (id, session_id, workspace_id, tool_call_id, tool_name, content, format, size, created_at)
78
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
79
+ [
80
+ artifact.id,
81
+ artifact.sessionId,
82
+ artifact.workspaceId ?? null,
83
+ artifact.toolCallId,
84
+ artifact.toolName,
85
+ artifact.content,
86
+ artifact.format,
87
+ artifact.size,
88
+ artifact.createdAt,
89
+ ],
90
+ );
91
+ return copy(artifact);
92
+ },
93
+ async getPage(sessionId, artifactId, offset, limit) {
94
+ if (!isToolOutputArtifactId(artifactId)) return null;
95
+ const row = db.query(
96
+ 'SELECT * FROM capek_tool_output_artifacts WHERE id = ? AND session_id = ?',
97
+ ).get(artifactId, sessionId) as ToolOutputArtifactRow | null;
98
+ return row ? buildToolOutputArtifactPage(fromRow(row), offset, limit) : null;
99
+ },
100
+ close() {
101
+ if (closed) return;
102
+ closed = true;
103
+ db.close();
104
+ },
105
+ };
106
+ }
@@ -0,0 +1,321 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import { mkdirSync } from 'node:fs';
3
+ import { dirname } from 'node:path';
4
+ import type { Message, MessageWithParts, Part, Session, ToolPart } from '@capekai/types';
5
+ import type {
6
+ ClosableStore,
7
+ ConversationStore,
8
+ SessionUpdates,
9
+ StreamingPartSnapshot,
10
+ TranscriptPageResult,
11
+ } from './contracts';
12
+
13
+ interface MessageRow {
14
+ id: string;
15
+ session_id: string;
16
+ sequence: number;
17
+ created_at: number;
18
+ record: string;
19
+ }
20
+
21
+ export type SqliteConversationStore = ConversationStore & ClosableStore;
22
+
23
+ function parse<T>(value: string): T {
24
+ return JSON.parse(value) as T;
25
+ }
26
+
27
+ function clone<T>(value: T): T {
28
+ return structuredClone(value);
29
+ }
30
+
31
+ export function createSqliteConversationStore(options: { path: string }): SqliteConversationStore {
32
+ mkdirSync(dirname(options.path), { recursive: true });
33
+ const db = new Database(options.path, { create: true, strict: true });
34
+ db.exec('PRAGMA journal_mode = WAL');
35
+ // WAL pairing: process-crash-safe commits; OS-loss tail accepted (agent
36
+ // transcripts, not ledgers). Single-writer invariant: only this process writes.
37
+ db.exec('PRAGMA synchronous = NORMAL');
38
+ db.exec('PRAGMA foreign_keys = ON');
39
+ db.exec('PRAGMA busy_timeout = 5000');
40
+ db.exec(`
41
+ CREATE TABLE IF NOT EXISTS capek_sessions (
42
+ id TEXT PRIMARY KEY,
43
+ parent_id TEXT,
44
+ created_at TEXT NOT NULL,
45
+ record TEXT NOT NULL
46
+ );
47
+ CREATE INDEX IF NOT EXISTS capek_sessions_parent_created
48
+ ON capek_sessions(parent_id, created_at, id);
49
+ CREATE TABLE IF NOT EXISTS capek_session_sequences (
50
+ session_id TEXT PRIMARY KEY,
51
+ next_sequence INTEGER NOT NULL,
52
+ FOREIGN KEY (session_id) REFERENCES capek_sessions(id) ON DELETE CASCADE
53
+ );
54
+ CREATE TABLE IF NOT EXISTS capek_messages (
55
+ id TEXT PRIMARY KEY,
56
+ session_id TEXT NOT NULL,
57
+ sequence INTEGER NOT NULL,
58
+ created_at INTEGER NOT NULL,
59
+ record TEXT NOT NULL,
60
+ FOREIGN KEY (session_id) REFERENCES capek_sessions(id) ON DELETE CASCADE,
61
+ UNIQUE (session_id, sequence)
62
+ );
63
+ CREATE INDEX IF NOT EXISTS capek_messages_session_sequence
64
+ ON capek_messages(session_id, sequence);
65
+ CREATE TABLE IF NOT EXISTS capek_parts (
66
+ id TEXT PRIMARY KEY,
67
+ message_id TEXT NOT NULL,
68
+ session_id TEXT NOT NULL,
69
+ type TEXT NOT NULL,
70
+ call_id TEXT,
71
+ created_at INTEGER NOT NULL,
72
+ record TEXT NOT NULL,
73
+ FOREIGN KEY (message_id) REFERENCES capek_messages(id) ON DELETE CASCADE,
74
+ FOREIGN KEY (session_id) REFERENCES capek_sessions(id) ON DELETE CASCADE
75
+ );
76
+ CREATE INDEX IF NOT EXISTS capek_parts_message_order
77
+ ON capek_parts(message_id, created_at, id);
78
+ CREATE INDEX IF NOT EXISTS capek_parts_session_order
79
+ ON capek_parts(session_id, created_at, id);
80
+ CREATE INDEX IF NOT EXISTS capek_parts_tool_call
81
+ ON capek_parts(session_id, call_id, created_at, id)
82
+ WHERE type = 'tool' AND call_id IS NOT NULL;
83
+ `);
84
+
85
+ const getPartsByMessage = (messageId: string): Part[] => db.query(
86
+ 'SELECT record FROM capek_parts WHERE message_id = ? ORDER BY created_at ASC, rowid ASC',
87
+ ).all(messageId).map(row => parse<Part>((row as { record: string }).record));
88
+
89
+ const listRows = (sessionId: string): MessageRow[] => db.query(
90
+ 'SELECT * FROM capek_messages WHERE session_id = ? ORDER BY sequence ASC, created_at ASC, id ASC',
91
+ ).all(sessionId) as MessageRow[];
92
+
93
+ const withParts = (rows: MessageRow[]): MessageWithParts[] => rows.map(row => ({
94
+ message: parse<Message>(row.record),
95
+ parts: getPartsByMessage(row.id),
96
+ }));
97
+
98
+ let closed = false;
99
+ const store: SqliteConversationStore = {
100
+ async createSession(input) {
101
+ const now = new Date().toISOString();
102
+ const session = clone({
103
+ ...input,
104
+ tags: input.tags ?? [],
105
+ createdAt: input.createdAt || now,
106
+ updatedAt: input.updatedAt || now,
107
+ }) as Session;
108
+ const transaction = db.transaction((value: Session) => {
109
+ db.run(
110
+ 'INSERT INTO capek_sessions (id, parent_id, created_at, record) VALUES (?, ?, ?, ?)',
111
+ [value.id, value.parentId ?? null, value.createdAt, JSON.stringify(value)],
112
+ );
113
+ db.run('INSERT INTO capek_session_sequences (session_id, next_sequence) VALUES (?, 1)', [value.id]);
114
+ });
115
+ transaction.immediate(session);
116
+ return clone(session);
117
+ },
118
+ async getSession(id) {
119
+ const row = db.query('SELECT record FROM capek_sessions WHERE id = ?').get(id) as { record: string } | null;
120
+ return row ? parse<Session>(row.record) : null;
121
+ },
122
+ async updateSession(id, updates: SessionUpdates) {
123
+ const current = await store.getSession(id);
124
+ if (!current) return null;
125
+ const updated = { ...current, ...updates, updatedAt: new Date().toISOString() } as Session;
126
+ db.run(
127
+ 'UPDATE capek_sessions SET parent_id = ?, record = ? WHERE id = ?',
128
+ [updated.parentId ?? null, JSON.stringify(updated), id],
129
+ );
130
+ return clone(updated);
131
+ },
132
+ async getChildSessions(parentId) {
133
+ return (db.query(
134
+ 'SELECT record FROM capek_sessions WHERE parent_id = ? ORDER BY created_at ASC, rowid ASC',
135
+ ).all(parentId) as Array<{ record: string }>).map(row => parse<Session>(row.record));
136
+ },
137
+ async createMessage(message) {
138
+ const transaction = db.transaction((value: Message) => {
139
+ const sequenceRow = db.query(
140
+ 'SELECT next_sequence FROM capek_session_sequences WHERE session_id = ?',
141
+ ).get(value.sessionId) as { next_sequence: number } | null;
142
+ if (!sequenceRow) throw new Error(`Session does not exist: ${value.sessionId}`);
143
+ db.run(
144
+ 'UPDATE capek_session_sequences SET next_sequence = ? WHERE session_id = ?',
145
+ [sequenceRow.next_sequence + 1, value.sessionId],
146
+ );
147
+ db.run(
148
+ 'INSERT INTO capek_messages (id, session_id, sequence, created_at, record) VALUES (?, ?, ?, ?, ?)',
149
+ [value.id, value.sessionId, sequenceRow.next_sequence, value.createdAt, JSON.stringify(value)],
150
+ );
151
+ });
152
+ transaction.immediate(message);
153
+ return clone(message);
154
+ },
155
+ async getMessage(id) {
156
+ const row = db.query('SELECT record FROM capek_messages WHERE id = ?').get(id) as { record: string } | null;
157
+ return row ? parse<Message>(row.record) : null;
158
+ },
159
+ async getMessageWithParts(messageId) {
160
+ const message = await store.getMessage(messageId);
161
+ return message ? { message, parts: getPartsByMessage(messageId) } : null;
162
+ },
163
+ async updateMessage(id, updates) {
164
+ const current = await store.getMessage(id);
165
+ if (!current) return null;
166
+ const updated = { ...current, ...updates } as Message;
167
+ db.run(
168
+ 'UPDATE capek_messages SET created_at = ?, record = ? WHERE id = ?',
169
+ [updated.createdAt, JSON.stringify(updated), id],
170
+ );
171
+ return clone(updated);
172
+ },
173
+ async deleteMessage(messageId) {
174
+ return db.run('DELETE FROM capek_messages WHERE id = ?', [messageId]).changes > 0;
175
+ },
176
+ async listMessagesWithParts(sessionId) {
177
+ return withParts(listRows(sessionId));
178
+ },
179
+ async listLatestMessagesWithPartsPage(sessionId, limit = 50): Promise<TranscriptPageResult> {
180
+ const effectiveLimit = Math.min(Math.max(limit, 1), 100);
181
+ const descending = db.query(
182
+ 'SELECT * FROM capek_messages WHERE session_id = ? ORDER BY sequence DESC, created_at DESC, id DESC LIMIT ?',
183
+ ).all(sessionId, effectiveLimit) as MessageRow[];
184
+ const rows = descending.reverse();
185
+ const oldestSequence = rows[0]?.sequence ?? null;
186
+ const hasOlder = oldestSequence === null ? false : Boolean(db.query(
187
+ 'SELECT 1 FROM capek_messages WHERE session_id = ? AND sequence < ? LIMIT 1',
188
+ ).get(sessionId, oldestSequence));
189
+ return {
190
+ messages: withParts(rows),
191
+ pagination: {
192
+ hasOlder,
193
+ oldestSequence,
194
+ newestSequence: rows.at(-1)?.sequence ?? null,
195
+ limit: effectiveLimit,
196
+ },
197
+ };
198
+ },
199
+ async buildEffectiveContextHistory(sessionId) {
200
+ const rows = listRows(sessionId);
201
+ let boundary: MessageRow | undefined;
202
+ for (let index = rows.length - 1; index >= 0; index -= 1) {
203
+ const message = parse<Message>(rows[index].record);
204
+ if (message.role !== 'assistant' || message.summary !== true || message.mode !== 'compaction' || !message.parentId) continue;
205
+ const trigger = rows.find(row => row.id === message.parentId);
206
+ if (!trigger) continue;
207
+ if (!getPartsByMessage(trigger.id).some(part => part.type === 'compaction')) continue;
208
+ boundary = trigger;
209
+ break;
210
+ }
211
+ const effectiveRows = boundary ? rows.filter(row => row.sequence >= boundary.sequence) : rows;
212
+ return {
213
+ messages: withParts(effectiveRows),
214
+ latestCompactionBoundary: boundary?.id ?? null,
215
+ hasCompaction: Boolean(boundary),
216
+ };
217
+ },
218
+ async createPart(part, sessionId) {
219
+ const message = db.query(
220
+ 'SELECT session_id FROM capek_messages WHERE id = ?',
221
+ ).get(part.messageId) as { session_id: string } | null;
222
+ if (!message || message.session_id !== sessionId) {
223
+ throw new Error(`Message does not exist in session: ${part.messageId}`);
224
+ }
225
+ db.run(
226
+ `INSERT INTO capek_parts (id, message_id, session_id, type, call_id, created_at, record)
227
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
228
+ [part.id, part.messageId, sessionId, part.type, part.type === 'tool' ? part.callId : null, part.createdAt, JSON.stringify(part)],
229
+ );
230
+ return clone(part);
231
+ },
232
+ async getPart(id) {
233
+ const row = db.query('SELECT record FROM capek_parts WHERE id = ?').get(id) as { record: string } | null;
234
+ return row ? parse<Part>(row.record) : null;
235
+ },
236
+ async getPartsByMessage(messageId) {
237
+ return getPartsByMessage(messageId);
238
+ },
239
+ async getPartsBySession(sessionId) {
240
+ return (db.query(
241
+ 'SELECT record FROM capek_parts WHERE session_id = ? ORDER BY created_at ASC, rowid ASC',
242
+ ).all(sessionId) as Array<{ record: string }>).map(row => parse<Part>(row.record));
243
+ },
244
+ async updatePart(id, updates) {
245
+ const current = await store.getPart(id);
246
+ if (!current) return null;
247
+ const updated = { ...current, ...updates } as Part;
248
+ db.run(
249
+ 'UPDATE capek_parts SET type = ?, call_id = ?, created_at = ?, record = ? WHERE id = ?',
250
+ [
251
+ updated.type,
252
+ updated.type === 'tool' ? updated.callId : null,
253
+ updated.createdAt,
254
+ JSON.stringify(updated),
255
+ id,
256
+ ],
257
+ );
258
+ return clone(updated);
259
+ },
260
+ async persistStreamingPartSnapshots(snapshots: StreamingPartSnapshot[]) {
261
+ const transaction = db.transaction((values: StreamingPartSnapshot[]) => {
262
+ const select = db.prepare(
263
+ 'SELECT record FROM capek_parts WHERE id = ? AND message_id = ? AND session_id = ? AND type = ?',
264
+ );
265
+ const update = db.prepare('UPDATE capek_parts SET record = ? WHERE id = ?');
266
+ let count = 0;
267
+ for (const snapshot of values) {
268
+ const row = select.get(snapshot.id, snapshot.messageId, snapshot.sessionId, snapshot.type) as { record: string } | null;
269
+ if (!row) continue;
270
+ const part = { ...parse<Part>(row.record), text: snapshot.text } as Part;
271
+ update.run(JSON.stringify(part), snapshot.id);
272
+ count += 1;
273
+ }
274
+ return count;
275
+ });
276
+ return transaction.immediate(snapshots);
277
+ },
278
+ async transitionToolToRunningByCallId(sessionId, callId, childSessionId) {
279
+ const rows = db.query(
280
+ `SELECT record FROM capek_parts
281
+ WHERE session_id = ? AND call_id = ? AND type = 'tool'
282
+ ORDER BY created_at DESC, rowid DESC`,
283
+ ).all(sessionId, callId) as Array<{ record: string }>;
284
+ const toolPart = rows.map(row => parse<ToolPart>(row.record))
285
+ .find(part => part.state.status === 'pending');
286
+ if (!toolPart) return null;
287
+ return await store.updatePart(toolPart.id, {
288
+ state: {
289
+ status: 'running',
290
+ input: toolPart.state.input,
291
+ startedAt: Date.now(),
292
+ ...(childSessionId ? { childSessionId } : {}),
293
+ },
294
+ }) as ToolPart;
295
+ },
296
+ async transitionToolToInterrupted(partId, reason) {
297
+ const current = await store.getPart(partId);
298
+ if (!current || current.type !== 'tool') return null;
299
+ const now = Date.now();
300
+ return await store.updatePart(partId, {
301
+ state: {
302
+ status: 'interrupted',
303
+ input: current.state.input,
304
+ startedAt: current.state.status === 'running' ? current.state.startedAt : now,
305
+ interruptedAt: now,
306
+ reason,
307
+ ...('childSessionId' in current.state && current.state.childSessionId
308
+ ? { childSessionId: current.state.childSessionId }
309
+ : {}),
310
+ },
311
+ }) as ToolPart;
312
+ },
313
+ close() {
314
+ if (closed) return;
315
+ closed = true;
316
+ db.close();
317
+ },
318
+ };
319
+
320
+ return store;
321
+ }
@@ -0,0 +1,75 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import type {
3
+ CreateToolOutputArtifact,
4
+ ToolOutputArtifact,
5
+ ToolOutputArtifactPage,
6
+ ToolOutputArtifactStore,
7
+ } from './contracts';
8
+
9
+ export const DEFAULT_TOOL_OUTPUT_PAGE_CHARS = 10_000;
10
+ export const MAX_TOOL_OUTPUT_PAGE_CHARS = 20_000;
11
+
12
+ export function isToolOutputArtifactId(value: string): boolean {
13
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
14
+ }
15
+
16
+ function copy<T>(value: T): T {
17
+ return structuredClone(value);
18
+ }
19
+
20
+ export function buildToolOutputArtifactPage(
21
+ artifact: ToolOutputArtifact,
22
+ offset = 0,
23
+ limit = DEFAULT_TOOL_OUTPUT_PAGE_CHARS,
24
+ ): ToolOutputArtifactPage {
25
+ const safeOffset = Number.isInteger(offset) && offset >= 0 ? Math.min(offset, artifact.size) : 0;
26
+ const safeLimit = Number.isInteger(limit) && limit > 0
27
+ ? Math.min(limit, MAX_TOOL_OUTPUT_PAGE_CHARS)
28
+ : DEFAULT_TOOL_OUTPUT_PAGE_CHARS;
29
+ const content = artifact.content.slice(safeOffset, safeOffset + safeLimit);
30
+ const consumed = safeOffset + content.length;
31
+ const complete = consumed >= artifact.size;
32
+ return {
33
+ artifactId: artifact.id,
34
+ toolCallId: artifact.toolCallId,
35
+ toolName: artifact.toolName,
36
+ format: artifact.format,
37
+ content,
38
+ offset: safeOffset,
39
+ limit: safeLimit,
40
+ totalChars: artifact.size,
41
+ nextOffset: complete ? null : consumed,
42
+ complete,
43
+ };
44
+ }
45
+
46
+ export function createArtifact(input: CreateToolOutputArtifact): ToolOutputArtifact {
47
+ return {
48
+ id: randomUUID(),
49
+ sessionId: input.sessionId,
50
+ ...(input.workspaceId ? { workspaceId: input.workspaceId } : {}),
51
+ toolCallId: input.toolCallId,
52
+ toolName: input.toolName,
53
+ content: input.content,
54
+ format: input.format,
55
+ size: input.content.length,
56
+ createdAt: Date.now(),
57
+ };
58
+ }
59
+
60
+ export function createInMemoryToolOutputArtifactStore(): ToolOutputArtifactStore {
61
+ const artifacts = new Map<string, ToolOutputArtifact>();
62
+ return {
63
+ async create(input) {
64
+ const artifact = createArtifact(input);
65
+ artifacts.set(artifact.id, copy(artifact));
66
+ return copy(artifact);
67
+ },
68
+ async getPage(sessionId, artifactId, offset, limit) {
69
+ if (!isToolOutputArtifactId(artifactId)) return null;
70
+ const artifact = artifacts.get(artifactId);
71
+ if (!artifact || artifact.sessionId !== sessionId) return null;
72
+ return buildToolOutputArtifactPage(artifact, offset, limit);
73
+ },
74
+ };
75
+ }
package/src/storage.ts ADDED
@@ -0,0 +1,31 @@
1
+ export type * from './storage/contracts';
2
+ export {
3
+ createInMemoryConversationStore,
4
+ createInMemoryMessageQueueStore,
5
+ createInMemoryStorageBundle,
6
+ type InMemoryAuxiliaryRecords,
7
+ } from './storage/memory';
8
+ export {
9
+ createSqliteConversationStore,
10
+ type SqliteConversationStore,
11
+ } from './storage/sqlite';
12
+ export { createAgentStorage, type AgentStorage, type AgentStorageOption } from './storage/options';
13
+ export {
14
+ buildToolOutputArtifactPage,
15
+ createArtifact,
16
+ createInMemoryToolOutputArtifactStore,
17
+ DEFAULT_TOOL_OUTPUT_PAGE_CHARS,
18
+ isToolOutputArtifactId,
19
+ MAX_TOOL_OUTPUT_PAGE_CHARS,
20
+ } from './storage/tool-output-artifacts';
21
+ export {
22
+ createSqliteToolOutputArtifactStore,
23
+ type SqliteToolOutputArtifactStore,
24
+ } from './storage/sqlite-tool-output-artifacts';
25
+ export {
26
+ configureStorage,
27
+ createToolOutputArtifact,
28
+ getStorage,
29
+ getToolOutputArtifactPage,
30
+ withStorage,
31
+ } from './storage/runtime';