@di-code/coding-agent 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.d.ts +26 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +117 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/models-loader.d.ts +4 -0
- package/dist/config/models-loader.d.ts.map +1 -0
- package/dist/config/models-loader.js +134 -0
- package/dist/config/models-loader.js.map +1 -0
- package/dist/config/models-types.d.ts +23 -0
- package/dist/config/models-types.d.ts.map +1 -0
- package/dist/config/models-types.js +2 -0
- package/dist/config/models-types.js.map +1 -0
- package/dist/config/settings-loader.d.ts +3 -0
- package/dist/config/settings-loader.d.ts.map +1 -0
- package/dist/config/settings-loader.js +130 -0
- package/dist/config/settings-loader.js.map +1 -0
- package/dist/config/settings-types.d.ts +21 -0
- package/dist/config/settings-types.d.ts.map +1 -0
- package/dist/config/settings-types.js +2 -0
- package/dist/config/settings-types.js.map +1 -0
- package/dist/core/context-builder.d.ts +8 -0
- package/dist/core/context-builder.d.ts.map +1 -0
- package/dist/core/context-builder.js +46 -0
- package/dist/core/context-builder.js.map +1 -0
- package/dist/core/model-catalog-store.d.ts +8 -0
- package/dist/core/model-catalog-store.d.ts.map +1 -0
- package/dist/core/model-catalog-store.js +68 -0
- package/dist/core/model-catalog-store.js.map +1 -0
- package/dist/core/model-composer.d.ts +4 -0
- package/dist/core/model-composer.d.ts.map +1 -0
- package/dist/core/model-composer.js +37 -0
- package/dist/core/model-composer.js.map +1 -0
- package/dist/core/session/session-manager.d.ts +44 -0
- package/dist/core/session/session-manager.d.ts.map +1 -0
- package/dist/core/session/session-manager.js +138 -0
- package/dist/core/session/session-manager.js.map +1 -0
- package/dist/core/session/session-storage.d.ts +11 -0
- package/dist/core/session/session-storage.d.ts.map +1 -0
- package/dist/core/session/session-storage.js +382 -0
- package/dist/core/session/session-storage.js.map +1 -0
- package/dist/core/session/types.d.ts +65 -0
- package/dist/core/session/types.d.ts.map +1 -0
- package/dist/core/session/types.js +28 -0
- package/dist/core/session/types.js.map +1 -0
- package/dist/core/session.d.ts +80 -0
- package/dist/core/session.d.ts.map +1 -0
- package/dist/core/session.js +262 -0
- package/dist/core/session.js.map +1 -0
- package/dist/core/tools/bash.d.ts +39 -0
- package/dist/core/tools/bash.d.ts.map +1 -0
- package/dist/core/tools/bash.js +208 -0
- package/dist/core/tools/bash.js.map +1 -0
- package/dist/core/tools/edit.d.ts +18 -0
- package/dist/core/tools/edit.d.ts.map +1 -0
- package/dist/core/tools/edit.js +107 -0
- package/dist/core/tools/edit.js.map +1 -0
- package/dist/core/tools/file-mutation-queue.d.ts +2 -0
- package/dist/core/tools/file-mutation-queue.d.ts.map +1 -0
- package/dist/core/tools/file-mutation-queue.js +24 -0
- package/dist/core/tools/file-mutation-queue.js.map +1 -0
- package/dist/core/tools/path-boundary.d.ts +3 -0
- package/dist/core/tools/path-boundary.d.ts.map +1 -0
- package/dist/core/tools/path-boundary.js +62 -0
- package/dist/core/tools/path-boundary.js.map +1 -0
- package/dist/core/tools/read.d.ts +17 -0
- package/dist/core/tools/read.d.ts.map +1 -0
- package/dist/core/tools/read.js +132 -0
- package/dist/core/tools/read.js.map +1 -0
- package/dist/core/tools/write.d.ts +17 -0
- package/dist/core/tools/write.d.ts.map +1 -0
- package/dist/core/tools/write.js +45 -0
- package/dist/core/tools/write.js.map +1 -0
- package/dist/entry.d.ts +3 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +27 -0
- package/dist/entry.js.map +1 -0
- package/dist/extensions/index.d.ts +5 -0
- package/dist/extensions/index.d.ts.map +1 -0
- package/dist/extensions/index.js +3 -0
- package/dist/extensions/index.js.map +1 -0
- package/dist/extensions/runtime.d.ts +46 -0
- package/dist/extensions/runtime.d.ts.map +1 -0
- package/dist/extensions/runtime.js +179 -0
- package/dist/extensions/runtime.js.map +1 -0
- package/dist/extensions/trust.d.ts +8 -0
- package/dist/extensions/trust.d.ts.map +1 -0
- package/dist/extensions/trust.js +59 -0
- package/dist/extensions/trust.js.map +1 -0
- package/dist/extensions/types.d.ts +46 -0
- package/dist/extensions/types.d.ts.map +1 -0
- package/dist/extensions/types.js +2 -0
- package/dist/extensions/types.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/main.d.ts +19 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +198 -0
- package/dist/main.js.map +1 -0
- package/dist/modes/interactive-components.d.ts +41 -0
- package/dist/modes/interactive-components.d.ts.map +1 -0
- package/dist/modes/interactive-components.js +195 -0
- package/dist/modes/interactive-components.js.map +1 -0
- package/dist/modes/interactive-layout.d.ts +20 -0
- package/dist/modes/interactive-layout.d.ts.map +1 -0
- package/dist/modes/interactive-layout.js +34 -0
- package/dist/modes/interactive-layout.js.map +1 -0
- package/dist/modes/interactive-state.d.ts +61 -0
- package/dist/modes/interactive-state.d.ts.map +1 -0
- package/dist/modes/interactive-state.js +201 -0
- package/dist/modes/interactive-state.js.map +1 -0
- package/dist/modes/interactive.d.ts +61 -0
- package/dist/modes/interactive.d.ts.map +1 -0
- package/dist/modes/interactive.js +436 -0
- package/dist/modes/interactive.js.map +1 -0
- package/dist/modes/json.d.ts +14 -0
- package/dist/modes/json.d.ts.map +1 -0
- package/dist/modes/json.js +26 -0
- package/dist/modes/json.js.map +1 -0
- package/dist/modes/print.d.ts +10 -0
- package/dist/modes/print.d.ts.map +1 -0
- package/dist/modes/print.js +28 -0
- package/dist/modes/print.js.map +1 -0
- package/dist/provider-onboarding.d.ts +13 -0
- package/dist/provider-onboarding.d.ts.map +1 -0
- package/dist/provider-onboarding.js +162 -0
- package/dist/provider-onboarding.js.map +1 -0
- package/dist/rpc/client.d.ts +38 -0
- package/dist/rpc/client.d.ts.map +1 -0
- package/dist/rpc/client.js +153 -0
- package/dist/rpc/client.js.map +1 -0
- package/dist/rpc/index.d.ts +5 -0
- package/dist/rpc/index.d.ts.map +1 -0
- package/dist/rpc/index.js +5 -0
- package/dist/rpc/index.js.map +1 -0
- package/dist/rpc/jsonl.d.ts +17 -0
- package/dist/rpc/jsonl.d.ts.map +1 -0
- package/dist/rpc/jsonl.js +52 -0
- package/dist/rpc/jsonl.js.map +1 -0
- package/dist/rpc/protocol.d.ts +80 -0
- package/dist/rpc/protocol.d.ts.map +1 -0
- package/dist/rpc/protocol.js +231 -0
- package/dist/rpc/protocol.js.map +1 -0
- package/dist/rpc/server.d.ts +41 -0
- package/dist/rpc/server.d.ts.map +1 -0
- package/dist/rpc/server.js +186 -0
- package/dist/rpc/server.js.map +1 -0
- package/dist/rpc-entry.d.ts +3 -0
- package/dist/rpc-entry.d.ts.map +1 -0
- package/dist/rpc-entry.js +28 -0
- package/dist/rpc-entry.js.map +1 -0
- package/dist/startup.d.ts +24 -0
- package/dist/startup.d.ts.map +1 -0
- package/dist/startup.js +227 -0
- package/dist/startup.js.map +1 -0
- package/package.json +43 -0
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import { appendFile, mkdir, open, readFile, unlink } from "node:fs/promises";
|
|
2
|
+
import { dirname, posix, win32 } from "node:path";
|
|
3
|
+
import { SESSION_FORMAT_VERSION, SessionLoadError, SessionWriteError, } from "./types.js";
|
|
4
|
+
function isObject(value) {
|
|
5
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6
|
+
}
|
|
7
|
+
function isNonEmptyString(value) {
|
|
8
|
+
return typeof value === "string" && value.length > 0;
|
|
9
|
+
}
|
|
10
|
+
function isNonNegativeNumber(value) {
|
|
11
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
12
|
+
}
|
|
13
|
+
function isTimestamp(value) {
|
|
14
|
+
return Number.isSafeInteger(value) && isNonNegativeNumber(value);
|
|
15
|
+
}
|
|
16
|
+
function isIsoTimestamp(value) {
|
|
17
|
+
if (typeof value !== "string")
|
|
18
|
+
return false;
|
|
19
|
+
const milliseconds = Date.parse(value);
|
|
20
|
+
return Number.isFinite(milliseconds) && new Date(milliseconds).toISOString() === value;
|
|
21
|
+
}
|
|
22
|
+
function isRecordId(value) {
|
|
23
|
+
return typeof value === "string" && /^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/.test(value);
|
|
24
|
+
}
|
|
25
|
+
function isAbsolutePath(value) {
|
|
26
|
+
return typeof value === "string" && (win32.isAbsolute(value) || posix.isAbsolute(value));
|
|
27
|
+
}
|
|
28
|
+
function isJsonValue(value) {
|
|
29
|
+
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
30
|
+
return true;
|
|
31
|
+
if (typeof value === "number")
|
|
32
|
+
return Number.isFinite(value);
|
|
33
|
+
if (Array.isArray(value))
|
|
34
|
+
return value.every(isJsonValue);
|
|
35
|
+
return isObject(value) && Object.values(value).every(isJsonValue);
|
|
36
|
+
}
|
|
37
|
+
function isProviderReplay(value) {
|
|
38
|
+
return isObject(value) && isNonEmptyString(value.api) && isJsonValue(value.data);
|
|
39
|
+
}
|
|
40
|
+
function isImageContent(value) {
|
|
41
|
+
return (isObject(value) && value.type === "image" && typeof value.data === "string" && isNonEmptyString(value.mimeType));
|
|
42
|
+
}
|
|
43
|
+
function isTextContent(value) {
|
|
44
|
+
return isObject(value) && value.type === "text" && typeof value.text === "string";
|
|
45
|
+
}
|
|
46
|
+
function isUserContent(value) {
|
|
47
|
+
return isTextContent(value) || isImageContent(value);
|
|
48
|
+
}
|
|
49
|
+
function isAssistantContent(value) {
|
|
50
|
+
if (!isObject(value))
|
|
51
|
+
return false;
|
|
52
|
+
switch (value.type) {
|
|
53
|
+
case "text":
|
|
54
|
+
return typeof value.text === "string";
|
|
55
|
+
case "thinking":
|
|
56
|
+
return typeof value.thinking === "string";
|
|
57
|
+
case "tool_call":
|
|
58
|
+
return (isNonEmptyString(value.id) &&
|
|
59
|
+
isNonEmptyString(value.name) &&
|
|
60
|
+
isObject(value.arguments) &&
|
|
61
|
+
isJsonValue(value.arguments));
|
|
62
|
+
default:
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function isToolResultContent(value) {
|
|
67
|
+
return isTextContent(value) || isImageContent(value);
|
|
68
|
+
}
|
|
69
|
+
function isUsage(value) {
|
|
70
|
+
if (!isObject(value) || !isObject(value.cost))
|
|
71
|
+
return false;
|
|
72
|
+
return (isNonNegativeNumber(value.input) &&
|
|
73
|
+
isNonNegativeNumber(value.output) &&
|
|
74
|
+
isNonNegativeNumber(value.cacheRead) &&
|
|
75
|
+
isNonNegativeNumber(value.cacheWrite) &&
|
|
76
|
+
isNonNegativeNumber(value.totalTokens) &&
|
|
77
|
+
isNonNegativeNumber(value.cost.input) &&
|
|
78
|
+
isNonNegativeNumber(value.cost.output) &&
|
|
79
|
+
isNonNegativeNumber(value.cost.cacheRead) &&
|
|
80
|
+
isNonNegativeNumber(value.cost.cacheWrite) &&
|
|
81
|
+
isNonNegativeNumber(value.cost.total));
|
|
82
|
+
}
|
|
83
|
+
function isAssistantMessage(value) {
|
|
84
|
+
if (value.role !== "assistant" ||
|
|
85
|
+
!Array.isArray(value.content) ||
|
|
86
|
+
!value.content.every(isAssistantContent) ||
|
|
87
|
+
!isNonEmptyString(value.provider) ||
|
|
88
|
+
!isNonEmptyString(value.model) ||
|
|
89
|
+
(value.providerReplay !== undefined && !isProviderReplay(value.providerReplay)) ||
|
|
90
|
+
!isUsage(value.usage) ||
|
|
91
|
+
!isTimestamp(value.timestamp)) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
if (value.stopReason === "stop" || value.stopReason === "length" || value.stopReason === "tool_use") {
|
|
95
|
+
return value.errorMessage === undefined;
|
|
96
|
+
}
|
|
97
|
+
if (value.stopReason === "error" || value.stopReason === "aborted") {
|
|
98
|
+
return typeof value.errorMessage === "string";
|
|
99
|
+
}
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
function isMessage(value) {
|
|
103
|
+
if (!isObject(value) || !isTimestamp(value.timestamp))
|
|
104
|
+
return false;
|
|
105
|
+
switch (value.role) {
|
|
106
|
+
case "user":
|
|
107
|
+
return Array.isArray(value.content) && value.content.every(isUserContent);
|
|
108
|
+
case "assistant":
|
|
109
|
+
return isAssistantMessage(value);
|
|
110
|
+
case "tool_result":
|
|
111
|
+
return (isNonEmptyString(value.toolCallId) &&
|
|
112
|
+
isNonEmptyString(value.toolName) &&
|
|
113
|
+
Array.isArray(value.content) &&
|
|
114
|
+
value.content.every(isToolResultContent) &&
|
|
115
|
+
typeof value.isError === "boolean");
|
|
116
|
+
default:
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function decodeHeader(value, filePath) {
|
|
121
|
+
if (!isObject(value) || value.type !== "session") {
|
|
122
|
+
throw new SessionLoadError("INVALID_HEADER", filePath, `Invalid session header in "${filePath}".`, {
|
|
123
|
+
lineNumber: 1,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
if (!("version" in value)) {
|
|
127
|
+
throw new SessionLoadError("INVALID_HEADER", filePath, `Session header has no version in "${filePath}".`, {
|
|
128
|
+
lineNumber: 1,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
if (value.version !== SESSION_FORMAT_VERSION) {
|
|
132
|
+
throw new SessionLoadError("UNSUPPORTED_VERSION", filePath, `Unsupported session version ${JSON.stringify(value.version)} in "${filePath}".`, { lineNumber: 1 });
|
|
133
|
+
}
|
|
134
|
+
if (!isRecordId(value.id) ||
|
|
135
|
+
value.parentId !== null ||
|
|
136
|
+
!isIsoTimestamp(value.timestamp) ||
|
|
137
|
+
!isAbsolutePath(value.cwd)) {
|
|
138
|
+
throw new SessionLoadError("INVALID_HEADER", filePath, `Invalid session header fields in "${filePath}".`, {
|
|
139
|
+
lineNumber: 1,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
return value;
|
|
143
|
+
}
|
|
144
|
+
function decodeSessionEntry(value) {
|
|
145
|
+
if (!isObject(value) || (value.type !== "message" && value.type !== "summary")) {
|
|
146
|
+
return { reason: 'record type must be "message" or "summary"' };
|
|
147
|
+
}
|
|
148
|
+
if (value.version !== SESSION_FORMAT_VERSION)
|
|
149
|
+
return { reason: "record version must be 1" };
|
|
150
|
+
if (!isRecordId(value.id))
|
|
151
|
+
return { reason: "record id is invalid" };
|
|
152
|
+
if (!isRecordId(value.parentId))
|
|
153
|
+
return { reason: "record parentId is invalid" };
|
|
154
|
+
if (!isIsoTimestamp(value.timestamp))
|
|
155
|
+
return { reason: "record timestamp is not canonical ISO 8601 UTC" };
|
|
156
|
+
if (value.type === "message") {
|
|
157
|
+
if (!isMessage(value.message))
|
|
158
|
+
return { reason: "record message does not match the Message contract" };
|
|
159
|
+
return { entry: value };
|
|
160
|
+
}
|
|
161
|
+
if (typeof value.summary !== "string" || value.summary.trim().length === 0) {
|
|
162
|
+
return { reason: "record summary must be a non-empty string" };
|
|
163
|
+
}
|
|
164
|
+
if (!isRecordId(value.firstKeptEntryId))
|
|
165
|
+
return { reason: "record firstKeptEntryId is invalid" };
|
|
166
|
+
if (!Number.isSafeInteger(value.tokensBefore) || !isNonNegativeNumber(value.tokensBefore)) {
|
|
167
|
+
return { reason: "record tokensBefore must be a non-negative safe integer" };
|
|
168
|
+
}
|
|
169
|
+
return { entry: value };
|
|
170
|
+
}
|
|
171
|
+
function withoutTrailingCarriageReturn(line) {
|
|
172
|
+
return line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
173
|
+
}
|
|
174
|
+
function errorText(cause) {
|
|
175
|
+
return cause instanceof Error ? cause.message : String(cause);
|
|
176
|
+
}
|
|
177
|
+
export async function loadSessionFile(filePath) {
|
|
178
|
+
let content;
|
|
179
|
+
try {
|
|
180
|
+
content = await readFile(filePath, "utf8");
|
|
181
|
+
}
|
|
182
|
+
catch (cause) {
|
|
183
|
+
throw new SessionLoadError("READ_FAILED", filePath, `Failed to read session file "${filePath}".`, { cause });
|
|
184
|
+
}
|
|
185
|
+
const physicalLines = content.split("\n");
|
|
186
|
+
let partialLineNumber;
|
|
187
|
+
if (content.endsWith("\n")) {
|
|
188
|
+
physicalLines.pop();
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
physicalLines.pop();
|
|
192
|
+
partialLineNumber = physicalLines.length + 1;
|
|
193
|
+
}
|
|
194
|
+
if (physicalLines.length === 0) {
|
|
195
|
+
throw new SessionLoadError("INVALID_HEADER", filePath, `Session file has no committed header: "${filePath}".`, {
|
|
196
|
+
lineNumber: 1,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
let headerValue;
|
|
200
|
+
try {
|
|
201
|
+
headerValue = JSON.parse(withoutTrailingCarriageReturn(physicalLines[0] ?? ""));
|
|
202
|
+
}
|
|
203
|
+
catch (cause) {
|
|
204
|
+
throw new SessionLoadError("INVALID_HEADER", filePath, `Session header is not valid JSON in "${filePath}".`, {
|
|
205
|
+
lineNumber: 1,
|
|
206
|
+
cause,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
const header = decodeHeader(headerValue, filePath);
|
|
210
|
+
const entries = [];
|
|
211
|
+
const diagnostics = [];
|
|
212
|
+
const seenIds = new Set([header.id]);
|
|
213
|
+
const messageEntryIds = new Set();
|
|
214
|
+
let expectedParentId = header.id;
|
|
215
|
+
for (let index = 1; index < physicalLines.length; index++) {
|
|
216
|
+
const lineNumber = index + 1;
|
|
217
|
+
try {
|
|
218
|
+
const value = JSON.parse(withoutTrailingCarriageReturn(physicalLines[index] ?? ""));
|
|
219
|
+
const decoded = decodeSessionEntry(value);
|
|
220
|
+
if (!decoded.entry)
|
|
221
|
+
throw new Error(decoded.reason ?? "Invalid session message record.");
|
|
222
|
+
if (seenIds.has(decoded.entry.id))
|
|
223
|
+
throw new Error("record id is duplicated");
|
|
224
|
+
if (decoded.entry.parentId !== expectedParentId) {
|
|
225
|
+
throw new Error(`record parentId must be "${expectedParentId}"`);
|
|
226
|
+
}
|
|
227
|
+
if (decoded.entry.type === "summary" && !messageEntryIds.has(decoded.entry.firstKeptEntryId)) {
|
|
228
|
+
throw new Error("record firstKeptEntryId must reference an earlier message entry");
|
|
229
|
+
}
|
|
230
|
+
entries.push(decoded.entry);
|
|
231
|
+
seenIds.add(decoded.entry.id);
|
|
232
|
+
if (decoded.entry.type === "message")
|
|
233
|
+
messageEntryIds.add(decoded.entry.id);
|
|
234
|
+
expectedParentId = decoded.entry.id;
|
|
235
|
+
}
|
|
236
|
+
catch (cause) {
|
|
237
|
+
diagnostics.push({ kind: "corrupt_record", lineNumber, reason: errorText(cause) });
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (diagnostics.length === 0 && partialLineNumber !== undefined) {
|
|
242
|
+
diagnostics.push({
|
|
243
|
+
kind: "trailing_partial_line",
|
|
244
|
+
lineNumber: partialLineNumber,
|
|
245
|
+
reason: "final line has no newline commit marker",
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
return {
|
|
249
|
+
header,
|
|
250
|
+
entries,
|
|
251
|
+
messages: entries
|
|
252
|
+
.filter((entry) => entry.type === "message")
|
|
253
|
+
.map((entry) => entry.message),
|
|
254
|
+
diagnostics,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
const DEFAULT_LOCK_TIMEOUT_MS = 5_000;
|
|
258
|
+
const DEFAULT_LOCK_RETRY_MS = 25;
|
|
259
|
+
function isNodeError(cause) {
|
|
260
|
+
return cause instanceof Error && "code" in cause;
|
|
261
|
+
}
|
|
262
|
+
function delay(milliseconds) {
|
|
263
|
+
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
264
|
+
}
|
|
265
|
+
export async function createSessionFile(filePath, header) {
|
|
266
|
+
const headerSnapshot = structuredClone(header);
|
|
267
|
+
try {
|
|
268
|
+
decodeHeader(headerSnapshot, filePath);
|
|
269
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
270
|
+
const handle = await open(filePath, "wx");
|
|
271
|
+
try {
|
|
272
|
+
await handle.writeFile(`${JSON.stringify(headerSnapshot)}\n`, "utf8");
|
|
273
|
+
}
|
|
274
|
+
finally {
|
|
275
|
+
await handle.close();
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
catch (cause) {
|
|
279
|
+
if (cause instanceof SessionWriteError)
|
|
280
|
+
throw cause;
|
|
281
|
+
throw new SessionWriteError("CREATE_FAILED", filePath, `Failed to create session file "${filePath}".`, {
|
|
282
|
+
cause,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
async function acquireLock(filePath, options) {
|
|
287
|
+
const lockPath = `${filePath}.lock`;
|
|
288
|
+
const timeout = options.lockTimeoutMs ?? DEFAULT_LOCK_TIMEOUT_MS;
|
|
289
|
+
const retry = options.lockRetryMs ?? DEFAULT_LOCK_RETRY_MS;
|
|
290
|
+
const now = options.now ?? Date.now;
|
|
291
|
+
const sleep = options.sleep ?? delay;
|
|
292
|
+
const startedAt = now();
|
|
293
|
+
while (true) {
|
|
294
|
+
try {
|
|
295
|
+
return { handle: await open(lockPath, "wx"), lockPath };
|
|
296
|
+
}
|
|
297
|
+
catch (cause) {
|
|
298
|
+
if (!isNodeError(cause) || cause.code !== "EEXIST") {
|
|
299
|
+
throw new SessionWriteError("APPEND_FAILED", filePath, `Failed to acquire session lock for "${filePath}".`, {
|
|
300
|
+
cause,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
if (now() - startedAt >= timeout) {
|
|
304
|
+
throw new SessionWriteError("LOCK_TIMEOUT", filePath, `Timed out waiting for session lock "${lockPath}".`, {
|
|
305
|
+
cause,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
await sleep(retry);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
async function releaseLock(handle, lockPath) {
|
|
313
|
+
let closeError;
|
|
314
|
+
try {
|
|
315
|
+
await handle.close();
|
|
316
|
+
}
|
|
317
|
+
catch (cause) {
|
|
318
|
+
closeError = cause;
|
|
319
|
+
}
|
|
320
|
+
try {
|
|
321
|
+
await unlink(lockPath);
|
|
322
|
+
}
|
|
323
|
+
catch (cause) {
|
|
324
|
+
if (!isNodeError(cause) || cause.code !== "ENOENT")
|
|
325
|
+
throw cause;
|
|
326
|
+
}
|
|
327
|
+
if (closeError !== undefined)
|
|
328
|
+
throw closeError;
|
|
329
|
+
}
|
|
330
|
+
export async function appendSessionEntry(filePath, entry, expectedParentId, options = {}) {
|
|
331
|
+
const entrySnapshot = structuredClone(entry);
|
|
332
|
+
const decodedEntry = decodeSessionEntry(entrySnapshot);
|
|
333
|
+
if (!decodedEntry.entry) {
|
|
334
|
+
throw new SessionWriteError("APPEND_FAILED", filePath, `Refusing to append an invalid session entry to "${filePath}": ${decodedEntry.reason ?? "invalid record"}.`);
|
|
335
|
+
}
|
|
336
|
+
const { handle, lockPath } = await acquireLock(filePath, options);
|
|
337
|
+
let operationError;
|
|
338
|
+
try {
|
|
339
|
+
const loaded = await loadSessionFile(filePath);
|
|
340
|
+
if (loaded.diagnostics.length > 0) {
|
|
341
|
+
throw new SessionWriteError("CORRUPT_SESSION", filePath, `Refusing to append to a session with recovery diagnostics: "${filePath}".`);
|
|
342
|
+
}
|
|
343
|
+
const actualParentId = loaded.entries.at(-1)?.id ?? loaded.header.id;
|
|
344
|
+
if (entrySnapshot.type === "summary" &&
|
|
345
|
+
!loaded.entries.some((existing) => existing.type === "message" && existing.id === entrySnapshot.firstKeptEntryId)) {
|
|
346
|
+
throw new SessionWriteError("APPEND_FAILED", filePath, `Refusing to append a summary with an invalid firstKeptEntryId to "${filePath}".`);
|
|
347
|
+
}
|
|
348
|
+
if (loaded.header.id === entrySnapshot.id || loaded.entries.some((existing) => existing.id === entrySnapshot.id)) {
|
|
349
|
+
throw new SessionWriteError("APPEND_FAILED", filePath, `Refusing to append a duplicate session record id to "${filePath}".`);
|
|
350
|
+
}
|
|
351
|
+
if (actualParentId !== expectedParentId || entrySnapshot.parentId !== expectedParentId) {
|
|
352
|
+
throw new SessionWriteError("CONCURRENT_MODIFICATION", filePath, `Session leaf changed before append in "${filePath}".`, { expectedParentId, actualParentId });
|
|
353
|
+
}
|
|
354
|
+
await appendFile(filePath, `${JSON.stringify(entrySnapshot)}\n`, "utf8");
|
|
355
|
+
}
|
|
356
|
+
catch (cause) {
|
|
357
|
+
operationError = cause;
|
|
358
|
+
}
|
|
359
|
+
finally {
|
|
360
|
+
try {
|
|
361
|
+
await releaseLock(handle, lockPath);
|
|
362
|
+
}
|
|
363
|
+
catch (cause) {
|
|
364
|
+
operationError ??= new SessionWriteError("APPEND_FAILED", filePath, `Failed to release session lock for "${filePath}".`, { cause });
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
if (operationError !== undefined) {
|
|
368
|
+
if (operationError instanceof SessionWriteError) {
|
|
369
|
+
throw operationError;
|
|
370
|
+
}
|
|
371
|
+
if (operationError instanceof SessionLoadError) {
|
|
372
|
+
const code = operationError.code === "READ_FAILED" ? "APPEND_FAILED" : "CORRUPT_SESSION";
|
|
373
|
+
throw new SessionWriteError(code, filePath, `Unable to load session before append: "${filePath}".`, {
|
|
374
|
+
cause: operationError,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
throw new SessionWriteError("APPEND_FAILED", filePath, `Failed to append session entry to "${filePath}".`, {
|
|
378
|
+
cause: operationError,
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
//# sourceMappingURL=session-storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-storage.js","sourceRoot":"","sources":["../../../src/core/session/session-storage.ts"],"names":[],"mappings":"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","sourcesContent":["import { appendFile, mkdir, open, readFile, unlink } from \"node:fs/promises\";\nimport { dirname, posix, win32 } from \"node:path\";\nimport type { AssistantContent, AssistantMessage, Message, ToolResultContent, Usage, UserContent } from \"@di-code/ai\";\nimport {\n\ttype LoadedSession,\n\tSESSION_FORMAT_VERSION,\n\ttype SessionEntry,\n\ttype SessionHeader,\n\tSessionLoadError,\n\ttype SessionMessageEntry,\n\ttype SessionSummaryEntry,\n\tSessionWriteError,\n} from \"./types.ts\";\n\ntype JsonObject = Record<string, unknown>;\n\nfunction isObject(value: unknown): value is JsonObject {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n\treturn typeof value === \"string\" && value.length > 0;\n}\n\nfunction isNonNegativeNumber(value: unknown): value is number {\n\treturn typeof value === \"number\" && Number.isFinite(value) && value >= 0;\n}\n\nfunction isTimestamp(value: unknown): value is number {\n\treturn Number.isSafeInteger(value) && isNonNegativeNumber(value);\n}\n\nfunction isIsoTimestamp(value: unknown): value is string {\n\tif (typeof value !== \"string\") return false;\n\tconst milliseconds = Date.parse(value);\n\treturn Number.isFinite(milliseconds) && new Date(milliseconds).toISOString() === value;\n}\n\nfunction isRecordId(value: unknown): value is string {\n\treturn typeof value === \"string\" && /^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/.test(value);\n}\n\nfunction isAbsolutePath(value: unknown): value is string {\n\treturn typeof value === \"string\" && (win32.isAbsolute(value) || posix.isAbsolute(value));\n}\n\nfunction isJsonValue(value: unknown): boolean {\n\tif (value === null || typeof value === \"string\" || typeof value === \"boolean\") return true;\n\tif (typeof value === \"number\") return Number.isFinite(value);\n\tif (Array.isArray(value)) return value.every(isJsonValue);\n\treturn isObject(value) && Object.values(value).every(isJsonValue);\n}\n\nfunction isProviderReplay(value: unknown): boolean {\n\treturn isObject(value) && isNonEmptyString(value.api) && isJsonValue(value.data);\n}\n\nfunction isImageContent(value: unknown): value is Extract<UserContent | ToolResultContent, { type: \"image\" }> {\n\treturn (\n\t\tisObject(value) && value.type === \"image\" && typeof value.data === \"string\" && isNonEmptyString(value.mimeType)\n\t);\n}\n\nfunction isTextContent(value: unknown): value is Extract<UserContent, { type: \"text\" }> {\n\treturn isObject(value) && value.type === \"text\" && typeof value.text === \"string\";\n}\n\nfunction isUserContent(value: unknown): value is UserContent {\n\treturn isTextContent(value) || isImageContent(value);\n}\n\nfunction isAssistantContent(value: unknown): value is AssistantContent {\n\tif (!isObject(value)) return false;\n\tswitch (value.type) {\n\t\tcase \"text\":\n\t\t\treturn typeof value.text === \"string\";\n\t\tcase \"thinking\":\n\t\t\treturn typeof value.thinking === \"string\";\n\t\tcase \"tool_call\":\n\t\t\treturn (\n\t\t\t\tisNonEmptyString(value.id) &&\n\t\t\t\tisNonEmptyString(value.name) &&\n\t\t\t\tisObject(value.arguments) &&\n\t\t\t\tisJsonValue(value.arguments)\n\t\t\t);\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\nfunction isToolResultContent(value: unknown): value is ToolResultContent {\n\treturn isTextContent(value) || isImageContent(value);\n}\n\nfunction isUsage(value: unknown): value is Usage {\n\tif (!isObject(value) || !isObject(value.cost)) return false;\n\treturn (\n\t\tisNonNegativeNumber(value.input) &&\n\t\tisNonNegativeNumber(value.output) &&\n\t\tisNonNegativeNumber(value.cacheRead) &&\n\t\tisNonNegativeNumber(value.cacheWrite) &&\n\t\tisNonNegativeNumber(value.totalTokens) &&\n\t\tisNonNegativeNumber(value.cost.input) &&\n\t\tisNonNegativeNumber(value.cost.output) &&\n\t\tisNonNegativeNumber(value.cost.cacheRead) &&\n\t\tisNonNegativeNumber(value.cost.cacheWrite) &&\n\t\tisNonNegativeNumber(value.cost.total)\n\t);\n}\n\nfunction isAssistantMessage(value: JsonObject): value is JsonObject & AssistantMessage {\n\tif (\n\t\tvalue.role !== \"assistant\" ||\n\t\t!Array.isArray(value.content) ||\n\t\t!value.content.every(isAssistantContent) ||\n\t\t!isNonEmptyString(value.provider) ||\n\t\t!isNonEmptyString(value.model) ||\n\t\t(value.providerReplay !== undefined && !isProviderReplay(value.providerReplay)) ||\n\t\t!isUsage(value.usage) ||\n\t\t!isTimestamp(value.timestamp)\n\t) {\n\t\treturn false;\n\t}\n\n\tif (value.stopReason === \"stop\" || value.stopReason === \"length\" || value.stopReason === \"tool_use\") {\n\t\treturn value.errorMessage === undefined;\n\t}\n\tif (value.stopReason === \"error\" || value.stopReason === \"aborted\") {\n\t\treturn typeof value.errorMessage === \"string\";\n\t}\n\treturn false;\n}\n\nfunction isMessage(value: unknown): value is Message {\n\tif (!isObject(value) || !isTimestamp(value.timestamp)) return false;\n\n\tswitch (value.role) {\n\t\tcase \"user\":\n\t\t\treturn Array.isArray(value.content) && value.content.every(isUserContent);\n\t\tcase \"assistant\":\n\t\t\treturn isAssistantMessage(value);\n\t\tcase \"tool_result\":\n\t\t\treturn (\n\t\t\t\tisNonEmptyString(value.toolCallId) &&\n\t\t\t\tisNonEmptyString(value.toolName) &&\n\t\t\t\tArray.isArray(value.content) &&\n\t\t\t\tvalue.content.every(isToolResultContent) &&\n\t\t\t\ttypeof value.isError === \"boolean\"\n\t\t\t);\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\nfunction decodeHeader(value: unknown, filePath: string): SessionHeader {\n\tif (!isObject(value) || value.type !== \"session\") {\n\t\tthrow new SessionLoadError(\"INVALID_HEADER\", filePath, `Invalid session header in \"${filePath}\".`, {\n\t\t\tlineNumber: 1,\n\t\t});\n\t}\n\tif (!(\"version\" in value)) {\n\t\tthrow new SessionLoadError(\"INVALID_HEADER\", filePath, `Session header has no version in \"${filePath}\".`, {\n\t\t\tlineNumber: 1,\n\t\t});\n\t}\n\tif (value.version !== SESSION_FORMAT_VERSION) {\n\t\tthrow new SessionLoadError(\n\t\t\t\"UNSUPPORTED_VERSION\",\n\t\t\tfilePath,\n\t\t\t`Unsupported session version ${JSON.stringify(value.version)} in \"${filePath}\".`,\n\t\t\t{ lineNumber: 1 },\n\t\t);\n\t}\n\tif (\n\t\t!isRecordId(value.id) ||\n\t\tvalue.parentId !== null ||\n\t\t!isIsoTimestamp(value.timestamp) ||\n\t\t!isAbsolutePath(value.cwd)\n\t) {\n\t\tthrow new SessionLoadError(\"INVALID_HEADER\", filePath, `Invalid session header fields in \"${filePath}\".`, {\n\t\t\tlineNumber: 1,\n\t\t});\n\t}\n\treturn value as unknown as SessionHeader;\n}\n\nfunction decodeSessionEntry(value: unknown): { readonly entry?: SessionEntry; readonly reason?: string } {\n\tif (!isObject(value) || (value.type !== \"message\" && value.type !== \"summary\")) {\n\t\treturn { reason: 'record type must be \"message\" or \"summary\"' };\n\t}\n\tif (value.version !== SESSION_FORMAT_VERSION) return { reason: \"record version must be 1\" };\n\tif (!isRecordId(value.id)) return { reason: \"record id is invalid\" };\n\tif (!isRecordId(value.parentId)) return { reason: \"record parentId is invalid\" };\n\tif (!isIsoTimestamp(value.timestamp)) return { reason: \"record timestamp is not canonical ISO 8601 UTC\" };\n\tif (value.type === \"message\") {\n\t\tif (!isMessage(value.message)) return { reason: \"record message does not match the Message contract\" };\n\t\treturn { entry: value as unknown as SessionMessageEntry };\n\t}\n\tif (typeof value.summary !== \"string\" || value.summary.trim().length === 0) {\n\t\treturn { reason: \"record summary must be a non-empty string\" };\n\t}\n\tif (!isRecordId(value.firstKeptEntryId)) return { reason: \"record firstKeptEntryId is invalid\" };\n\tif (!Number.isSafeInteger(value.tokensBefore) || !isNonNegativeNumber(value.tokensBefore)) {\n\t\treturn { reason: \"record tokensBefore must be a non-negative safe integer\" };\n\t}\n\treturn { entry: value as unknown as SessionSummaryEntry };\n}\n\nfunction withoutTrailingCarriageReturn(line: string): string {\n\treturn line.endsWith(\"\\r\") ? line.slice(0, -1) : line;\n}\n\nfunction errorText(cause: unknown): string {\n\treturn cause instanceof Error ? cause.message : String(cause);\n}\n\nexport async function loadSessionFile(filePath: string): Promise<LoadedSession> {\n\tlet content: string;\n\ttry {\n\t\tcontent = await readFile(filePath, \"utf8\");\n\t} catch (cause) {\n\t\tthrow new SessionLoadError(\"READ_FAILED\", filePath, `Failed to read session file \"${filePath}\".`, { cause });\n\t}\n\n\tconst physicalLines = content.split(\"\\n\");\n\tlet partialLineNumber: number | undefined;\n\tif (content.endsWith(\"\\n\")) {\n\t\tphysicalLines.pop();\n\t} else {\n\t\tphysicalLines.pop();\n\t\tpartialLineNumber = physicalLines.length + 1;\n\t}\n\n\tif (physicalLines.length === 0) {\n\t\tthrow new SessionLoadError(\"INVALID_HEADER\", filePath, `Session file has no committed header: \"${filePath}\".`, {\n\t\t\tlineNumber: 1,\n\t\t});\n\t}\n\n\tlet headerValue: unknown;\n\ttry {\n\t\theaderValue = JSON.parse(withoutTrailingCarriageReturn(physicalLines[0] ?? \"\")) as unknown;\n\t} catch (cause) {\n\t\tthrow new SessionLoadError(\"INVALID_HEADER\", filePath, `Session header is not valid JSON in \"${filePath}\".`, {\n\t\t\tlineNumber: 1,\n\t\t\tcause,\n\t\t});\n\t}\n\n\tconst header = decodeHeader(headerValue, filePath);\n\tconst entries: SessionEntry[] = [];\n\tconst diagnostics: LoadedSession[\"diagnostics\"][number][] = [];\n\tconst seenIds = new Set<string>([header.id]);\n\tconst messageEntryIds = new Set<string>();\n\tlet expectedParentId = header.id;\n\n\tfor (let index = 1; index < physicalLines.length; index++) {\n\t\tconst lineNumber = index + 1;\n\t\ttry {\n\t\t\tconst value = JSON.parse(withoutTrailingCarriageReturn(physicalLines[index] ?? \"\")) as unknown;\n\t\t\tconst decoded = decodeSessionEntry(value);\n\t\t\tif (!decoded.entry) throw new Error(decoded.reason ?? \"Invalid session message record.\");\n\t\t\tif (seenIds.has(decoded.entry.id)) throw new Error(\"record id is duplicated\");\n\t\t\tif (decoded.entry.parentId !== expectedParentId) {\n\t\t\t\tthrow new Error(`record parentId must be \"${expectedParentId}\"`);\n\t\t\t}\n\t\t\tif (decoded.entry.type === \"summary\" && !messageEntryIds.has(decoded.entry.firstKeptEntryId)) {\n\t\t\t\tthrow new Error(\"record firstKeptEntryId must reference an earlier message entry\");\n\t\t\t}\n\t\t\tentries.push(decoded.entry);\n\t\t\tseenIds.add(decoded.entry.id);\n\t\t\tif (decoded.entry.type === \"message\") messageEntryIds.add(decoded.entry.id);\n\t\t\texpectedParentId = decoded.entry.id;\n\t\t} catch (cause) {\n\t\t\tdiagnostics.push({ kind: \"corrupt_record\", lineNumber, reason: errorText(cause) });\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (diagnostics.length === 0 && partialLineNumber !== undefined) {\n\t\tdiagnostics.push({\n\t\t\tkind: \"trailing_partial_line\",\n\t\t\tlineNumber: partialLineNumber,\n\t\t\treason: \"final line has no newline commit marker\",\n\t\t});\n\t}\n\n\treturn {\n\t\theader,\n\t\tentries,\n\t\tmessages: entries\n\t\t\t.filter((entry): entry is SessionMessageEntry => entry.type === \"message\")\n\t\t\t.map((entry) => entry.message),\n\t\tdiagnostics,\n\t};\n}\n\nexport interface SessionAppendOptions {\n\treadonly lockTimeoutMs?: number;\n\treadonly lockRetryMs?: number;\n\treadonly now?: () => number;\n\treadonly sleep?: (milliseconds: number) => Promise<void>;\n}\n\nconst DEFAULT_LOCK_TIMEOUT_MS = 5_000;\nconst DEFAULT_LOCK_RETRY_MS = 25;\n\nfunction isNodeError(cause: unknown): cause is NodeJS.ErrnoException {\n\treturn cause instanceof Error && \"code\" in cause;\n}\n\nfunction delay(milliseconds: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, milliseconds));\n}\n\nexport async function createSessionFile(filePath: string, header: SessionHeader): Promise<void> {\n\tconst headerSnapshot = structuredClone(header);\n\ttry {\n\t\tdecodeHeader(headerSnapshot, filePath);\n\t\tawait mkdir(dirname(filePath), { recursive: true });\n\t\tconst handle = await open(filePath, \"wx\");\n\t\ttry {\n\t\t\tawait handle.writeFile(`${JSON.stringify(headerSnapshot)}\\n`, \"utf8\");\n\t\t} finally {\n\t\t\tawait handle.close();\n\t\t}\n\t} catch (cause) {\n\t\tif (cause instanceof SessionWriteError) throw cause;\n\t\tthrow new SessionWriteError(\"CREATE_FAILED\", filePath, `Failed to create session file \"${filePath}\".`, {\n\t\t\tcause,\n\t\t});\n\t}\n}\n\nasync function acquireLock(filePath: string, options: SessionAppendOptions) {\n\tconst lockPath = `${filePath}.lock`;\n\tconst timeout = options.lockTimeoutMs ?? DEFAULT_LOCK_TIMEOUT_MS;\n\tconst retry = options.lockRetryMs ?? DEFAULT_LOCK_RETRY_MS;\n\tconst now = options.now ?? Date.now;\n\tconst sleep = options.sleep ?? delay;\n\tconst startedAt = now();\n\n\twhile (true) {\n\t\ttry {\n\t\t\treturn { handle: await open(lockPath, \"wx\"), lockPath };\n\t\t} catch (cause) {\n\t\t\tif (!isNodeError(cause) || cause.code !== \"EEXIST\") {\n\t\t\t\tthrow new SessionWriteError(\"APPEND_FAILED\", filePath, `Failed to acquire session lock for \"${filePath}\".`, {\n\t\t\t\t\tcause,\n\t\t\t\t});\n\t\t\t}\n\t\t\tif (now() - startedAt >= timeout) {\n\t\t\t\tthrow new SessionWriteError(\"LOCK_TIMEOUT\", filePath, `Timed out waiting for session lock \"${lockPath}\".`, {\n\t\t\t\t\tcause,\n\t\t\t\t});\n\t\t\t}\n\t\t\tawait sleep(retry);\n\t\t}\n\t}\n}\n\nasync function releaseLock(handle: Awaited<ReturnType<typeof open>>, lockPath: string): Promise<void> {\n\tlet closeError: unknown;\n\ttry {\n\t\tawait handle.close();\n\t} catch (cause) {\n\t\tcloseError = cause;\n\t}\n\n\ttry {\n\t\tawait unlink(lockPath);\n\t} catch (cause) {\n\t\tif (!isNodeError(cause) || cause.code !== \"ENOENT\") throw cause;\n\t}\n\n\tif (closeError !== undefined) throw closeError;\n}\n\nexport async function appendSessionEntry(\n\tfilePath: string,\n\tentry: SessionEntry,\n\texpectedParentId: string,\n\toptions: SessionAppendOptions = {},\n): Promise<void> {\n\tconst entrySnapshot = structuredClone(entry);\n\tconst decodedEntry = decodeSessionEntry(entrySnapshot);\n\tif (!decodedEntry.entry) {\n\t\tthrow new SessionWriteError(\n\t\t\t\"APPEND_FAILED\",\n\t\t\tfilePath,\n\t\t\t`Refusing to append an invalid session entry to \"${filePath}\": ${decodedEntry.reason ?? \"invalid record\"}.`,\n\t\t);\n\t}\n\tconst { handle, lockPath } = await acquireLock(filePath, options);\n\tlet operationError: unknown;\n\ttry {\n\t\tconst loaded = await loadSessionFile(filePath);\n\t\tif (loaded.diagnostics.length > 0) {\n\t\t\tthrow new SessionWriteError(\n\t\t\t\t\"CORRUPT_SESSION\",\n\t\t\t\tfilePath,\n\t\t\t\t`Refusing to append to a session with recovery diagnostics: \"${filePath}\".`,\n\t\t\t);\n\t\t}\n\n\t\tconst actualParentId = loaded.entries.at(-1)?.id ?? loaded.header.id;\n\t\tif (\n\t\t\tentrySnapshot.type === \"summary\" &&\n\t\t\t!loaded.entries.some(\n\t\t\t\t(existing): existing is SessionMessageEntry =>\n\t\t\t\t\texisting.type === \"message\" && existing.id === entrySnapshot.firstKeptEntryId,\n\t\t\t)\n\t\t) {\n\t\t\tthrow new SessionWriteError(\n\t\t\t\t\"APPEND_FAILED\",\n\t\t\t\tfilePath,\n\t\t\t\t`Refusing to append a summary with an invalid firstKeptEntryId to \"${filePath}\".`,\n\t\t\t);\n\t\t}\n\t\tif (loaded.header.id === entrySnapshot.id || loaded.entries.some((existing) => existing.id === entrySnapshot.id)) {\n\t\t\tthrow new SessionWriteError(\n\t\t\t\t\"APPEND_FAILED\",\n\t\t\t\tfilePath,\n\t\t\t\t`Refusing to append a duplicate session record id to \"${filePath}\".`,\n\t\t\t);\n\t\t}\n\t\tif (actualParentId !== expectedParentId || entrySnapshot.parentId !== expectedParentId) {\n\t\t\tthrow new SessionWriteError(\n\t\t\t\t\"CONCURRENT_MODIFICATION\",\n\t\t\t\tfilePath,\n\t\t\t\t`Session leaf changed before append in \"${filePath}\".`,\n\t\t\t\t{ expectedParentId, actualParentId },\n\t\t\t);\n\t\t}\n\n\t\tawait appendFile(filePath, `${JSON.stringify(entrySnapshot)}\\n`, \"utf8\");\n\t} catch (cause) {\n\t\toperationError = cause;\n\t} finally {\n\t\ttry {\n\t\t\tawait releaseLock(handle, lockPath);\n\t\t} catch (cause) {\n\t\t\toperationError ??= new SessionWriteError(\n\t\t\t\t\"APPEND_FAILED\",\n\t\t\t\tfilePath,\n\t\t\t\t`Failed to release session lock for \"${filePath}\".`,\n\t\t\t\t{ cause },\n\t\t\t);\n\t\t}\n\t}\n\n\tif (operationError !== undefined) {\n\t\tif (operationError instanceof SessionWriteError) {\n\t\t\tthrow operationError;\n\t\t}\n\t\tif (operationError instanceof SessionLoadError) {\n\t\t\tconst code = operationError.code === \"READ_FAILED\" ? \"APPEND_FAILED\" : \"CORRUPT_SESSION\";\n\t\t\tthrow new SessionWriteError(code, filePath, `Unable to load session before append: \"${filePath}\".`, {\n\t\t\t\tcause: operationError,\n\t\t\t});\n\t\t}\n\t\tthrow new SessionWriteError(\"APPEND_FAILED\", filePath, `Failed to append session entry to \"${filePath}\".`, {\n\t\t\tcause: operationError,\n\t\t});\n\t}\n}\n"]}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { Message } from "@di-code/ai";
|
|
2
|
+
export declare const SESSION_FORMAT_VERSION: 1;
|
|
3
|
+
export interface SessionRecordBase {
|
|
4
|
+
readonly version: typeof SESSION_FORMAT_VERSION;
|
|
5
|
+
readonly id: string;
|
|
6
|
+
readonly parentId: string | null;
|
|
7
|
+
readonly timestamp: string;
|
|
8
|
+
}
|
|
9
|
+
export interface SessionHeader extends SessionRecordBase {
|
|
10
|
+
readonly type: "session";
|
|
11
|
+
readonly parentId: null;
|
|
12
|
+
readonly cwd: string;
|
|
13
|
+
}
|
|
14
|
+
export interface SessionMessageEntry extends SessionRecordBase {
|
|
15
|
+
readonly type: "message";
|
|
16
|
+
readonly parentId: string;
|
|
17
|
+
readonly message: Message;
|
|
18
|
+
}
|
|
19
|
+
export interface SessionSummaryEntry extends SessionRecordBase {
|
|
20
|
+
readonly type: "summary";
|
|
21
|
+
readonly parentId: string;
|
|
22
|
+
readonly summary: string;
|
|
23
|
+
readonly firstKeptEntryId: string;
|
|
24
|
+
readonly tokensBefore: number;
|
|
25
|
+
}
|
|
26
|
+
export type SessionEntry = SessionMessageEntry | SessionSummaryEntry;
|
|
27
|
+
export type SessionRecord = SessionHeader | SessionEntry;
|
|
28
|
+
export type SessionDiagnostic = {
|
|
29
|
+
readonly kind: "trailing_partial_line";
|
|
30
|
+
readonly lineNumber: number;
|
|
31
|
+
readonly reason: string;
|
|
32
|
+
} | {
|
|
33
|
+
readonly kind: "corrupt_record";
|
|
34
|
+
readonly lineNumber: number;
|
|
35
|
+
readonly reason: string;
|
|
36
|
+
};
|
|
37
|
+
export interface LoadedSession {
|
|
38
|
+
readonly header: SessionHeader;
|
|
39
|
+
readonly entries: readonly SessionEntry[];
|
|
40
|
+
readonly messages: readonly Message[];
|
|
41
|
+
readonly diagnostics: readonly SessionDiagnostic[];
|
|
42
|
+
}
|
|
43
|
+
export type SessionLoadErrorCode = "INVALID_HEADER" | "UNSUPPORTED_VERSION" | "READ_FAILED";
|
|
44
|
+
export declare class SessionLoadError extends Error {
|
|
45
|
+
readonly code: SessionLoadErrorCode;
|
|
46
|
+
readonly filePath: string;
|
|
47
|
+
readonly lineNumber?: number;
|
|
48
|
+
constructor(code: SessionLoadErrorCode, filePath: string, message: string, options?: {
|
|
49
|
+
readonly lineNumber?: number;
|
|
50
|
+
readonly cause?: unknown;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
export type SessionWriteErrorCode = "CREATE_FAILED" | "APPEND_FAILED" | "LOCK_TIMEOUT" | "CONCURRENT_MODIFICATION" | "CORRUPT_SESSION";
|
|
54
|
+
export declare class SessionWriteError extends Error {
|
|
55
|
+
readonly code: SessionWriteErrorCode;
|
|
56
|
+
readonly filePath: string;
|
|
57
|
+
readonly expectedParentId?: string;
|
|
58
|
+
readonly actualParentId?: string;
|
|
59
|
+
constructor(code: SessionWriteErrorCode, filePath: string, message: string, options?: {
|
|
60
|
+
readonly cause?: unknown;
|
|
61
|
+
readonly expectedParentId?: string;
|
|
62
|
+
readonly actualParentId?: string;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/session/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export const SESSION_FORMAT_VERSION = 1;
|
|
2
|
+
export class SessionLoadError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
filePath;
|
|
5
|
+
lineNumber;
|
|
6
|
+
constructor(code, filePath, message, options) {
|
|
7
|
+
super(message, { cause: options?.cause });
|
|
8
|
+
this.name = "SessionLoadError";
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.filePath = filePath;
|
|
11
|
+
this.lineNumber = options?.lineNumber;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export class SessionWriteError extends Error {
|
|
15
|
+
code;
|
|
16
|
+
filePath;
|
|
17
|
+
expectedParentId;
|
|
18
|
+
actualParentId;
|
|
19
|
+
constructor(code, filePath, message, options) {
|
|
20
|
+
super(message, { cause: options?.cause });
|
|
21
|
+
this.name = "SessionWriteError";
|
|
22
|
+
this.code = code;
|
|
23
|
+
this.filePath = filePath;
|
|
24
|
+
this.expectedParentId = options?.expectedParentId;
|
|
25
|
+
this.actualParentId = options?.actualParentId;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/core/session/types.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAU,CAAC;AAsDjD,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IACjC,IAAI,CAAuB;IAC3B,QAAQ,CAAS;IACjB,UAAU,CAAU;IAE7B,YACC,IAA0B,EAC1B,QAAgB,EAChB,OAAe,EACf,OAAoE;QAEpE,KAAK,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;QAC1C,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;QAC/B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,UAAU,GAAG,OAAO,EAAE,UAAU,CAAC;IACvC,CAAC;CACD;AASD,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IAClC,IAAI,CAAwB;IAC5B,QAAQ,CAAS;IACjB,gBAAgB,CAAU;IAC1B,cAAc,CAAU;IAEjC,YACC,IAA2B,EAC3B,QAAgB,EAChB,OAAe,EACf,OAIC;QAED,KAAK,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;QAC1C,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;QAChC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,gBAAgB,GAAG,OAAO,EAAE,gBAAgB,CAAC;QAClD,IAAI,CAAC,cAAc,GAAG,OAAO,EAAE,cAAc,CAAC;IAC/C,CAAC;CACD","sourcesContent":["import type { Message } from \"@di-code/ai\";\n\nexport const SESSION_FORMAT_VERSION = 1 as const;\n\nexport interface SessionRecordBase {\n\treadonly version: typeof SESSION_FORMAT_VERSION;\n\treadonly id: string;\n\treadonly parentId: string | null;\n\treadonly timestamp: string;\n}\n\nexport interface SessionHeader extends SessionRecordBase {\n\treadonly type: \"session\";\n\treadonly parentId: null;\n\treadonly cwd: string;\n}\n\nexport interface SessionMessageEntry extends SessionRecordBase {\n\treadonly type: \"message\";\n\treadonly parentId: string;\n\treadonly message: Message;\n}\n\nexport interface SessionSummaryEntry extends SessionRecordBase {\n\treadonly type: \"summary\";\n\treadonly parentId: string;\n\treadonly summary: string;\n\treadonly firstKeptEntryId: string;\n\treadonly tokensBefore: number;\n}\n\nexport type SessionEntry = SessionMessageEntry | SessionSummaryEntry;\n\nexport type SessionRecord = SessionHeader | SessionEntry;\n\nexport type SessionDiagnostic =\n\t| {\n\t\t\treadonly kind: \"trailing_partial_line\";\n\t\t\treadonly lineNumber: number;\n\t\t\treadonly reason: string;\n\t }\n\t| {\n\t\t\treadonly kind: \"corrupt_record\";\n\t\t\treadonly lineNumber: number;\n\t\t\treadonly reason: string;\n\t };\n\nexport interface LoadedSession {\n\treadonly header: SessionHeader;\n\treadonly entries: readonly SessionEntry[];\n\treadonly messages: readonly Message[];\n\treadonly diagnostics: readonly SessionDiagnostic[];\n}\n\nexport type SessionLoadErrorCode = \"INVALID_HEADER\" | \"UNSUPPORTED_VERSION\" | \"READ_FAILED\";\n\nexport class SessionLoadError extends Error {\n\treadonly code: SessionLoadErrorCode;\n\treadonly filePath: string;\n\treadonly lineNumber?: number;\n\n\tconstructor(\n\t\tcode: SessionLoadErrorCode,\n\t\tfilePath: string,\n\t\tmessage: string,\n\t\toptions?: { readonly lineNumber?: number; readonly cause?: unknown },\n\t) {\n\t\tsuper(message, { cause: options?.cause });\n\t\tthis.name = \"SessionLoadError\";\n\t\tthis.code = code;\n\t\tthis.filePath = filePath;\n\t\tthis.lineNumber = options?.lineNumber;\n\t}\n}\n\nexport type SessionWriteErrorCode =\n\t| \"CREATE_FAILED\"\n\t| \"APPEND_FAILED\"\n\t| \"LOCK_TIMEOUT\"\n\t| \"CONCURRENT_MODIFICATION\"\n\t| \"CORRUPT_SESSION\";\n\nexport class SessionWriteError extends Error {\n\treadonly code: SessionWriteErrorCode;\n\treadonly filePath: string;\n\treadonly expectedParentId?: string;\n\treadonly actualParentId?: string;\n\n\tconstructor(\n\t\tcode: SessionWriteErrorCode,\n\t\tfilePath: string,\n\t\tmessage: string,\n\t\toptions?: {\n\t\t\treadonly cause?: unknown;\n\t\t\treadonly expectedParentId?: string;\n\t\t\treadonly actualParentId?: string;\n\t\t},\n\t) {\n\t\tsuper(message, { cause: options?.cause });\n\t\tthis.name = \"SessionWriteError\";\n\t\tthis.code = code;\n\t\tthis.filePath = filePath;\n\t\tthis.expectedParentId = options?.expectedParentId;\n\t\tthis.actualParentId = options?.actualParentId;\n\t}\n}\n"]}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { type AgentListener } from "@di-code/agent";
|
|
2
|
+
import type { AssistantMessage, Message, Model, Provider, Usage } from "@di-code/ai";
|
|
3
|
+
import type { SessionManager } from "./session/session-manager.ts";
|
|
4
|
+
import type { SessionDiagnostic } from "./session/types.ts";
|
|
5
|
+
export interface AgentSessionCompactionOptions {
|
|
6
|
+
readonly enabled?: boolean;
|
|
7
|
+
readonly keepRecentTokens?: number;
|
|
8
|
+
}
|
|
9
|
+
export interface SessionUsage {
|
|
10
|
+
readonly requestCount: number;
|
|
11
|
+
readonly inputTokens: number;
|
|
12
|
+
readonly outputTokens: number;
|
|
13
|
+
readonly cacheReadTokens: number;
|
|
14
|
+
readonly cacheWriteTokens: number;
|
|
15
|
+
readonly totalTokens: number;
|
|
16
|
+
readonly cost: Usage["cost"];
|
|
17
|
+
readonly estimatedContextTokens: number;
|
|
18
|
+
readonly contextWindow: number;
|
|
19
|
+
readonly reserveTokens: number;
|
|
20
|
+
readonly triggerTokens: number;
|
|
21
|
+
}
|
|
22
|
+
export interface AgentSessionOptions {
|
|
23
|
+
readonly allowedRoot: string;
|
|
24
|
+
readonly provider: Provider;
|
|
25
|
+
readonly model: Model;
|
|
26
|
+
readonly now?: () => number;
|
|
27
|
+
readonly sessionManager?: SessionManager;
|
|
28
|
+
readonly compaction?: AgentSessionCompactionOptions;
|
|
29
|
+
}
|
|
30
|
+
export type AgentSessionEvent = import("@di-code/agent").AgentEvent | {
|
|
31
|
+
type: "compaction_start";
|
|
32
|
+
reason: "threshold" | "manual";
|
|
33
|
+
} | {
|
|
34
|
+
type: "compaction_end";
|
|
35
|
+
reason: "threshold" | "manual";
|
|
36
|
+
success: boolean;
|
|
37
|
+
errorMessage?: string;
|
|
38
|
+
} | {
|
|
39
|
+
type: "usage_update";
|
|
40
|
+
usage: SessionUsage;
|
|
41
|
+
};
|
|
42
|
+
export type AgentSessionListener = (event: AgentSessionEvent) => void | Promise<void>;
|
|
43
|
+
export declare class AgentSession {
|
|
44
|
+
private readonly agent;
|
|
45
|
+
private readonly allowedRootValue;
|
|
46
|
+
private readonly sessionManager?;
|
|
47
|
+
private readonly provider;
|
|
48
|
+
private model;
|
|
49
|
+
private readonly sessionIdValue;
|
|
50
|
+
private readonly now;
|
|
51
|
+
private compactionEnabledValue;
|
|
52
|
+
private contextBudget;
|
|
53
|
+
private keepRecentTokens;
|
|
54
|
+
private usageTotals;
|
|
55
|
+
private persistenceError?;
|
|
56
|
+
private promptActive;
|
|
57
|
+
private readonly sessionListeners;
|
|
58
|
+
constructor(options: AgentSessionOptions);
|
|
59
|
+
get transcript(): readonly Message[];
|
|
60
|
+
get isStreaming(): boolean;
|
|
61
|
+
get sessionId(): string;
|
|
62
|
+
get modelId(): string;
|
|
63
|
+
get allowedRoot(): string;
|
|
64
|
+
get availableModels(): readonly Model[];
|
|
65
|
+
setModel(modelId: string): Model;
|
|
66
|
+
get compactionEnabled(): boolean;
|
|
67
|
+
setCompactionEnabled(enabled: boolean): boolean;
|
|
68
|
+
get sessionFile(): string | undefined;
|
|
69
|
+
get sessionDiagnostics(): readonly SessionDiagnostic[];
|
|
70
|
+
get usage(): SessionUsage;
|
|
71
|
+
prompt(text: string, signal?: AbortSignal): Promise<AssistantMessage>;
|
|
72
|
+
compact(signal?: AbortSignal): Promise<void>;
|
|
73
|
+
subscribe(listener: AgentListener): () => void;
|
|
74
|
+
subscribeSession(listener: AgentSessionListener): () => void;
|
|
75
|
+
private emitSession;
|
|
76
|
+
private addUsage;
|
|
77
|
+
private compactIfNeeded;
|
|
78
|
+
private compactNow;
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/core/session.ts"],"names":[],"mappings":"AACA,OAAO,EAEN,KAAK,aAAa,EAOlB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAErF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAM5D,MAAM,WAAW,6BAA6B;IAC7C,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CACnC;AAED,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7B,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;IACxC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IAC5B,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,CAAC;IACzC,QAAQ,CAAC,UAAU,CAAC,EAAE,6BAA6B,CAAC;CACpD;AAED,MAAM,MAAM,iBAAiB,GAC1B,OAAO,gBAAgB,EAAE,UAAU,GACnC;IAAE,IAAI,EAAE,kBAAkB,CAAC;IAAC,MAAM,EAAE,WAAW,GAAG,QAAQ,CAAA;CAAE,GAC5D;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,MAAM,EAAE,WAAW,GAAG,QAAQ,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,GACnG;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,KAAK,EAAE,YAAY,CAAA;CAAE,CAAC;AAEjD,MAAM,MAAM,oBAAoB,GAAG,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAEtF,qBAAa,YAAY;IACxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAiB;IACjD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAW;IACpC,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,sBAAsB,CAAU;IACxC,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,WAAW,CAGjB;IACF,OAAO,CAAC,gBAAgB,CAAC,CAAU;IACnC,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAmC;gBAExD,OAAO,EAAE,mBAAmB;IAiExC,IAAI,UAAU,IAAI,SAAS,OAAO,EAAE,CAEnC;IAED,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,IAAI,eAAe,IAAI,SAAS,KAAK,EAAE,CAEtC;IAED,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK;IAchC,IAAI,iBAAiB,IAAI,OAAO,CAE/B;IAED,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO;IAM/C,IAAI,WAAW,IAAI,MAAM,GAAG,SAAS,CAEpC;IAED,IAAI,kBAAkB,IAAI,SAAS,iBAAiB,EAAE,CAErD;IAED,IAAI,KAAK,IAAI,YAAY,CASxB;IAEK,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAiBrE,OAAO,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAWlD,SAAS,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,IAAI;IAI9C,gBAAgB,CAAC,QAAQ,EAAE,oBAAoB,GAAG,MAAM,IAAI;YAK9C,WAAW;IAIzB,OAAO,CAAC,QAAQ;YAkBF,eAAe;YAef,UAAU;CA8CxB"}
|