@jun133/kitty 0.0.7 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -20
- package/dist/App-6FETP3LH.mjs +521 -0
- package/dist/chunk-3KMC6H5K.mjs +2701 -0
- package/dist/chunk-4BN45TQG.mjs +654 -0
- package/dist/chunk-6NJJLOY3.mjs +2129 -0
- package/dist/chunk-DFDOKON5.mjs +530 -0
- package/dist/chunk-ELBEXOR7.mjs +10020 -0
- package/dist/chunk-YSWK3BGL.mjs +84 -0
- package/dist/cli.js +1321 -655
- package/dist/cli.js.map +1 -1
- package/dist/interactive-KLW4JL7R.mjs +340 -0
- package/dist/oneShot-YHDMPFQM.mjs +54 -0
- package/dist/session-XKWJHRVY.mjs +66 -0
- package/dist/tui.mjs +728 -0
- package/package.json +8 -2
|
@@ -0,0 +1,2701 @@
|
|
|
1
|
+
// src/provider/capabilities.ts
|
|
2
|
+
var DEFAULT_PROVIDER = "openai-compatible";
|
|
3
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
4
|
+
var DEFAULT_DOCTOR_PROBE_TIMEOUT_MS = 1e4;
|
|
5
|
+
var RELAY_REQUEST_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
6
|
+
var RELAY_DOCTOR_PROBE_TIMEOUT_MS = 45e3;
|
|
7
|
+
function resolveProviderCapabilities(input) {
|
|
8
|
+
const provider = normalizeProviderName(input.provider);
|
|
9
|
+
const model = normalizeModelName(input.model);
|
|
10
|
+
if (provider === "deepseek" || model.startsWith("deepseek-")) {
|
|
11
|
+
return {
|
|
12
|
+
provider: "deepseek",
|
|
13
|
+
model,
|
|
14
|
+
wireApi: "chat.completions",
|
|
15
|
+
supportsReasoningContent: true,
|
|
16
|
+
defaultReasoningEnabled: true,
|
|
17
|
+
defaultReasoningEffort: "high",
|
|
18
|
+
requestTimeoutMs: DEFAULT_REQUEST_TIMEOUT_MS,
|
|
19
|
+
doctorProbeTimeoutMs: DEFAULT_DOCTOR_PROBE_TIMEOUT_MS
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
if (provider === "openai" || model === "gpt-5.4") {
|
|
23
|
+
return {
|
|
24
|
+
provider: "openai",
|
|
25
|
+
model,
|
|
26
|
+
wireApi: "responses",
|
|
27
|
+
supportsReasoningContent: false,
|
|
28
|
+
defaultReasoningEnabled: true,
|
|
29
|
+
defaultReasoningEffort: "xhigh",
|
|
30
|
+
requestTimeoutMs: RELAY_REQUEST_TIMEOUT_MS,
|
|
31
|
+
doctorProbeTimeoutMs: RELAY_DOCTOR_PROBE_TIMEOUT_MS
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
provider,
|
|
36
|
+
model,
|
|
37
|
+
wireApi: "chat.completions",
|
|
38
|
+
supportsReasoningContent: false,
|
|
39
|
+
defaultReasoningEnabled: false,
|
|
40
|
+
requestTimeoutMs: DEFAULT_REQUEST_TIMEOUT_MS,
|
|
41
|
+
doctorProbeTimeoutMs: DEFAULT_DOCTOR_PROBE_TIMEOUT_MS
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function normalizeProviderName(value) {
|
|
45
|
+
const normalized = String(value ?? "").trim().toLowerCase();
|
|
46
|
+
return normalized || DEFAULT_PROVIDER;
|
|
47
|
+
}
|
|
48
|
+
function normalizeModelName(value) {
|
|
49
|
+
return String(value ?? "").trim();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// src/utils/abort.ts
|
|
53
|
+
function createAbortError(message = "Operation aborted") {
|
|
54
|
+
const error = new Error(message);
|
|
55
|
+
error.name = "AbortError";
|
|
56
|
+
error.code = "ABORT_ERR";
|
|
57
|
+
return error;
|
|
58
|
+
}
|
|
59
|
+
function isAbortError(error) {
|
|
60
|
+
if (!error) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
if (error instanceof Error) {
|
|
64
|
+
if (error.name === "AbortError") {
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
const code = String(error.code ?? "");
|
|
68
|
+
if (code === "ABORT_ERR" || code === "ERR_ABORTED" || code === "ABORTED") {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
const message = error.message.toLowerCase();
|
|
72
|
+
if (message.includes("abort") || message.includes("aborted") || message.includes("cancelled") || message.includes("canceled")) {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (typeof error === "object" && error && "cause" in error) {
|
|
77
|
+
return isAbortError(error.cause);
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
function throwIfAborted(signal, message) {
|
|
82
|
+
if (signal?.aborted) {
|
|
83
|
+
throw createAbortError(message ?? "Operation aborted");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function sleepWithSignal(ms, signal) {
|
|
87
|
+
if (!signal) {
|
|
88
|
+
return new Promise((resolve) => {
|
|
89
|
+
setTimeout(resolve, ms);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
if (signal.aborted) {
|
|
93
|
+
return Promise.reject(createAbortError("Sleep aborted"));
|
|
94
|
+
}
|
|
95
|
+
return new Promise((resolve, reject) => {
|
|
96
|
+
const timer = setTimeout(() => {
|
|
97
|
+
signal.removeEventListener("abort", onAbort);
|
|
98
|
+
resolve();
|
|
99
|
+
}, ms);
|
|
100
|
+
const onAbort = () => {
|
|
101
|
+
clearTimeout(timer);
|
|
102
|
+
signal.removeEventListener("abort", onAbort);
|
|
103
|
+
reject(createAbortError("Sleep aborted"));
|
|
104
|
+
};
|
|
105
|
+
signal.addEventListener("abort", onAbort);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// src/provider/apiRetry.ts
|
|
110
|
+
var API_MAX_RETRIES = 3;
|
|
111
|
+
var API_RETRY_BASE_DELAY_MS = 1200;
|
|
112
|
+
async function withApiRetries(operation, abortSignal) {
|
|
113
|
+
let lastError;
|
|
114
|
+
for (let attempt = 1; attempt <= API_MAX_RETRIES; attempt += 1) {
|
|
115
|
+
try {
|
|
116
|
+
return await operation();
|
|
117
|
+
} catch (error) {
|
|
118
|
+
if (isAbortError(error)) {
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
lastError = error;
|
|
122
|
+
if (!isRetryableApiError(error) || attempt === API_MAX_RETRIES) {
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
await sleepWithSignal(API_RETRY_BASE_DELAY_MS * attempt, abortSignal);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
throw lastError;
|
|
129
|
+
}
|
|
130
|
+
function isRetryableApiError(error) {
|
|
131
|
+
const status = error.status;
|
|
132
|
+
if (typeof status === "number") {
|
|
133
|
+
return status === 408 || status === 409 || status === 429 || status >= 500;
|
|
134
|
+
}
|
|
135
|
+
const message = String(error.message ?? error).toLowerCase();
|
|
136
|
+
return message.includes("timeout") || message.includes("network") || message.includes("connection error") || message.includes("connection reset") || message.includes("econnreset") || message.includes("econnrefused") || message.includes("connect timeout") || message.includes("temporarily") || message.includes("rate limit") || message.includes("overloaded");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// src/provider/usageNormalizer.ts
|
|
140
|
+
function normalizeProviderUsage(usage) {
|
|
141
|
+
if (!usage || typeof usage !== "object") {
|
|
142
|
+
return void 0;
|
|
143
|
+
}
|
|
144
|
+
const record = usage;
|
|
145
|
+
const promptDetails = readObject(record.prompt_tokens_details);
|
|
146
|
+
const completionDetails = readObject(record.completion_tokens_details);
|
|
147
|
+
const outputDetails = readObject(record.output_tokens_details);
|
|
148
|
+
const cacheCreation = readObject(record.cache_creation);
|
|
149
|
+
const inputTokens = readUsageNumber(record.prompt_tokens ?? record.input_tokens);
|
|
150
|
+
const outputTokens = readUsageNumber(record.completion_tokens ?? record.output_tokens);
|
|
151
|
+
const totalTokens = readUsageNumber(record.total_tokens);
|
|
152
|
+
const reasoningTokens = readUsageNumber(
|
|
153
|
+
completionDetails?.reasoning_tokens ?? outputDetails?.reasoning_tokens
|
|
154
|
+
);
|
|
155
|
+
const openAiCachedTokens = readUsageNumber(promptDetails?.cached_tokens);
|
|
156
|
+
const deepSeekHitTokens = readUsageNumber(record.prompt_cache_hit_tokens);
|
|
157
|
+
const deepSeekMissTokens = readUsageNumber(record.prompt_cache_miss_tokens);
|
|
158
|
+
const anthropicCacheReadTokens = readUsageNumber(record.cache_read_input_tokens);
|
|
159
|
+
const anthropicCacheCreationTokens = readUsageNumber(record.cache_creation_input_tokens) ?? sumUsageNumbers([
|
|
160
|
+
cacheCreation?.ephemeral_1h_input_tokens,
|
|
161
|
+
cacheCreation?.ephemeral_5m_input_tokens
|
|
162
|
+
]);
|
|
163
|
+
const geminiCachedTokens = readUsageNumber(record.cachedContentTokenCount ?? record.cached_content_token_count);
|
|
164
|
+
const cacheReadTokens = firstNumber(
|
|
165
|
+
anthropicCacheReadTokens,
|
|
166
|
+
openAiCachedTokens,
|
|
167
|
+
geminiCachedTokens
|
|
168
|
+
);
|
|
169
|
+
const cacheHitTokens = firstNumber(deepSeekHitTokens, cacheReadTokens);
|
|
170
|
+
const cacheMissTokens = deepSeekMissTokens;
|
|
171
|
+
const cacheCreationTokens = anthropicCacheCreationTokens;
|
|
172
|
+
const snapshot = {
|
|
173
|
+
inputTokens,
|
|
174
|
+
outputTokens,
|
|
175
|
+
totalTokens,
|
|
176
|
+
reasoningTokens,
|
|
177
|
+
cacheReadTokens,
|
|
178
|
+
cacheCreationTokens,
|
|
179
|
+
cacheHitTokens,
|
|
180
|
+
cacheMissTokens
|
|
181
|
+
};
|
|
182
|
+
const cacheHitRate = computeCacheHitRate(snapshot);
|
|
183
|
+
if (cacheHitRate !== void 0) {
|
|
184
|
+
snapshot.cacheHitRate = cacheHitRate;
|
|
185
|
+
}
|
|
186
|
+
return Object.values(snapshot).some((value) => typeof value === "number") ? snapshot : void 0;
|
|
187
|
+
}
|
|
188
|
+
function hasProviderUsageSnapshot(usage) {
|
|
189
|
+
return Boolean(usage && Object.values(usage).some((value) => typeof value === "number"));
|
|
190
|
+
}
|
|
191
|
+
function computeCacheHitRate(snapshot) {
|
|
192
|
+
if (typeof snapshot.cacheHitTokens === "number" && typeof snapshot.cacheMissTokens === "number") {
|
|
193
|
+
return ratio(snapshot.cacheHitTokens, snapshot.cacheHitTokens + snapshot.cacheMissTokens);
|
|
194
|
+
}
|
|
195
|
+
if (typeof snapshot.cacheReadTokens === "number") {
|
|
196
|
+
const denominator = (snapshot.inputTokens ?? 0) + snapshot.cacheReadTokens + (snapshot.cacheCreationTokens ?? 0);
|
|
197
|
+
return ratio(snapshot.cacheReadTokens, denominator);
|
|
198
|
+
}
|
|
199
|
+
return void 0;
|
|
200
|
+
}
|
|
201
|
+
function ratio(numerator, denominator) {
|
|
202
|
+
if (denominator <= 0) {
|
|
203
|
+
return void 0;
|
|
204
|
+
}
|
|
205
|
+
return Math.round(numerator / denominator * 1e4) / 1e4;
|
|
206
|
+
}
|
|
207
|
+
function readObject(value) {
|
|
208
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
209
|
+
}
|
|
210
|
+
function firstNumber(...values) {
|
|
211
|
+
return values.find((value) => typeof value === "number");
|
|
212
|
+
}
|
|
213
|
+
function sumUsageNumbers(values) {
|
|
214
|
+
const numbers = values.map(readUsageNumber).filter((value) => typeof value === "number");
|
|
215
|
+
if (numbers.length === 0) {
|
|
216
|
+
return void 0;
|
|
217
|
+
}
|
|
218
|
+
return numbers.reduce((total, value) => total + value, 0);
|
|
219
|
+
}
|
|
220
|
+
function readUsageNumber(value) {
|
|
221
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.round(value) : void 0;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// src/observability/writer.ts
|
|
225
|
+
import fs2 from "fs/promises";
|
|
226
|
+
import path2 from "path";
|
|
227
|
+
|
|
228
|
+
// src/project/statePaths.ts
|
|
229
|
+
import fs from "fs/promises";
|
|
230
|
+
import path from "path";
|
|
231
|
+
var PROJECT_STATE_DIR_NAME = ".kitty";
|
|
232
|
+
var PROJECT_STATE_ENV_FILE_NAME = ".env";
|
|
233
|
+
var PROJECT_STATE_ENV_EXAMPLE_FILE_NAME = ".env.example";
|
|
234
|
+
var PROJECT_STATE_IGNORE_FILE_NAME = ".kittyignore";
|
|
235
|
+
var PRESERVED_PROJECT_STATE_ENTRY_NAMES = [
|
|
236
|
+
PROJECT_STATE_ENV_FILE_NAME,
|
|
237
|
+
PROJECT_STATE_ENV_EXAMPLE_FILE_NAME
|
|
238
|
+
];
|
|
239
|
+
function getProjectStatePaths(rootDir) {
|
|
240
|
+
const normalizedRoot = path.resolve(rootDir);
|
|
241
|
+
const kittyDir = path.join(normalizedRoot, PROJECT_STATE_DIR_NAME);
|
|
242
|
+
const extensionsDir = path.join(kittyDir, "extensions");
|
|
243
|
+
const memoryDir = path.join(kittyDir, "memory");
|
|
244
|
+
const observabilityDir = path.join(kittyDir, "observability");
|
|
245
|
+
return {
|
|
246
|
+
rootDir: normalizedRoot,
|
|
247
|
+
kittyDir,
|
|
248
|
+
cacheDir: path.join(kittyDir, "cache"),
|
|
249
|
+
sessionsDir: path.join(kittyDir, "sessions"),
|
|
250
|
+
changesDir: path.join(kittyDir, "changes"),
|
|
251
|
+
eventsDir: path.join(kittyDir, "events"),
|
|
252
|
+
extensionsDir,
|
|
253
|
+
memoryDir,
|
|
254
|
+
evidenceMemoryDir: path.join(memoryDir, "evidence"),
|
|
255
|
+
projectMemoryDir: path.join(memoryDir, "project"),
|
|
256
|
+
sessionMemoryDir: path.join(memoryDir, "sessions"),
|
|
257
|
+
userMemoryDir: path.join(memoryDir, "user"),
|
|
258
|
+
controlPlaneLedgerFile: path.join(kittyDir, "control-plane.sqlite"),
|
|
259
|
+
observabilityDir,
|
|
260
|
+
observabilityEventsDir: path.join(observabilityDir, "events"),
|
|
261
|
+
observabilityCrashesDir: path.join(observabilityDir, "crashes")
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
async function ensureProjectStateDirectories(rootDir) {
|
|
265
|
+
const paths = getProjectStatePaths(rootDir);
|
|
266
|
+
await fs.mkdir(paths.extensionsDir, { recursive: true });
|
|
267
|
+
await fs.mkdir(paths.cacheDir, { recursive: true });
|
|
268
|
+
await fs.mkdir(paths.sessionsDir, { recursive: true });
|
|
269
|
+
await fs.mkdir(paths.changesDir, { recursive: true });
|
|
270
|
+
await fs.mkdir(paths.eventsDir, { recursive: true });
|
|
271
|
+
await fs.mkdir(paths.evidenceMemoryDir, { recursive: true });
|
|
272
|
+
await fs.mkdir(paths.projectMemoryDir, { recursive: true });
|
|
273
|
+
await fs.mkdir(paths.sessionMemoryDir, { recursive: true });
|
|
274
|
+
await fs.mkdir(paths.userMemoryDir, { recursive: true });
|
|
275
|
+
await fs.mkdir(paths.observabilityEventsDir, { recursive: true });
|
|
276
|
+
await fs.mkdir(paths.observabilityCrashesDir, { recursive: true });
|
|
277
|
+
return paths;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// src/observability/schema.ts
|
|
281
|
+
var OBSERVABILITY_VERSION = 1;
|
|
282
|
+
function buildObservabilityEventRecord(input) {
|
|
283
|
+
return {
|
|
284
|
+
version: OBSERVABILITY_VERSION,
|
|
285
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
286
|
+
event: normalizeText(input.event, "unknown"),
|
|
287
|
+
status: normalizeText(input.status, "unknown"),
|
|
288
|
+
host: normalizeOptionalText(input.host),
|
|
289
|
+
sessionId: normalizeOptionalText(input.sessionId),
|
|
290
|
+
executionId: normalizeOptionalText(input.executionId),
|
|
291
|
+
identityKind: normalizeOptionalText(input.identityKind),
|
|
292
|
+
identityName: normalizeOptionalText(input.identityName),
|
|
293
|
+
durationMs: normalizeOptionalNumber(input.durationMs),
|
|
294
|
+
toolName: normalizeOptionalText(input.toolName),
|
|
295
|
+
model: normalizeOptionalText(input.model),
|
|
296
|
+
error: normalizeObservabilityError(input.error),
|
|
297
|
+
details: normalizeDetails(input.details)
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
function normalizeObservabilityError(error) {
|
|
301
|
+
if (error == null) {
|
|
302
|
+
return void 0;
|
|
303
|
+
}
|
|
304
|
+
if (typeof error === "object" && error !== null && "message" in error) {
|
|
305
|
+
const record = error;
|
|
306
|
+
const message2 = normalizeText(record.message, "");
|
|
307
|
+
if (!message2) {
|
|
308
|
+
return void 0;
|
|
309
|
+
}
|
|
310
|
+
return {
|
|
311
|
+
message: message2,
|
|
312
|
+
code: normalizeOptionalText(record.code),
|
|
313
|
+
details: normalizeValue(record.details)
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
const message = readErrorMessage(error);
|
|
317
|
+
return message ? { message } : void 0;
|
|
318
|
+
}
|
|
319
|
+
function normalizeDetails(details) {
|
|
320
|
+
if (!details || typeof details !== "object") {
|
|
321
|
+
return void 0;
|
|
322
|
+
}
|
|
323
|
+
const normalized = normalizeValue(details);
|
|
324
|
+
return normalized && typeof normalized === "object" && !Array.isArray(normalized) ? normalized : void 0;
|
|
325
|
+
}
|
|
326
|
+
function normalizeValue(value, depth = 0) {
|
|
327
|
+
if (value == null) {
|
|
328
|
+
return void 0;
|
|
329
|
+
}
|
|
330
|
+
if (depth >= 4) {
|
|
331
|
+
return "[truncated]";
|
|
332
|
+
}
|
|
333
|
+
if (typeof value === "string") {
|
|
334
|
+
return value.length <= 2e3 ? value : `${value.slice(0, 1997)}...`;
|
|
335
|
+
}
|
|
336
|
+
if (typeof value === "number") {
|
|
337
|
+
return Number.isFinite(value) ? value : void 0;
|
|
338
|
+
}
|
|
339
|
+
if (typeof value === "boolean") {
|
|
340
|
+
return value;
|
|
341
|
+
}
|
|
342
|
+
if (value instanceof Error) {
|
|
343
|
+
return {
|
|
344
|
+
name: normalizeText(value.name, "Error"),
|
|
345
|
+
message: normalizeText(value.message, "Unknown error"),
|
|
346
|
+
stack: normalizeValue(value.stack, depth + 1)
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
if (Array.isArray(value)) {
|
|
350
|
+
return value.slice(0, 20).map((item) => normalizeValue(item, depth + 1)).filter((item) => item !== void 0);
|
|
351
|
+
}
|
|
352
|
+
if (typeof value === "object") {
|
|
353
|
+
const entries = Object.entries(value).slice(0, 30);
|
|
354
|
+
const normalizedEntries = entries.map(([key, item]) => [key, normalizeValue(item, depth + 1)]).filter(([, item]) => item !== void 0);
|
|
355
|
+
return Object.fromEntries(normalizedEntries);
|
|
356
|
+
}
|
|
357
|
+
return normalizeText(String(value), "");
|
|
358
|
+
}
|
|
359
|
+
function normalizeText(value, fallback) {
|
|
360
|
+
const normalized = String(value ?? "").trim();
|
|
361
|
+
return normalized || fallback;
|
|
362
|
+
}
|
|
363
|
+
function normalizeOptionalText(value) {
|
|
364
|
+
const normalized = String(value ?? "").trim();
|
|
365
|
+
return normalized || void 0;
|
|
366
|
+
}
|
|
367
|
+
function normalizeOptionalNumber(value) {
|
|
368
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.round(value) : void 0;
|
|
369
|
+
}
|
|
370
|
+
function readErrorMessage(error) {
|
|
371
|
+
if (error instanceof Error) {
|
|
372
|
+
return normalizeText(error.message, error.name || "Unknown error");
|
|
373
|
+
}
|
|
374
|
+
if (typeof error === "object" && error !== null && "message" in error) {
|
|
375
|
+
return normalizeText(error.message, "Unknown error");
|
|
376
|
+
}
|
|
377
|
+
return normalizeText(String(error ?? "Unknown error"), "Unknown error");
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// src/observability/writer.ts
|
|
381
|
+
async function appendObservabilityEvent(rootDir, input) {
|
|
382
|
+
const paths = await ensureProjectStateDirectories(rootDir);
|
|
383
|
+
const record = buildObservabilityEventRecord(input);
|
|
384
|
+
const filePath = path2.join(paths.observabilityEventsDir, `${record.timestamp.slice(0, 10)}.jsonl`);
|
|
385
|
+
await fs2.appendFile(filePath, `${JSON.stringify(record)}
|
|
386
|
+
`, "utf8");
|
|
387
|
+
return record;
|
|
388
|
+
}
|
|
389
|
+
async function recordObservabilityEvent(rootDir, input) {
|
|
390
|
+
try {
|
|
391
|
+
await appendObservabilityEvent(rootDir, input);
|
|
392
|
+
} catch {
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// src/provider/cachePolicy.ts
|
|
397
|
+
function resolveProviderCachePolicy(input) {
|
|
398
|
+
const capabilities = resolveProviderCapabilities(input);
|
|
399
|
+
if (capabilities.provider === "openai") {
|
|
400
|
+
return {
|
|
401
|
+
provider: "openai",
|
|
402
|
+
automaticPrefixCache: true,
|
|
403
|
+
promptCacheKey: buildPromptCacheKey(input)
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
if (capabilities.provider === "deepseek") {
|
|
407
|
+
return {
|
|
408
|
+
provider: "deepseek",
|
|
409
|
+
automaticPrefixCache: true
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
return {
|
|
413
|
+
provider: "generic",
|
|
414
|
+
automaticPrefixCache: false
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
function buildPromptCacheKey(input) {
|
|
418
|
+
const seed = input.sessionId || input.projectRoot;
|
|
419
|
+
if (!seed) {
|
|
420
|
+
return void 0;
|
|
421
|
+
}
|
|
422
|
+
return `kitty:${stableHash(seed)}`;
|
|
423
|
+
}
|
|
424
|
+
function stableHash(value) {
|
|
425
|
+
let hash = 2166136261;
|
|
426
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
427
|
+
hash ^= value.charCodeAt(index);
|
|
428
|
+
hash = Math.imul(hash, 16777619);
|
|
429
|
+
}
|
|
430
|
+
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// src/provider/chatRequestBody.ts
|
|
434
|
+
function buildProviderRequestBody(input) {
|
|
435
|
+
const capabilities = resolveProviderCapabilities(input);
|
|
436
|
+
const thinking = capabilities.provider === "deepseek" ? resolveDeepSeekThinking(input.messages, input.thinking ?? "enabled") : input.thinking;
|
|
437
|
+
const body = {
|
|
438
|
+
model: input.model,
|
|
439
|
+
messages: toChatCompletionMessages(input.messages),
|
|
440
|
+
tools: input.tools,
|
|
441
|
+
stream: input.stream
|
|
442
|
+
};
|
|
443
|
+
if (capabilities.provider !== "deepseek" && input.tools?.length) {
|
|
444
|
+
body.tool_choice = "auto";
|
|
445
|
+
}
|
|
446
|
+
if (input.stream) {
|
|
447
|
+
body.stream_options = {
|
|
448
|
+
include_usage: true
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
const cachePolicy = resolveProviderCachePolicy(input);
|
|
452
|
+
if (cachePolicy.promptCacheKey) {
|
|
453
|
+
body.prompt_cache_key = cachePolicy.promptCacheKey;
|
|
454
|
+
}
|
|
455
|
+
if (typeof input.maxOutputTokens === "number" && Number.isFinite(input.maxOutputTokens)) {
|
|
456
|
+
body.max_tokens = Math.max(1, Math.trunc(input.maxOutputTokens));
|
|
457
|
+
}
|
|
458
|
+
if (capabilities.provider === "deepseek") {
|
|
459
|
+
body.thinking = { type: thinking };
|
|
460
|
+
if (thinking === "enabled") {
|
|
461
|
+
body.reasoning_effort = normalizeDeepSeekReasoningEffort(input.reasoningEffort ?? capabilities.defaultReasoningEffort);
|
|
462
|
+
}
|
|
463
|
+
} else if (input.forceReasoning || capabilities.defaultReasoningEnabled) {
|
|
464
|
+
body.thinking = { type: "enabled" };
|
|
465
|
+
}
|
|
466
|
+
return body;
|
|
467
|
+
}
|
|
468
|
+
function resolveDeepSeekThinking(messages, requested) {
|
|
469
|
+
if (requested === "disabled") {
|
|
470
|
+
return "disabled";
|
|
471
|
+
}
|
|
472
|
+
return hasUnreplayableAssistantReasoning(messages) ? "disabled" : "enabled";
|
|
473
|
+
}
|
|
474
|
+
function hasUnreplayableAssistantReasoning(messages) {
|
|
475
|
+
return messages.some(
|
|
476
|
+
(message) => message.role === "assistant" && Array.isArray(message.toolCalls) && message.toolCalls.length > 0 && message.reasoningContent === void 0
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
function normalizeDeepSeekReasoningEffort(effort) {
|
|
480
|
+
if (effort === void 0 || effort === "minimal" || effort === "low" || effort === "medium" || effort === "high") {
|
|
481
|
+
return "high";
|
|
482
|
+
}
|
|
483
|
+
if (effort === "xhigh" || effort === "max") {
|
|
484
|
+
return "max";
|
|
485
|
+
}
|
|
486
|
+
return "high";
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// src/provider/chatCompletionsAdapter.ts
|
|
490
|
+
var chatCompletionsAdapter = {
|
|
491
|
+
wireApi: "chat.completions",
|
|
492
|
+
async fetchStreaming(client, request) {
|
|
493
|
+
const startedAt = Date.now();
|
|
494
|
+
let usage;
|
|
495
|
+
throwIfAborted(request.abortSignal, "Streaming request aborted");
|
|
496
|
+
try {
|
|
497
|
+
const stream = await client.chat.completions.create(
|
|
498
|
+
{
|
|
499
|
+
...buildProviderRequestBody({
|
|
500
|
+
provider: request.provider,
|
|
501
|
+
model: request.model,
|
|
502
|
+
messages: request.messages,
|
|
503
|
+
tools: request.tools,
|
|
504
|
+
stream: true,
|
|
505
|
+
forceReasoning: request.forceReasoning,
|
|
506
|
+
thinking: request.thinking,
|
|
507
|
+
reasoningEffort: request.reasoningEffort,
|
|
508
|
+
maxOutputTokens: request.maxOutputTokens,
|
|
509
|
+
sessionId: request.sessionId,
|
|
510
|
+
projectRoot: request.projectRoot
|
|
511
|
+
}),
|
|
512
|
+
signal: request.abortSignal
|
|
513
|
+
}
|
|
514
|
+
);
|
|
515
|
+
if (request.abortSignal?.aborted) {
|
|
516
|
+
abortStream(stream);
|
|
517
|
+
throw createAbortError("Streaming aborted");
|
|
518
|
+
}
|
|
519
|
+
let content = "";
|
|
520
|
+
let reasoningContent = "";
|
|
521
|
+
const toolCallParts = /* @__PURE__ */ new Map();
|
|
522
|
+
for await (const chunk of stream) {
|
|
523
|
+
if (request.abortSignal?.aborted) {
|
|
524
|
+
abortStream(stream);
|
|
525
|
+
throw createAbortError("Streaming aborted");
|
|
526
|
+
}
|
|
527
|
+
usage = normalizeProviderUsage(chunk.usage) ?? usage;
|
|
528
|
+
const delta = chunk.choices?.[0]?.delta;
|
|
529
|
+
if (!delta) {
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
532
|
+
if (typeof delta.content === "string" && delta.content.length > 0) {
|
|
533
|
+
content += delta.content;
|
|
534
|
+
request.callbacks?.onAssistantDelta?.(delta.content);
|
|
535
|
+
}
|
|
536
|
+
if (typeof delta.reasoning_content === "string" && delta.reasoning_content.length > 0) {
|
|
537
|
+
reasoningContent += delta.reasoning_content;
|
|
538
|
+
request.callbacks?.onReasoningDelta?.(delta.reasoning_content);
|
|
539
|
+
}
|
|
540
|
+
if (Array.isArray(delta.tool_calls)) {
|
|
541
|
+
for (const toolCall of delta.tool_calls) {
|
|
542
|
+
const index = typeof toolCall.index === "number" ? toolCall.index : 0;
|
|
543
|
+
const existing = toolCallParts.get(index) ?? {
|
|
544
|
+
id: toolCall.id ?? `tool-${index}`,
|
|
545
|
+
name: "",
|
|
546
|
+
arguments: ""
|
|
547
|
+
};
|
|
548
|
+
if (toolCall.id) {
|
|
549
|
+
existing.id = toolCall.id;
|
|
550
|
+
}
|
|
551
|
+
if (toolCall.function?.name) {
|
|
552
|
+
existing.name += toolCall.function.name;
|
|
553
|
+
}
|
|
554
|
+
if (toolCall.function?.arguments) {
|
|
555
|
+
existing.arguments += toolCall.function.arguments;
|
|
556
|
+
}
|
|
557
|
+
toolCallParts.set(index, existing);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
return {
|
|
562
|
+
content: content.length > 0 ? content : null,
|
|
563
|
+
reasoningContent: reasoningContent.length > 0 ? reasoningContent : void 0,
|
|
564
|
+
streamedAssistantContent: content.length > 0,
|
|
565
|
+
streamedReasoningContent: reasoningContent.length > 0,
|
|
566
|
+
toolCalls: [...toolCallParts.entries()].sort((left, right) => left[0] - right[0]).map(([, toolCall]) => ({
|
|
567
|
+
id: toolCall.id,
|
|
568
|
+
type: "function",
|
|
569
|
+
function: {
|
|
570
|
+
name: toolCall.name,
|
|
571
|
+
arguments: toolCall.arguments
|
|
572
|
+
}
|
|
573
|
+
}))
|
|
574
|
+
};
|
|
575
|
+
} finally {
|
|
576
|
+
request.onRequestMetric?.({
|
|
577
|
+
durationMs: Date.now() - startedAt,
|
|
578
|
+
usage
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
},
|
|
582
|
+
async fetchNonStreaming(client, request) {
|
|
583
|
+
const startedAt = Date.now();
|
|
584
|
+
let usage;
|
|
585
|
+
throwIfAborted(request.abortSignal, "Request aborted");
|
|
586
|
+
try {
|
|
587
|
+
const completion = await client.chat.completions.create(
|
|
588
|
+
{
|
|
589
|
+
...buildProviderRequestBody({
|
|
590
|
+
provider: request.provider,
|
|
591
|
+
model: request.model,
|
|
592
|
+
messages: request.messages,
|
|
593
|
+
tools: request.tools,
|
|
594
|
+
stream: false,
|
|
595
|
+
forceReasoning: request.forceReasoning,
|
|
596
|
+
thinking: request.thinking,
|
|
597
|
+
reasoningEffort: request.reasoningEffort,
|
|
598
|
+
maxOutputTokens: request.maxOutputTokens,
|
|
599
|
+
sessionId: request.sessionId,
|
|
600
|
+
projectRoot: request.projectRoot
|
|
601
|
+
}),
|
|
602
|
+
signal: request.abortSignal
|
|
603
|
+
}
|
|
604
|
+
);
|
|
605
|
+
usage = normalizeProviderUsage(completion.usage);
|
|
606
|
+
const message = completion.choices[0]?.message;
|
|
607
|
+
if (!message) {
|
|
608
|
+
throw new Error("API returned no message.");
|
|
609
|
+
}
|
|
610
|
+
return {
|
|
611
|
+
content: typeof message.content === "string" ? message.content : collapseContentParts(message.content),
|
|
612
|
+
reasoningContent: readReasoningContent(message),
|
|
613
|
+
streamedAssistantContent: false,
|
|
614
|
+
streamedReasoningContent: false,
|
|
615
|
+
toolCalls: (message.tool_calls ?? []).filter((call) => call.type === "function").map((call) => ({
|
|
616
|
+
id: call.id,
|
|
617
|
+
type: "function",
|
|
618
|
+
function: {
|
|
619
|
+
name: call.function.name,
|
|
620
|
+
arguments: call.function.arguments
|
|
621
|
+
}
|
|
622
|
+
}))
|
|
623
|
+
};
|
|
624
|
+
} finally {
|
|
625
|
+
request.onRequestMetric?.({
|
|
626
|
+
durationMs: Date.now() - startedAt,
|
|
627
|
+
usage
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
function abortStream(stream) {
|
|
633
|
+
try {
|
|
634
|
+
stream?.controller?.abort();
|
|
635
|
+
} catch {
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
function toChatCompletionMessages(messages) {
|
|
639
|
+
return messages.map((message) => {
|
|
640
|
+
if (message.role === "tool") {
|
|
641
|
+
return {
|
|
642
|
+
role: "tool",
|
|
643
|
+
content: message.content ?? "",
|
|
644
|
+
tool_call_id: message.toolCallId ?? ""
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
if (message.role === "assistant" && message.toolCalls?.length) {
|
|
648
|
+
const assistantMessage = {
|
|
649
|
+
role: "assistant",
|
|
650
|
+
content: message.content ?? "",
|
|
651
|
+
tool_calls: message.toolCalls
|
|
652
|
+
};
|
|
653
|
+
if (message.reasoningContent !== void 0) {
|
|
654
|
+
assistantMessage.reasoning_content = message.reasoningContent;
|
|
655
|
+
}
|
|
656
|
+
return assistantMessage;
|
|
657
|
+
}
|
|
658
|
+
const baseMessage = {
|
|
659
|
+
role: message.role,
|
|
660
|
+
content: message.content ?? "",
|
|
661
|
+
name: message.name
|
|
662
|
+
};
|
|
663
|
+
if (message.role === "assistant" && message.reasoningContent !== void 0) {
|
|
664
|
+
baseMessage.reasoning_content = message.reasoningContent;
|
|
665
|
+
}
|
|
666
|
+
return baseMessage;
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// src/provider/responsesAdapter.ts
|
|
671
|
+
var responsesAdapter = {
|
|
672
|
+
wireApi: "responses",
|
|
673
|
+
async fetchStreaming(client, request) {
|
|
674
|
+
const startedAt = Date.now();
|
|
675
|
+
let usage;
|
|
676
|
+
throwIfAborted(request.abortSignal, "Streaming request aborted");
|
|
677
|
+
try {
|
|
678
|
+
const stream = await client.responses.create(
|
|
679
|
+
{
|
|
680
|
+
...buildResponsesRequestBody(request),
|
|
681
|
+
stream: true
|
|
682
|
+
},
|
|
683
|
+
{
|
|
684
|
+
signal: request.abortSignal
|
|
685
|
+
}
|
|
686
|
+
);
|
|
687
|
+
if (request.abortSignal?.aborted) {
|
|
688
|
+
abortStream2(stream);
|
|
689
|
+
throw createAbortError("Streaming aborted");
|
|
690
|
+
}
|
|
691
|
+
let content = "";
|
|
692
|
+
let reasoningContent = "";
|
|
693
|
+
const toolCalls = /* @__PURE__ */ new Map();
|
|
694
|
+
for await (const event of stream) {
|
|
695
|
+
if (request.abortSignal?.aborted) {
|
|
696
|
+
abortStream2(stream);
|
|
697
|
+
throw createAbortError("Streaming aborted");
|
|
698
|
+
}
|
|
699
|
+
usage = normalizeProviderUsage(event.response?.usage) ?? usage;
|
|
700
|
+
if (event.type === "response.output_text.delta" && typeof event.delta === "string") {
|
|
701
|
+
content += event.delta;
|
|
702
|
+
request.callbacks?.onAssistantDelta?.(event.delta);
|
|
703
|
+
continue;
|
|
704
|
+
}
|
|
705
|
+
if ((event.type === "response.reasoning_text.delta" || event.type === "response.reasoning_summary_text.delta") && typeof event.delta === "string") {
|
|
706
|
+
reasoningContent += event.delta;
|
|
707
|
+
request.callbacks?.onReasoningDelta?.(event.delta);
|
|
708
|
+
continue;
|
|
709
|
+
}
|
|
710
|
+
if (event.type === "response.function_call_arguments.delta" && typeof event.delta === "string") {
|
|
711
|
+
const index = typeof event.output_index === "number" ? event.output_index : 0;
|
|
712
|
+
const existing = toolCalls.get(index) ?? {
|
|
713
|
+
id: event.item_id ?? `tool-${index}`,
|
|
714
|
+
name: "",
|
|
715
|
+
arguments: ""
|
|
716
|
+
};
|
|
717
|
+
existing.arguments += event.delta;
|
|
718
|
+
toolCalls.set(index, existing);
|
|
719
|
+
continue;
|
|
720
|
+
}
|
|
721
|
+
if (event.type === "response.function_call_arguments.done") {
|
|
722
|
+
const index = typeof event.output_index === "number" ? event.output_index : 0;
|
|
723
|
+
const existing = toolCalls.get(index) ?? {
|
|
724
|
+
id: event.item_id ?? `tool-${index}`,
|
|
725
|
+
name: "",
|
|
726
|
+
arguments: ""
|
|
727
|
+
};
|
|
728
|
+
if (typeof event.name === "string") {
|
|
729
|
+
existing.name = event.name;
|
|
730
|
+
}
|
|
731
|
+
if (typeof event.arguments === "string" && event.arguments.length > 0) {
|
|
732
|
+
existing.arguments = event.arguments;
|
|
733
|
+
}
|
|
734
|
+
toolCalls.set(index, existing);
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
737
|
+
if (event.type === "response.output_item.done" && event.item?.type === "function_call") {
|
|
738
|
+
const index = typeof event.output_index === "number" ? event.output_index : 0;
|
|
739
|
+
toolCalls.set(index, {
|
|
740
|
+
id: event.item.call_id ?? event.item.id ?? `tool-${index}`,
|
|
741
|
+
name: event.item.name ?? "",
|
|
742
|
+
arguments: event.item.arguments ?? ""
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
return {
|
|
747
|
+
content: content.length > 0 ? content : null,
|
|
748
|
+
reasoningContent: reasoningContent.length > 0 ? reasoningContent : void 0,
|
|
749
|
+
streamedAssistantContent: content.length > 0,
|
|
750
|
+
streamedReasoningContent: reasoningContent.length > 0,
|
|
751
|
+
toolCalls: [...toolCalls.entries()].sort((left, right) => left[0] - right[0]).map(([, toolCall]) => ({
|
|
752
|
+
id: toolCall.id,
|
|
753
|
+
type: "function",
|
|
754
|
+
function: {
|
|
755
|
+
name: toolCall.name,
|
|
756
|
+
arguments: toolCall.arguments
|
|
757
|
+
}
|
|
758
|
+
}))
|
|
759
|
+
};
|
|
760
|
+
} finally {
|
|
761
|
+
request.onRequestMetric?.({
|
|
762
|
+
durationMs: Date.now() - startedAt,
|
|
763
|
+
usage
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
},
|
|
767
|
+
async fetchNonStreaming(client, request) {
|
|
768
|
+
const startedAt = Date.now();
|
|
769
|
+
let usage;
|
|
770
|
+
throwIfAborted(request.abortSignal, "Request aborted");
|
|
771
|
+
try {
|
|
772
|
+
const response = await client.responses.create(
|
|
773
|
+
{
|
|
774
|
+
...buildResponsesRequestBody(request),
|
|
775
|
+
stream: false
|
|
776
|
+
},
|
|
777
|
+
{
|
|
778
|
+
signal: request.abortSignal
|
|
779
|
+
}
|
|
780
|
+
);
|
|
781
|
+
usage = normalizeProviderUsage(response.usage);
|
|
782
|
+
return {
|
|
783
|
+
content: normalizeOutputText(response),
|
|
784
|
+
reasoningContent: readResponseReasoning(response),
|
|
785
|
+
streamedAssistantContent: false,
|
|
786
|
+
streamedReasoningContent: false,
|
|
787
|
+
toolCalls: readResponseToolCalls(response)
|
|
788
|
+
};
|
|
789
|
+
} finally {
|
|
790
|
+
request.onRequestMetric?.({
|
|
791
|
+
durationMs: Date.now() - startedAt,
|
|
792
|
+
usage
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
};
|
|
797
|
+
function buildResponsesRequestBody(request) {
|
|
798
|
+
const capabilities = resolveProviderCapabilities({
|
|
799
|
+
provider: request.provider,
|
|
800
|
+
model: request.model
|
|
801
|
+
});
|
|
802
|
+
const body = {
|
|
803
|
+
model: request.model,
|
|
804
|
+
input: toResponsesInput(request.messages),
|
|
805
|
+
tools: request.tools?.map((tool) => ({
|
|
806
|
+
type: "function",
|
|
807
|
+
name: tool.function.name,
|
|
808
|
+
description: tool.function.description,
|
|
809
|
+
parameters: tool.function.parameters ?? null,
|
|
810
|
+
strict: false
|
|
811
|
+
})),
|
|
812
|
+
tool_choice: request.tools?.length ? "auto" : void 0
|
|
813
|
+
};
|
|
814
|
+
if (typeof request.maxOutputTokens === "number" && Number.isFinite(request.maxOutputTokens)) {
|
|
815
|
+
body.max_output_tokens = Math.max(1, Math.trunc(request.maxOutputTokens));
|
|
816
|
+
}
|
|
817
|
+
const cachePolicy = resolveProviderCachePolicy({
|
|
818
|
+
provider: request.provider,
|
|
819
|
+
model: request.model,
|
|
820
|
+
sessionId: request.sessionId,
|
|
821
|
+
projectRoot: request.projectRoot
|
|
822
|
+
});
|
|
823
|
+
if (cachePolicy.promptCacheKey) {
|
|
824
|
+
body.prompt_cache_key = cachePolicy.promptCacheKey;
|
|
825
|
+
}
|
|
826
|
+
const reasoningEffort = normalizeResponsesReasoningEffort(
|
|
827
|
+
request.reasoningEffort ?? capabilities.defaultReasoningEffort
|
|
828
|
+
);
|
|
829
|
+
if (request.forceReasoning || capabilities.defaultReasoningEnabled || reasoningEffort) {
|
|
830
|
+
body.reasoning = {
|
|
831
|
+
effort: reasoningEffort ?? "high",
|
|
832
|
+
summary: "detailed"
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
return body;
|
|
836
|
+
}
|
|
837
|
+
function normalizeResponsesReasoningEffort(effort) {
|
|
838
|
+
return effort === "max" ? void 0 : effort;
|
|
839
|
+
}
|
|
840
|
+
function toResponsesInput(messages) {
|
|
841
|
+
const items = [];
|
|
842
|
+
for (const message of messages) {
|
|
843
|
+
if (message.role === "tool") {
|
|
844
|
+
items.push({
|
|
845
|
+
type: "function_call_output",
|
|
846
|
+
call_id: message.toolCallId ?? "",
|
|
847
|
+
output: message.content ?? ""
|
|
848
|
+
});
|
|
849
|
+
continue;
|
|
850
|
+
}
|
|
851
|
+
if (message.role === "assistant" && message.toolCalls?.length) {
|
|
852
|
+
if (typeof message.content === "string" && message.content.trim().length > 0) {
|
|
853
|
+
items.push({
|
|
854
|
+
type: "message",
|
|
855
|
+
role: "assistant",
|
|
856
|
+
content: message.content
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
for (const toolCall of message.toolCalls) {
|
|
860
|
+
items.push({
|
|
861
|
+
type: "function_call",
|
|
862
|
+
call_id: toolCall.id,
|
|
863
|
+
name: toolCall.function.name,
|
|
864
|
+
arguments: toolCall.function.arguments
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
continue;
|
|
868
|
+
}
|
|
869
|
+
items.push({
|
|
870
|
+
type: "message",
|
|
871
|
+
role: message.role,
|
|
872
|
+
content: message.content ?? ""
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
return items;
|
|
876
|
+
}
|
|
877
|
+
function normalizeOutputText(response) {
|
|
878
|
+
const outputText = response.output_text;
|
|
879
|
+
if (typeof outputText === "string" && outputText.trim().length > 0) {
|
|
880
|
+
return outputText;
|
|
881
|
+
}
|
|
882
|
+
const output = response.output;
|
|
883
|
+
if (!Array.isArray(output)) {
|
|
884
|
+
return null;
|
|
885
|
+
}
|
|
886
|
+
const fragments = output.flatMap((item) => {
|
|
887
|
+
if (!item || typeof item !== "object" || item.type !== "message") {
|
|
888
|
+
return [];
|
|
889
|
+
}
|
|
890
|
+
const content = item.content;
|
|
891
|
+
if (!Array.isArray(content)) {
|
|
892
|
+
return [];
|
|
893
|
+
}
|
|
894
|
+
return content.flatMap((part) => {
|
|
895
|
+
if (!part || typeof part !== "object" || part.type !== "output_text") {
|
|
896
|
+
return [];
|
|
897
|
+
}
|
|
898
|
+
return typeof part.text === "string" ? [part.text] : [];
|
|
899
|
+
});
|
|
900
|
+
});
|
|
901
|
+
return fragments.length > 0 ? fragments.join("") : null;
|
|
902
|
+
}
|
|
903
|
+
function readResponseToolCalls(response) {
|
|
904
|
+
const output = response.output;
|
|
905
|
+
if (!Array.isArray(output)) {
|
|
906
|
+
return [];
|
|
907
|
+
}
|
|
908
|
+
return output.filter((item) => Boolean(item) && typeof item === "object" && item.type === "function_call").map((item) => ({
|
|
909
|
+
id: item.call_id ?? item.id ?? crypto.randomUUID(),
|
|
910
|
+
type: "function",
|
|
911
|
+
function: {
|
|
912
|
+
name: item.name ?? "",
|
|
913
|
+
arguments: item.arguments ?? ""
|
|
914
|
+
}
|
|
915
|
+
}));
|
|
916
|
+
}
|
|
917
|
+
function readResponseReasoning(response) {
|
|
918
|
+
const output = response.output;
|
|
919
|
+
if (!Array.isArray(output)) {
|
|
920
|
+
return void 0;
|
|
921
|
+
}
|
|
922
|
+
const fragments = output.flatMap((item) => {
|
|
923
|
+
if (!item || typeof item !== "object" || item.type !== "reasoning") {
|
|
924
|
+
return [];
|
|
925
|
+
}
|
|
926
|
+
const reasoningItem = item;
|
|
927
|
+
const summary = Array.isArray(reasoningItem.summary) ? reasoningItem.summary.map((entry) => typeof entry?.text === "string" ? entry.text : "").filter(Boolean) : [];
|
|
928
|
+
const content = Array.isArray(reasoningItem.content) ? reasoningItem.content.map((entry) => typeof entry?.text === "string" ? entry.text : "").filter(Boolean) : [];
|
|
929
|
+
return [...content, ...summary];
|
|
930
|
+
});
|
|
931
|
+
return fragments.length > 0 ? fragments.join("") : void 0;
|
|
932
|
+
}
|
|
933
|
+
function abortStream2(stream) {
|
|
934
|
+
try {
|
|
935
|
+
stream?.controller?.abort();
|
|
936
|
+
} catch {
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
// src/provider/client.ts
|
|
941
|
+
import OpenAI from "openai";
|
|
942
|
+
|
|
943
|
+
// src/provider/connection.ts
|
|
944
|
+
function buildProviderBaseUrlCandidates(baseUrl) {
|
|
945
|
+
const normalized = trimTrailingSlash(baseUrl);
|
|
946
|
+
if (!normalized) {
|
|
947
|
+
return [normalized];
|
|
948
|
+
}
|
|
949
|
+
const candidates = [normalized];
|
|
950
|
+
try {
|
|
951
|
+
const parsed = new URL(normalized);
|
|
952
|
+
if (parsed.pathname === "" || parsed.pathname === "/") {
|
|
953
|
+
candidates.push(trimTrailingSlash(new URL("v1", ensureTrailingSlash(parsed.toString())).toString()));
|
|
954
|
+
}
|
|
955
|
+
} catch {
|
|
956
|
+
return candidates;
|
|
957
|
+
}
|
|
958
|
+
return [...new Set(candidates)];
|
|
959
|
+
}
|
|
960
|
+
function ensureTrailingSlash(baseUrl) {
|
|
961
|
+
return baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
962
|
+
}
|
|
963
|
+
function trimTrailingSlash(baseUrl) {
|
|
964
|
+
const trimmed = String(baseUrl ?? "").trim();
|
|
965
|
+
if (!trimmed) {
|
|
966
|
+
return trimmed;
|
|
967
|
+
}
|
|
968
|
+
return trimmed.endsWith("/") ? trimmed.slice(0, -1) : trimmed;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
// src/provider/client.ts
|
|
972
|
+
function createProviderClientPool(config) {
|
|
973
|
+
const capabilities = resolveProviderCapabilities({
|
|
974
|
+
provider: config.provider,
|
|
975
|
+
model: config.model
|
|
976
|
+
});
|
|
977
|
+
const baseUrls = buildProviderBaseUrlCandidates(config.baseUrl);
|
|
978
|
+
const clients = /* @__PURE__ */ new Map();
|
|
979
|
+
let preferredBaseUrl;
|
|
980
|
+
return {
|
|
981
|
+
candidates() {
|
|
982
|
+
const ordered = preferredBaseUrl ? [preferredBaseUrl, ...baseUrls.filter((baseUrl) => baseUrl !== preferredBaseUrl)] : baseUrls;
|
|
983
|
+
return ordered.map((baseUrl) => ({
|
|
984
|
+
baseUrl,
|
|
985
|
+
client: getOrCreateClient(baseUrl)
|
|
986
|
+
}));
|
|
987
|
+
},
|
|
988
|
+
markHealthy(baseUrl) {
|
|
989
|
+
preferredBaseUrl = baseUrl;
|
|
990
|
+
}
|
|
991
|
+
};
|
|
992
|
+
function getOrCreateClient(baseUrl) {
|
|
993
|
+
const existing = clients.get(baseUrl);
|
|
994
|
+
if (existing) {
|
|
995
|
+
return existing;
|
|
996
|
+
}
|
|
997
|
+
const client = new OpenAI({
|
|
998
|
+
apiKey: config.apiKey,
|
|
999
|
+
baseURL: baseUrl,
|
|
1000
|
+
timeout: capabilities.requestTimeoutMs,
|
|
1001
|
+
maxRetries: 0
|
|
1002
|
+
});
|
|
1003
|
+
clients.set(baseUrl, client);
|
|
1004
|
+
return client;
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
function isProviderClientPool(value) {
|
|
1008
|
+
return Boolean(
|
|
1009
|
+
value && typeof value === "object" && typeof value.candidates === "function" && typeof value.markHealthy === "function"
|
|
1010
|
+
);
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
// src/provider/request.ts
|
|
1014
|
+
async function fetchAssistantResponse(client, messages, request, tools, callbacks, abortSignal, onRequestMetric, observability) {
|
|
1015
|
+
const capabilities = resolveProviderCapabilities(request);
|
|
1016
|
+
const adapter = selectProviderWireAdapter(capabilities.wireApi);
|
|
1017
|
+
return tryFetch(
|
|
1018
|
+
adapter,
|
|
1019
|
+
client,
|
|
1020
|
+
messages,
|
|
1021
|
+
request,
|
|
1022
|
+
tools,
|
|
1023
|
+
callbacks,
|
|
1024
|
+
false,
|
|
1025
|
+
abortSignal,
|
|
1026
|
+
onRequestMetric,
|
|
1027
|
+
observability
|
|
1028
|
+
);
|
|
1029
|
+
}
|
|
1030
|
+
async function tryFetch(adapter, client, messages, request, tools, callbacks, forceReasoning, abortSignal, onRequestMetric, observability) {
|
|
1031
|
+
const startedAt = Date.now();
|
|
1032
|
+
let latestMetric;
|
|
1033
|
+
let resolvedBaseUrl;
|
|
1034
|
+
const forwardMetric = (metric) => {
|
|
1035
|
+
latestMetric = metric;
|
|
1036
|
+
onRequestMetric?.(metric);
|
|
1037
|
+
};
|
|
1038
|
+
if (observability) {
|
|
1039
|
+
await recordObservabilityEvent(observability.rootDir, {
|
|
1040
|
+
event: "model.request",
|
|
1041
|
+
status: "started",
|
|
1042
|
+
sessionId: observability.sessionId,
|
|
1043
|
+
identityKind: observability.identityKind,
|
|
1044
|
+
identityName: observability.identityName,
|
|
1045
|
+
model: request.model,
|
|
1046
|
+
details: {
|
|
1047
|
+
provider: request.provider,
|
|
1048
|
+
configuredModel: observability.configuredModel,
|
|
1049
|
+
requestModel: request.model,
|
|
1050
|
+
wireApi: adapter.wireApi,
|
|
1051
|
+
baseUrl: resolvedBaseUrl
|
|
1052
|
+
}
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
try {
|
|
1056
|
+
const response = await withApiRetries(
|
|
1057
|
+
() => invokeWithProviderClients(client, async (providerClient, baseUrl) => {
|
|
1058
|
+
resolvedBaseUrl = baseUrl;
|
|
1059
|
+
return adapter.fetchStreaming(providerClient, {
|
|
1060
|
+
provider: request.provider,
|
|
1061
|
+
model: request.model,
|
|
1062
|
+
messages,
|
|
1063
|
+
tools,
|
|
1064
|
+
callbacks,
|
|
1065
|
+
forceReasoning,
|
|
1066
|
+
thinking: request.thinking,
|
|
1067
|
+
reasoningEffort: request.reasoningEffort,
|
|
1068
|
+
maxOutputTokens: request.maxOutputTokens,
|
|
1069
|
+
sessionId: request.sessionId,
|
|
1070
|
+
projectRoot: request.projectRoot,
|
|
1071
|
+
abortSignal,
|
|
1072
|
+
onRequestMetric: forwardMetric
|
|
1073
|
+
});
|
|
1074
|
+
}),
|
|
1075
|
+
abortSignal
|
|
1076
|
+
);
|
|
1077
|
+
if (observability) {
|
|
1078
|
+
await recordObservabilityEvent(observability.rootDir, {
|
|
1079
|
+
event: "model.request",
|
|
1080
|
+
status: "completed",
|
|
1081
|
+
sessionId: observability.sessionId,
|
|
1082
|
+
identityKind: observability.identityKind,
|
|
1083
|
+
identityName: observability.identityName,
|
|
1084
|
+
model: request.model,
|
|
1085
|
+
durationMs: Date.now() - startedAt,
|
|
1086
|
+
details: {
|
|
1087
|
+
provider: request.provider,
|
|
1088
|
+
configuredModel: observability.configuredModel,
|
|
1089
|
+
requestModel: request.model,
|
|
1090
|
+
wireApi: adapter.wireApi,
|
|
1091
|
+
baseUrl: resolvedBaseUrl,
|
|
1092
|
+
usage: latestMetric?.usage,
|
|
1093
|
+
usageAvailable: hasProviderUsageSnapshot(latestMetric?.usage)
|
|
1094
|
+
}
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
return response;
|
|
1098
|
+
} catch (error) {
|
|
1099
|
+
if (isAbortError(error)) {
|
|
1100
|
+
throw error;
|
|
1101
|
+
}
|
|
1102
|
+
try {
|
|
1103
|
+
const response = await withApiRetries(
|
|
1104
|
+
() => invokeWithProviderClients(client, async (providerClient, baseUrl) => {
|
|
1105
|
+
resolvedBaseUrl = baseUrl;
|
|
1106
|
+
return adapter.fetchNonStreaming(providerClient, {
|
|
1107
|
+
provider: request.provider,
|
|
1108
|
+
model: request.model,
|
|
1109
|
+
messages,
|
|
1110
|
+
tools,
|
|
1111
|
+
callbacks,
|
|
1112
|
+
forceReasoning,
|
|
1113
|
+
thinking: request.thinking,
|
|
1114
|
+
reasoningEffort: request.reasoningEffort,
|
|
1115
|
+
maxOutputTokens: request.maxOutputTokens,
|
|
1116
|
+
sessionId: request.sessionId,
|
|
1117
|
+
projectRoot: request.projectRoot,
|
|
1118
|
+
abortSignal,
|
|
1119
|
+
onRequestMetric: forwardMetric
|
|
1120
|
+
});
|
|
1121
|
+
}),
|
|
1122
|
+
abortSignal
|
|
1123
|
+
);
|
|
1124
|
+
if (observability) {
|
|
1125
|
+
await recordObservabilityEvent(observability.rootDir, {
|
|
1126
|
+
event: "model.request",
|
|
1127
|
+
status: "completed",
|
|
1128
|
+
sessionId: observability.sessionId,
|
|
1129
|
+
identityKind: observability.identityKind,
|
|
1130
|
+
identityName: observability.identityName,
|
|
1131
|
+
model: request.model,
|
|
1132
|
+
durationMs: Date.now() - startedAt,
|
|
1133
|
+
details: {
|
|
1134
|
+
provider: request.provider,
|
|
1135
|
+
configuredModel: observability.configuredModel,
|
|
1136
|
+
requestModel: request.model,
|
|
1137
|
+
wireApi: adapter.wireApi,
|
|
1138
|
+
baseUrl: resolvedBaseUrl,
|
|
1139
|
+
usage: latestMetric?.usage,
|
|
1140
|
+
usageAvailable: hasProviderUsageSnapshot(latestMetric?.usage)
|
|
1141
|
+
}
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
1144
|
+
return response;
|
|
1145
|
+
} catch (fallbackError) {
|
|
1146
|
+
if (!isAbortError(fallbackError) && observability) {
|
|
1147
|
+
await recordObservabilityEvent(observability.rootDir, {
|
|
1148
|
+
event: "model.request",
|
|
1149
|
+
status: "failed",
|
|
1150
|
+
sessionId: observability.sessionId,
|
|
1151
|
+
identityKind: observability.identityKind,
|
|
1152
|
+
identityName: observability.identityName,
|
|
1153
|
+
model: request.model,
|
|
1154
|
+
durationMs: Date.now() - startedAt,
|
|
1155
|
+
error: fallbackError,
|
|
1156
|
+
details: {
|
|
1157
|
+
provider: request.provider,
|
|
1158
|
+
configuredModel: observability.configuredModel,
|
|
1159
|
+
requestModel: request.model,
|
|
1160
|
+
wireApi: adapter.wireApi,
|
|
1161
|
+
baseUrl: resolvedBaseUrl,
|
|
1162
|
+
usage: latestMetric?.usage,
|
|
1163
|
+
usageAvailable: hasProviderUsageSnapshot(latestMetric?.usage)
|
|
1164
|
+
}
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
throw fallbackError;
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
function selectProviderWireAdapter(wireApi) {
|
|
1172
|
+
if (wireApi === "responses") {
|
|
1173
|
+
return responsesAdapter;
|
|
1174
|
+
}
|
|
1175
|
+
return chatCompletionsAdapter;
|
|
1176
|
+
}
|
|
1177
|
+
async function invokeWithProviderClients(client, operation) {
|
|
1178
|
+
if (!isProviderClientPool(client)) {
|
|
1179
|
+
return operation(client, void 0);
|
|
1180
|
+
}
|
|
1181
|
+
let lastError;
|
|
1182
|
+
const candidates = client.candidates();
|
|
1183
|
+
for (let index = 0; index < candidates.length; index += 1) {
|
|
1184
|
+
const candidate = candidates[index];
|
|
1185
|
+
try {
|
|
1186
|
+
const result = await operation(candidate.client, candidate.baseUrl);
|
|
1187
|
+
client.markHealthy(candidate.baseUrl);
|
|
1188
|
+
return result;
|
|
1189
|
+
} catch (error) {
|
|
1190
|
+
lastError = error;
|
|
1191
|
+
if (isAbortError(error)) {
|
|
1192
|
+
throw error;
|
|
1193
|
+
}
|
|
1194
|
+
const hasMoreCandidates = index < candidates.length - 1;
|
|
1195
|
+
if (!hasMoreCandidates || !canRetryWithAlternateBaseUrl(error)) {
|
|
1196
|
+
throw error;
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
throw lastError;
|
|
1201
|
+
}
|
|
1202
|
+
function canRetryWithAlternateBaseUrl(error) {
|
|
1203
|
+
const status = error.status;
|
|
1204
|
+
const message = String(error.message ?? error).toLowerCase();
|
|
1205
|
+
return status === 404 || status === 405 || message.includes("404") || message.includes("not found");
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
// src/session/messages.ts
|
|
1209
|
+
function buildChatMessages(systemPrompt, messages, contextWindowMessages, model) {
|
|
1210
|
+
const recentMessages = messages.slice(-contextWindowMessages);
|
|
1211
|
+
return [
|
|
1212
|
+
{
|
|
1213
|
+
role: "system",
|
|
1214
|
+
content: systemPrompt
|
|
1215
|
+
},
|
|
1216
|
+
...recentMessages.map(
|
|
1217
|
+
(message, index) => toChatMessage(message, {
|
|
1218
|
+
includeReasoning: shouldIncludeStoredAssistantReasoning(recentMessages, index, model)
|
|
1219
|
+
})
|
|
1220
|
+
)
|
|
1221
|
+
];
|
|
1222
|
+
}
|
|
1223
|
+
function createMessage(role, content, options = {}) {
|
|
1224
|
+
return {
|
|
1225
|
+
role,
|
|
1226
|
+
content,
|
|
1227
|
+
source: options.source,
|
|
1228
|
+
name: options.name,
|
|
1229
|
+
tool_calls: options.toolCalls,
|
|
1230
|
+
reasoningContent: options.reasoningContent,
|
|
1231
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1234
|
+
function createToolMessage(toolCallId, content, name) {
|
|
1235
|
+
return {
|
|
1236
|
+
role: "tool",
|
|
1237
|
+
content,
|
|
1238
|
+
tool_call_id: toolCallId,
|
|
1239
|
+
name,
|
|
1240
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1241
|
+
};
|
|
1242
|
+
}
|
|
1243
|
+
function readReasoningContent(message) {
|
|
1244
|
+
const candidate = message.reasoning_content;
|
|
1245
|
+
return typeof candidate === "string" && candidate.length > 0 ? candidate : void 0;
|
|
1246
|
+
}
|
|
1247
|
+
function collapseContentParts(content) {
|
|
1248
|
+
if (!Array.isArray(content)) {
|
|
1249
|
+
return null;
|
|
1250
|
+
}
|
|
1251
|
+
const fragments = content.map((part) => {
|
|
1252
|
+
if (part && typeof part === "object" && "text" in part && typeof part.text === "string") {
|
|
1253
|
+
return part.text;
|
|
1254
|
+
}
|
|
1255
|
+
return "";
|
|
1256
|
+
}).filter(Boolean);
|
|
1257
|
+
return fragments.length > 0 ? fragments.join("") : null;
|
|
1258
|
+
}
|
|
1259
|
+
function toChatMessage(message, options) {
|
|
1260
|
+
if (message.role === "tool") {
|
|
1261
|
+
return {
|
|
1262
|
+
role: "tool",
|
|
1263
|
+
content: message.content ?? "",
|
|
1264
|
+
tool_call_id: message.tool_call_id ?? ""
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1267
|
+
if (message.role === "assistant" && message.tool_calls?.length) {
|
|
1268
|
+
const assistantMessage = {
|
|
1269
|
+
role: "assistant",
|
|
1270
|
+
content: message.content,
|
|
1271
|
+
tool_calls: message.tool_calls
|
|
1272
|
+
};
|
|
1273
|
+
if (options.includeReasoning && message.reasoningContent !== void 0) {
|
|
1274
|
+
assistantMessage.reasoning_content = message.reasoningContent;
|
|
1275
|
+
}
|
|
1276
|
+
return assistantMessage;
|
|
1277
|
+
}
|
|
1278
|
+
const baseMessage = {
|
|
1279
|
+
role: message.role,
|
|
1280
|
+
content: message.content ?? "",
|
|
1281
|
+
name: message.name
|
|
1282
|
+
};
|
|
1283
|
+
if (message.role === "assistant" && options.includeReasoning && message.reasoningContent !== void 0) {
|
|
1284
|
+
baseMessage.reasoning_content = message.reasoningContent;
|
|
1285
|
+
}
|
|
1286
|
+
return baseMessage;
|
|
1287
|
+
}
|
|
1288
|
+
function findLatestUserIndex(messages) {
|
|
1289
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
1290
|
+
if (messages[index]?.role === "user") {
|
|
1291
|
+
return index;
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
return -1;
|
|
1295
|
+
}
|
|
1296
|
+
function expandStartToToolBoundary(messages, startIndex) {
|
|
1297
|
+
let index = Math.max(0, Math.min(startIndex, messages.length - 1));
|
|
1298
|
+
while (index > 0 && messages[index]?.role === "tool") {
|
|
1299
|
+
index -= 1;
|
|
1300
|
+
}
|
|
1301
|
+
return index;
|
|
1302
|
+
}
|
|
1303
|
+
function modelUsesReasoningContent(model) {
|
|
1304
|
+
return resolveProviderCapabilities({ model }).supportsReasoningContent;
|
|
1305
|
+
}
|
|
1306
|
+
function isAssistantMessageInLatestTurn(messages, index) {
|
|
1307
|
+
return messages[index]?.role === "assistant" && index > findLatestUserIndex(messages);
|
|
1308
|
+
}
|
|
1309
|
+
function shouldIncludeStoredAssistantReasoning(messages, index, model) {
|
|
1310
|
+
return modelUsesReasoningContent(model) && messages[index]?.role === "assistant";
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
// src/session/turnFrame.ts
|
|
1314
|
+
function isInternalMessage(message) {
|
|
1315
|
+
if (typeof message === "object" && message !== null && "source" in message) {
|
|
1316
|
+
return message.source === "internal";
|
|
1317
|
+
}
|
|
1318
|
+
return false;
|
|
1319
|
+
}
|
|
1320
|
+
function createInternalReminder(text) {
|
|
1321
|
+
return text.trim();
|
|
1322
|
+
}
|
|
1323
|
+
function readUserInput(message) {
|
|
1324
|
+
if (typeof message === "object" && message !== null) {
|
|
1325
|
+
if (isInternalMessage(message)) {
|
|
1326
|
+
return void 0;
|
|
1327
|
+
}
|
|
1328
|
+
const normalized2 = oneLine(message.content ?? "");
|
|
1329
|
+
return normalized2 || void 0;
|
|
1330
|
+
}
|
|
1331
|
+
if (isInternalMessage(message)) {
|
|
1332
|
+
return void 0;
|
|
1333
|
+
}
|
|
1334
|
+
const normalized = oneLine(message ?? "");
|
|
1335
|
+
return normalized || void 0;
|
|
1336
|
+
}
|
|
1337
|
+
function findLatestUserInputIndex(messages) {
|
|
1338
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
1339
|
+
const message = messages[index];
|
|
1340
|
+
if (message?.role === "user" && readUserInput(message)) {
|
|
1341
|
+
return index;
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
return -1;
|
|
1345
|
+
}
|
|
1346
|
+
function sliceCurrentUserInputFrame(messages) {
|
|
1347
|
+
const frameStart = findLatestUserInputIndex(messages);
|
|
1348
|
+
if (frameStart < 0) {
|
|
1349
|
+
return [];
|
|
1350
|
+
}
|
|
1351
|
+
const frame = messages.slice(frameStart);
|
|
1352
|
+
return frame.filter((message) => !(message.role === "user" && isInternalMessage(message)));
|
|
1353
|
+
}
|
|
1354
|
+
function oneLine(value) {
|
|
1355
|
+
return value.replace(/\s+/g, " ").trim();
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
// src/session/taskStateHistory.ts
|
|
1359
|
+
import path3 from "path";
|
|
1360
|
+
function collectActiveFiles(messages) {
|
|
1361
|
+
const files = [];
|
|
1362
|
+
for (const message of messages) {
|
|
1363
|
+
if (!message) {
|
|
1364
|
+
continue;
|
|
1365
|
+
}
|
|
1366
|
+
if (message.role === "assistant" && message.tool_calls?.length) {
|
|
1367
|
+
for (const toolCall of message.tool_calls) {
|
|
1368
|
+
const parsed = safeParseObject(toolCall.function.arguments);
|
|
1369
|
+
collectPathsFromValue(parsed, files);
|
|
1370
|
+
}
|
|
1371
|
+
continue;
|
|
1372
|
+
}
|
|
1373
|
+
if (message.role === "tool") {
|
|
1374
|
+
const parsed = safeParseObject(message.content ?? "");
|
|
1375
|
+
collectPathsFromValue(parsed, files);
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
return files.map((value) => normalizeFilePath(value)).filter(Boolean);
|
|
1379
|
+
}
|
|
1380
|
+
function collectPlannedActions(messages) {
|
|
1381
|
+
const actions = [];
|
|
1382
|
+
for (const message of messages) {
|
|
1383
|
+
if (message?.role !== "assistant" || !message.tool_calls?.length) {
|
|
1384
|
+
continue;
|
|
1385
|
+
}
|
|
1386
|
+
const names = message.tool_calls.map((toolCall) => toolCall.function.name).join(", ");
|
|
1387
|
+
if (names) {
|
|
1388
|
+
actions.push(`plan ${names}`);
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
return actions;
|
|
1392
|
+
}
|
|
1393
|
+
function collectCompletedActions(messages) {
|
|
1394
|
+
const actions = [];
|
|
1395
|
+
for (const message of messages) {
|
|
1396
|
+
if (message?.role !== "tool" || !message.name) {
|
|
1397
|
+
continue;
|
|
1398
|
+
}
|
|
1399
|
+
const content = message.content ?? "";
|
|
1400
|
+
const parsed = safeParseObject(content);
|
|
1401
|
+
if (parsed && typeof parsed.error === "string" && parsed.error.length > 0) {
|
|
1402
|
+
continue;
|
|
1403
|
+
}
|
|
1404
|
+
actions.push(formatCompletedAction(message.name, parsed));
|
|
1405
|
+
}
|
|
1406
|
+
return actions.filter(Boolean);
|
|
1407
|
+
}
|
|
1408
|
+
function collectBlockers(messages) {
|
|
1409
|
+
const blockers = [];
|
|
1410
|
+
for (const message of messages) {
|
|
1411
|
+
if (message?.role !== "tool") {
|
|
1412
|
+
continue;
|
|
1413
|
+
}
|
|
1414
|
+
const parsed = safeParseObject(message.content ?? "");
|
|
1415
|
+
if (!parsed || typeof parsed.error !== "string" || parsed.error.length === 0) {
|
|
1416
|
+
continue;
|
|
1417
|
+
}
|
|
1418
|
+
blockers.push(`${message.name ?? "tool"}: ${truncate(oneLine2(parsed.error), 180)}`);
|
|
1419
|
+
}
|
|
1420
|
+
return blockers;
|
|
1421
|
+
}
|
|
1422
|
+
function oneLine2(value) {
|
|
1423
|
+
return value.replace(/\s+/g, " ").trim();
|
|
1424
|
+
}
|
|
1425
|
+
function truncate(value, maxChars) {
|
|
1426
|
+
return value.length <= maxChars ? value : `${value.slice(0, maxChars)}...`;
|
|
1427
|
+
}
|
|
1428
|
+
function formatCompletedAction(toolName, payload) {
|
|
1429
|
+
const pathValue = normalizeFilePath(readPath(payload?.path));
|
|
1430
|
+
if (toolName === "bash") {
|
|
1431
|
+
const command = typeof payload?.command === "string" ? payload.command : "command";
|
|
1432
|
+
const exitCode = typeof payload?.exitCode === "number" ? payload.exitCode : "unknown";
|
|
1433
|
+
return `bash ${truncate(oneLine2(command), 120)} (exit ${exitCode})`;
|
|
1434
|
+
}
|
|
1435
|
+
return pathValue ? `${toolName} ${truncate(pathValue, 160)}` : toolName;
|
|
1436
|
+
}
|
|
1437
|
+
function collectPathsFromValue(value, bucket) {
|
|
1438
|
+
if (!value || typeof value !== "object") {
|
|
1439
|
+
return;
|
|
1440
|
+
}
|
|
1441
|
+
if (Array.isArray(value)) {
|
|
1442
|
+
for (const item of value) {
|
|
1443
|
+
collectPathsFromValue(item, bucket);
|
|
1444
|
+
}
|
|
1445
|
+
return;
|
|
1446
|
+
}
|
|
1447
|
+
for (const [key, item] of Object.entries(value)) {
|
|
1448
|
+
if (typeof item === "string" && isPathLikeKey(key)) {
|
|
1449
|
+
bucket.push(item);
|
|
1450
|
+
continue;
|
|
1451
|
+
}
|
|
1452
|
+
if (Array.isArray(item)) {
|
|
1453
|
+
for (const nested of item) {
|
|
1454
|
+
collectPathsFromValue(nested, bucket);
|
|
1455
|
+
}
|
|
1456
|
+
continue;
|
|
1457
|
+
}
|
|
1458
|
+
if (item && typeof item === "object") {
|
|
1459
|
+
collectPathsFromValue(item, bucket);
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
function isPathLikeKey(key) {
|
|
1464
|
+
return key === "path" || key === "cwd" || key.endsWith("Path");
|
|
1465
|
+
}
|
|
1466
|
+
function readPath(value) {
|
|
1467
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
1468
|
+
}
|
|
1469
|
+
function safeParseObject(raw) {
|
|
1470
|
+
try {
|
|
1471
|
+
const parsed = JSON.parse(raw);
|
|
1472
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
1473
|
+
} catch {
|
|
1474
|
+
return null;
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
function normalizeFilePath(value) {
|
|
1478
|
+
if (!value) {
|
|
1479
|
+
return void 0;
|
|
1480
|
+
}
|
|
1481
|
+
const trimmed = value.trim();
|
|
1482
|
+
if (!trimmed || trimmed.startsWith("<") || trimmed.includes("\n")) {
|
|
1483
|
+
return void 0;
|
|
1484
|
+
}
|
|
1485
|
+
if (trimmed.length > 260) {
|
|
1486
|
+
return truncate(trimmed, 260);
|
|
1487
|
+
}
|
|
1488
|
+
return trimmed.includes(path3.sep) || trimmed.includes("/") || trimmed.includes(".") ? trimmed : void 0;
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
// src/session/taskState.ts
|
|
1492
|
+
var MAX_ACTIVE_FILES = 12;
|
|
1493
|
+
var MAX_PLANNED_ACTIONS = 8;
|
|
1494
|
+
var MAX_COMPLETED_ACTIONS = 12;
|
|
1495
|
+
var MAX_BLOCKERS = 8;
|
|
1496
|
+
function createEmptyTaskState(timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
1497
|
+
return {
|
|
1498
|
+
activeFiles: [],
|
|
1499
|
+
plannedActions: [],
|
|
1500
|
+
completedActions: [],
|
|
1501
|
+
blockers: [],
|
|
1502
|
+
lastUpdatedAt: timestamp
|
|
1503
|
+
};
|
|
1504
|
+
}
|
|
1505
|
+
function deriveTaskState(messages, previous) {
|
|
1506
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1507
|
+
const currentTurn = findCurrentTurn(messages);
|
|
1508
|
+
const frameMessages = currentTurn ? messages.slice(currentTurn.startIndex) : messages;
|
|
1509
|
+
return {
|
|
1510
|
+
focus: previous?.focus,
|
|
1511
|
+
activeFiles: takeLastUnique(collectActiveFiles(frameMessages), MAX_ACTIVE_FILES),
|
|
1512
|
+
plannedActions: takeLastUnique(collectPlannedActions(frameMessages), MAX_PLANNED_ACTIONS),
|
|
1513
|
+
completedActions: takeLastUnique(collectCompletedActions(frameMessages), MAX_COMPLETED_ACTIONS),
|
|
1514
|
+
blockers: takeLastUnique(collectBlockers(frameMessages), MAX_BLOCKERS),
|
|
1515
|
+
lastUpdatedAt: now
|
|
1516
|
+
};
|
|
1517
|
+
}
|
|
1518
|
+
function normalizeTaskState(taskState) {
|
|
1519
|
+
if (!taskState) {
|
|
1520
|
+
return void 0;
|
|
1521
|
+
}
|
|
1522
|
+
return {
|
|
1523
|
+
focus: typeof taskState.focus === "string" ? taskState.focus : void 0,
|
|
1524
|
+
activeFiles: takeLastUnique(taskState.activeFiles ?? [], MAX_ACTIVE_FILES),
|
|
1525
|
+
plannedActions: takeLastUnique(taskState.plannedActions ?? [], MAX_PLANNED_ACTIONS),
|
|
1526
|
+
completedActions: takeLastUnique(taskState.completedActions ?? [], MAX_COMPLETED_ACTIONS),
|
|
1527
|
+
blockers: takeLastUnique(taskState.blockers ?? [], MAX_BLOCKERS),
|
|
1528
|
+
lastUpdatedAt: typeof taskState.lastUpdatedAt === "string" && taskState.lastUpdatedAt.length > 0 ? taskState.lastUpdatedAt : (/* @__PURE__ */ new Date()).toISOString()
|
|
1529
|
+
};
|
|
1530
|
+
}
|
|
1531
|
+
function formatTaskStateBlock(taskState) {
|
|
1532
|
+
if (!taskState) {
|
|
1533
|
+
return "- none";
|
|
1534
|
+
}
|
|
1535
|
+
const parts = [
|
|
1536
|
+
taskState.focus ? `- Focus: ${taskState.focus}` : "- Focus: none",
|
|
1537
|
+
`- Planned actions: ${formatList(taskState.plannedActions)}`,
|
|
1538
|
+
`- Blockers: ${formatList(taskState.blockers)}`,
|
|
1539
|
+
`- Updated at: ${taskState.lastUpdatedAt}`
|
|
1540
|
+
];
|
|
1541
|
+
return parts.join("\n");
|
|
1542
|
+
}
|
|
1543
|
+
function normalizeSessionRecord(session) {
|
|
1544
|
+
return {
|
|
1545
|
+
...session,
|
|
1546
|
+
messageCount: Array.isArray(session.messages) ? session.messages.length : 0,
|
|
1547
|
+
messages: Array.isArray(session.messages) ? session.messages : [],
|
|
1548
|
+
taskState: normalizeTaskState(deriveTaskState(Array.isArray(session.messages) ? session.messages : [], session.taskState))
|
|
1549
|
+
};
|
|
1550
|
+
}
|
|
1551
|
+
function applyCurrentTurnFrame(session, input, timestamp = (/* @__PURE__ */ new Date()).toISOString(), source = "external") {
|
|
1552
|
+
const userInput = readUserInput({ content: input, source });
|
|
1553
|
+
if (!userInput) {
|
|
1554
|
+
return {
|
|
1555
|
+
...session,
|
|
1556
|
+
taskState: normalizeTaskState(session.taskState ?? createEmptyTaskState(timestamp))
|
|
1557
|
+
};
|
|
1558
|
+
}
|
|
1559
|
+
return {
|
|
1560
|
+
...session,
|
|
1561
|
+
taskState: {
|
|
1562
|
+
focus: session.taskState?.focus,
|
|
1563
|
+
activeFiles: [],
|
|
1564
|
+
plannedActions: [],
|
|
1565
|
+
completedActions: [],
|
|
1566
|
+
blockers: [],
|
|
1567
|
+
lastUpdatedAt: timestamp
|
|
1568
|
+
}
|
|
1569
|
+
};
|
|
1570
|
+
}
|
|
1571
|
+
function findCurrentTurn(messages) {
|
|
1572
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
1573
|
+
const message = messages[index];
|
|
1574
|
+
if (message?.role !== "user") {
|
|
1575
|
+
continue;
|
|
1576
|
+
}
|
|
1577
|
+
const normalized = readUserInput(message);
|
|
1578
|
+
if (normalized) {
|
|
1579
|
+
return {
|
|
1580
|
+
startIndex: index
|
|
1581
|
+
};
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
return void 0;
|
|
1585
|
+
}
|
|
1586
|
+
function takeLastUnique(values, limit) {
|
|
1587
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1588
|
+
const result = [];
|
|
1589
|
+
for (let index = values.length - 1; index >= 0; index -= 1) {
|
|
1590
|
+
const value = values[index]?.trim();
|
|
1591
|
+
if (!value || seen.has(value)) {
|
|
1592
|
+
continue;
|
|
1593
|
+
}
|
|
1594
|
+
seen.add(value);
|
|
1595
|
+
result.unshift(value);
|
|
1596
|
+
if (result.length >= limit) {
|
|
1597
|
+
break;
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
return result;
|
|
1601
|
+
}
|
|
1602
|
+
function formatList(values) {
|
|
1603
|
+
return values.length > 0 ? values.join(" | ") : "none";
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
// src/utils/normalize.ts
|
|
1607
|
+
function normalizeTimestamp(value, fallback) {
|
|
1608
|
+
return typeof value === "string" && value.trim().length > 0 ? value : fallback;
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
// src/session/sessionDiff.ts
|
|
1612
|
+
var MAX_SESSION_DIFF_CHANGES = 20;
|
|
1613
|
+
var MAX_SESSION_DIFF_PATHS = 24;
|
|
1614
|
+
var MAX_DIFF_PREVIEW_CHARS = 2e3;
|
|
1615
|
+
function createEmptySessionDiff(timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
1616
|
+
return {
|
|
1617
|
+
version: 1,
|
|
1618
|
+
changedPaths: [],
|
|
1619
|
+
changes: [],
|
|
1620
|
+
updatedAt: timestamp
|
|
1621
|
+
};
|
|
1622
|
+
}
|
|
1623
|
+
function normalizeSessionDiff(sessionDiff, timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
1624
|
+
const normalizedChanges = (sessionDiff?.changes ?? []).map((change) => normalizeSessionDiffChange(change, timestamp)).filter((change) => Boolean(change));
|
|
1625
|
+
return {
|
|
1626
|
+
version: 1,
|
|
1627
|
+
changedPaths: takeLastUniquePaths(sessionDiff?.changedPaths ?? []),
|
|
1628
|
+
changes: normalizedChanges.slice(-MAX_SESSION_DIFF_CHANGES),
|
|
1629
|
+
updatedAt: normalizeTimestamp(sessionDiff?.updatedAt, timestamp)
|
|
1630
|
+
};
|
|
1631
|
+
}
|
|
1632
|
+
function normalizeSessionDiffState(session) {
|
|
1633
|
+
return {
|
|
1634
|
+
...session,
|
|
1635
|
+
sessionDiff: normalizeSessionDiff(session.sessionDiff)
|
|
1636
|
+
};
|
|
1637
|
+
}
|
|
1638
|
+
function noteSessionDiff(session, change, timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
1639
|
+
if (!change) {
|
|
1640
|
+
return normalizeSessionDiffState(session);
|
|
1641
|
+
}
|
|
1642
|
+
const current = normalizeSessionDiff(session.sessionDiff, timestamp);
|
|
1643
|
+
const normalizedChange = normalizeSessionDiffChange(change, timestamp);
|
|
1644
|
+
if (!normalizedChange) {
|
|
1645
|
+
return {
|
|
1646
|
+
...session,
|
|
1647
|
+
sessionDiff: current
|
|
1648
|
+
};
|
|
1649
|
+
}
|
|
1650
|
+
return {
|
|
1651
|
+
...session,
|
|
1652
|
+
sessionDiff: {
|
|
1653
|
+
version: 1,
|
|
1654
|
+
changedPaths: takeLastUniquePaths([...current.changedPaths, ...normalizedChange.changedPaths]),
|
|
1655
|
+
changes: [...current.changes, normalizedChange].slice(-MAX_SESSION_DIFF_CHANGES),
|
|
1656
|
+
updatedAt: timestamp
|
|
1657
|
+
}
|
|
1658
|
+
};
|
|
1659
|
+
}
|
|
1660
|
+
function normalizeSessionDiffChange(change, timestamp) {
|
|
1661
|
+
const toolName = normalizeText2(change?.toolName);
|
|
1662
|
+
const changedPaths = takeLastUniquePaths(change?.changedPaths ?? []);
|
|
1663
|
+
if (!toolName || changedPaths.length === 0) {
|
|
1664
|
+
return null;
|
|
1665
|
+
}
|
|
1666
|
+
return {
|
|
1667
|
+
toolName,
|
|
1668
|
+
changeId: normalizeText2(change?.changeId) || void 0,
|
|
1669
|
+
changedPaths,
|
|
1670
|
+
diff: truncate2(change?.diff, MAX_DIFF_PREVIEW_CHARS),
|
|
1671
|
+
diagnosticsStatus: normalizeDiagnosticsStatus(change?.diagnosticsStatus),
|
|
1672
|
+
errorCount: normalizeCount(change?.errorCount),
|
|
1673
|
+
warningCount: normalizeCount(change?.warningCount),
|
|
1674
|
+
recordedAt: normalizeTimestamp(change?.recordedAt, timestamp)
|
|
1675
|
+
};
|
|
1676
|
+
}
|
|
1677
|
+
function takeLastUniquePaths(paths) {
|
|
1678
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1679
|
+
const result = [];
|
|
1680
|
+
for (let index = paths.length - 1; index >= 0; index -= 1) {
|
|
1681
|
+
const normalized = normalizeText2(paths[index]);
|
|
1682
|
+
if (!normalized || seen.has(normalized)) {
|
|
1683
|
+
continue;
|
|
1684
|
+
}
|
|
1685
|
+
seen.add(normalized);
|
|
1686
|
+
result.unshift(normalized);
|
|
1687
|
+
if (result.length >= MAX_SESSION_DIFF_PATHS) {
|
|
1688
|
+
break;
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
return result;
|
|
1692
|
+
}
|
|
1693
|
+
function normalizeDiagnosticsStatus(value) {
|
|
1694
|
+
return value === "issues" || value === "unavailable" ? value : "clean";
|
|
1695
|
+
}
|
|
1696
|
+
function normalizeCount(value) {
|
|
1697
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.trunc(value) : 0;
|
|
1698
|
+
}
|
|
1699
|
+
function normalizeText2(value) {
|
|
1700
|
+
return String(value ?? "").trim();
|
|
1701
|
+
}
|
|
1702
|
+
function truncate2(value, maxChars) {
|
|
1703
|
+
if (!value) {
|
|
1704
|
+
return void 0;
|
|
1705
|
+
}
|
|
1706
|
+
return value.length <= maxChars ? value : `${value.slice(0, maxChars)}...`;
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
// src/agent/runtimeTransition/shared.ts
|
|
1710
|
+
var MAX_REASON_ITEMS = 6;
|
|
1711
|
+
var MAX_REASON_TEXT_CHARS = 220;
|
|
1712
|
+
function takeLastUnique2(values) {
|
|
1713
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1714
|
+
const result = [];
|
|
1715
|
+
for (let index = values.length - 1; index >= 0; index -= 1) {
|
|
1716
|
+
const normalized = normalizeText3(values[index]);
|
|
1717
|
+
if (!normalized || seen.has(normalized)) {
|
|
1718
|
+
continue;
|
|
1719
|
+
}
|
|
1720
|
+
seen.add(normalized);
|
|
1721
|
+
result.unshift(normalized);
|
|
1722
|
+
if (result.length >= MAX_REASON_ITEMS) {
|
|
1723
|
+
break;
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
return result;
|
|
1727
|
+
}
|
|
1728
|
+
function truncate3(value) {
|
|
1729
|
+
return value.length <= MAX_REASON_TEXT_CHARS ? value : `${value.slice(0, MAX_REASON_TEXT_CHARS)}...`;
|
|
1730
|
+
}
|
|
1731
|
+
function normalizeText3(value) {
|
|
1732
|
+
return String(value ?? "").replace(/\s+/g, " ").trim();
|
|
1733
|
+
}
|
|
1734
|
+
function clampWholeNumber(value, min, max, fallback) {
|
|
1735
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
1736
|
+
return fallback;
|
|
1737
|
+
}
|
|
1738
|
+
return Math.max(min, Math.min(max, Math.trunc(value)));
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
// src/agent/runtimeTransition/builders.ts
|
|
1742
|
+
function createToolBatchTransition(input, timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
1743
|
+
return {
|
|
1744
|
+
action: "continue",
|
|
1745
|
+
reason: {
|
|
1746
|
+
code: "continue.after_tool_batch",
|
|
1747
|
+
toolNames: takeLastUnique2(input.toolNames),
|
|
1748
|
+
changedPaths: takeLastUnique2(input.changedPaths ?? [])
|
|
1749
|
+
},
|
|
1750
|
+
timestamp
|
|
1751
|
+
};
|
|
1752
|
+
}
|
|
1753
|
+
function createEmptyAssistantResponseTransition(timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
1754
|
+
return {
|
|
1755
|
+
action: "continue",
|
|
1756
|
+
reason: {
|
|
1757
|
+
code: "continue.empty_assistant_response"
|
|
1758
|
+
},
|
|
1759
|
+
timestamp
|
|
1760
|
+
};
|
|
1761
|
+
}
|
|
1762
|
+
function createProviderRecoveryTransition(input, timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
1763
|
+
return {
|
|
1764
|
+
action: "recover",
|
|
1765
|
+
reason: {
|
|
1766
|
+
code: "recover.provider_request_retry",
|
|
1767
|
+
consecutiveFailures: Math.max(1, Math.trunc(input.consecutiveFailures)),
|
|
1768
|
+
error: truncate3(normalizeText3(input.error?.message ?? input.error) || "request failed"),
|
|
1769
|
+
configuredModel: normalizeText3(input.configuredModel) || "unknown_model",
|
|
1770
|
+
requestModel: normalizeText3(input.requestModel) || "unknown_model",
|
|
1771
|
+
contextWindowMessages: Math.max(1, Math.trunc(input.requestConfig.contextWindowMessages)),
|
|
1772
|
+
maxContextChars: Math.max(1, Math.trunc(input.requestConfig.maxContextChars)),
|
|
1773
|
+
contextSummaryChars: Math.max(1, Math.trunc(input.requestConfig.contextSummaryChars)),
|
|
1774
|
+
delayMs: Math.max(0, Math.trunc(input.delayMs))
|
|
1775
|
+
},
|
|
1776
|
+
timestamp
|
|
1777
|
+
};
|
|
1778
|
+
}
|
|
1779
|
+
function createFinalizeTransition(input, timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
1780
|
+
return {
|
|
1781
|
+
action: "finalize",
|
|
1782
|
+
reason: {
|
|
1783
|
+
code: "finalize.completed",
|
|
1784
|
+
changedPaths: takeLastUnique2([...input.changedPaths])
|
|
1785
|
+
},
|
|
1786
|
+
timestamp
|
|
1787
|
+
};
|
|
1788
|
+
}
|
|
1789
|
+
function createExecutionWaitYieldTransition(input, timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
1790
|
+
return {
|
|
1791
|
+
action: "yield",
|
|
1792
|
+
reason: {
|
|
1793
|
+
code: "yield.execution_wait",
|
|
1794
|
+
executionIds: takeLastUnique2([...input.executionIds]),
|
|
1795
|
+
toolNames: takeLastUnique2([...input.toolNames])
|
|
1796
|
+
},
|
|
1797
|
+
timestamp
|
|
1798
|
+
};
|
|
1799
|
+
}
|
|
1800
|
+
function buildRunTurnResult(input) {
|
|
1801
|
+
return {
|
|
1802
|
+
session: input.session,
|
|
1803
|
+
changedPaths: [...input.changedPaths],
|
|
1804
|
+
transition: input.transition
|
|
1805
|
+
};
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
// src/agent/runtimeTransition/normalize.ts
|
|
1809
|
+
function normalizeRuntimeTransition(transition, timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
1810
|
+
if (!transition || typeof transition !== "object") {
|
|
1811
|
+
return void 0;
|
|
1812
|
+
}
|
|
1813
|
+
const action = normalizeAction(transition.action);
|
|
1814
|
+
const reason = transition.reason;
|
|
1815
|
+
const normalizedTimestamp = normalizeTimestamp(transition.timestamp, timestamp);
|
|
1816
|
+
if (!reason || typeof reason !== "object") {
|
|
1817
|
+
return void 0;
|
|
1818
|
+
}
|
|
1819
|
+
switch (action) {
|
|
1820
|
+
case "continue":
|
|
1821
|
+
return normalizeContinueTransition(reason, normalizedTimestamp);
|
|
1822
|
+
case "recover":
|
|
1823
|
+
return normalizeRecoverTransition(reason, normalizedTimestamp);
|
|
1824
|
+
case "finalize":
|
|
1825
|
+
return normalizeFinalizeTransition(reason, normalizedTimestamp);
|
|
1826
|
+
case "yield":
|
|
1827
|
+
return normalizeYieldTransition(reason, normalizedTimestamp);
|
|
1828
|
+
default:
|
|
1829
|
+
return void 0;
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
function normalizeContinueTransition(reason, timestamp) {
|
|
1833
|
+
switch (reason.code) {
|
|
1834
|
+
case "continue.after_tool_batch": {
|
|
1835
|
+
const toolNames = takeLastUnique2(reason.toolNames);
|
|
1836
|
+
if (toolNames.length === 0) {
|
|
1837
|
+
return void 0;
|
|
1838
|
+
}
|
|
1839
|
+
return {
|
|
1840
|
+
action: "continue",
|
|
1841
|
+
reason: {
|
|
1842
|
+
code: reason.code,
|
|
1843
|
+
toolNames,
|
|
1844
|
+
changedPaths: takeLastUnique2(reason.changedPaths ?? [])
|
|
1845
|
+
},
|
|
1846
|
+
timestamp
|
|
1847
|
+
};
|
|
1848
|
+
}
|
|
1849
|
+
case "continue.empty_assistant_response":
|
|
1850
|
+
return {
|
|
1851
|
+
action: "continue",
|
|
1852
|
+
reason: {
|
|
1853
|
+
code: reason.code
|
|
1854
|
+
},
|
|
1855
|
+
timestamp
|
|
1856
|
+
};
|
|
1857
|
+
default:
|
|
1858
|
+
return void 0;
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
function normalizeRecoverTransition(reason, timestamp) {
|
|
1862
|
+
if (reason.code !== "recover.provider_request_retry") {
|
|
1863
|
+
return void 0;
|
|
1864
|
+
}
|
|
1865
|
+
return {
|
|
1866
|
+
action: "recover",
|
|
1867
|
+
reason: {
|
|
1868
|
+
code: reason.code,
|
|
1869
|
+
consecutiveFailures: clampWholeNumber(reason.consecutiveFailures, 1, 50, 1) ?? 1,
|
|
1870
|
+
error: truncate3(normalizeText3(reason.error) || "request failed"),
|
|
1871
|
+
configuredModel: normalizeText3(reason.configuredModel) || "unknown_model",
|
|
1872
|
+
requestModel: normalizeText3(reason.requestModel) || "unknown_model",
|
|
1873
|
+
contextWindowMessages: clampWholeNumber(reason.contextWindowMessages, 1, 999, 1) ?? 1,
|
|
1874
|
+
maxContextChars: clampWholeNumber(reason.maxContextChars, 1, 1e6, 1) ?? 1,
|
|
1875
|
+
contextSummaryChars: clampWholeNumber(reason.contextSummaryChars, 1, 1e6, 1) ?? 1,
|
|
1876
|
+
delayMs: clampWholeNumber(reason.delayMs, 0, 36e5, 0) ?? 0
|
|
1877
|
+
},
|
|
1878
|
+
timestamp
|
|
1879
|
+
};
|
|
1880
|
+
}
|
|
1881
|
+
function normalizeFinalizeTransition(reason, timestamp) {
|
|
1882
|
+
if (reason.code !== "finalize.completed") {
|
|
1883
|
+
return void 0;
|
|
1884
|
+
}
|
|
1885
|
+
return {
|
|
1886
|
+
action: "finalize",
|
|
1887
|
+
reason: {
|
|
1888
|
+
code: reason.code,
|
|
1889
|
+
changedPaths: takeLastUnique2(reason.changedPaths ?? [])
|
|
1890
|
+
},
|
|
1891
|
+
timestamp
|
|
1892
|
+
};
|
|
1893
|
+
}
|
|
1894
|
+
function normalizeYieldTransition(reason, timestamp) {
|
|
1895
|
+
if (reason.code !== "yield.execution_wait") {
|
|
1896
|
+
return void 0;
|
|
1897
|
+
}
|
|
1898
|
+
const executionIds = takeLastUnique2(reason.executionIds ?? []);
|
|
1899
|
+
if (executionIds.length === 0) {
|
|
1900
|
+
return void 0;
|
|
1901
|
+
}
|
|
1902
|
+
return {
|
|
1903
|
+
action: "yield",
|
|
1904
|
+
reason: {
|
|
1905
|
+
code: reason.code,
|
|
1906
|
+
executionIds,
|
|
1907
|
+
toolNames: takeLastUnique2(reason.toolNames ?? [])
|
|
1908
|
+
},
|
|
1909
|
+
timestamp
|
|
1910
|
+
};
|
|
1911
|
+
}
|
|
1912
|
+
function normalizeAction(value) {
|
|
1913
|
+
return value === "continue" || value === "recover" || value === "finalize" || value === "yield" ? value : void 0;
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
// src/agent/runtimeTransition/flow.ts
|
|
1917
|
+
function normalizeCheckpointFlow(flow, status, timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
1918
|
+
const lastTransition = normalizeRuntimeTransition(flow?.lastTransition, timestamp);
|
|
1919
|
+
const phase = normalizePhase(lastTransition ? getRuntimeTransitionPhase(lastTransition) : flow?.phase, status);
|
|
1920
|
+
const runState = normalizeRunState({
|
|
1921
|
+
current: flow?.runState,
|
|
1922
|
+
status,
|
|
1923
|
+
timestamp
|
|
1924
|
+
});
|
|
1925
|
+
return {
|
|
1926
|
+
phase,
|
|
1927
|
+
reason: lastTransition ? formatRuntimeTransitionReason(lastTransition) : normalizeText3(flow?.reason) || void 0,
|
|
1928
|
+
recoveryFailures: lastTransition?.action === "recover" ? lastTransition.reason.consecutiveFailures : phase === "recovery" ? clampWholeNumber(flow?.recoveryFailures, 1, 50, void 0) : void 0,
|
|
1929
|
+
runState,
|
|
1930
|
+
lastTransition,
|
|
1931
|
+
updatedAt: normalizeTimestamp(flow?.updatedAt, timestamp)
|
|
1932
|
+
};
|
|
1933
|
+
}
|
|
1934
|
+
function buildCheckpointFlow(input) {
|
|
1935
|
+
const timestamp = input.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1936
|
+
const transition = normalizeRuntimeTransition(input.transition, timestamp);
|
|
1937
|
+
const phase = normalizePhase(
|
|
1938
|
+
transition ? getRuntimeTransitionPhase(transition) : input.defaultPhase ?? input.current?.phase,
|
|
1939
|
+
input.status
|
|
1940
|
+
);
|
|
1941
|
+
const runState = normalizeRunState({
|
|
1942
|
+
current: input.current?.runState,
|
|
1943
|
+
status: input.status,
|
|
1944
|
+
override: input.runState,
|
|
1945
|
+
timestamp
|
|
1946
|
+
});
|
|
1947
|
+
return {
|
|
1948
|
+
phase,
|
|
1949
|
+
reason: transition ? formatRuntimeTransitionReason(transition) : void 0,
|
|
1950
|
+
recoveryFailures: transition?.action === "recover" ? transition.reason.consecutiveFailures : void 0,
|
|
1951
|
+
runState,
|
|
1952
|
+
lastTransition: transition,
|
|
1953
|
+
updatedAt: timestamp
|
|
1954
|
+
};
|
|
1955
|
+
}
|
|
1956
|
+
function getTurnInputTransition(input, timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
1957
|
+
return void 0;
|
|
1958
|
+
}
|
|
1959
|
+
function formatRuntimeTransitionReason(transition) {
|
|
1960
|
+
return transition.reason.code;
|
|
1961
|
+
}
|
|
1962
|
+
function getRuntimeTransitionPhase(transition) {
|
|
1963
|
+
if (transition.action === "recover") {
|
|
1964
|
+
return "recovery";
|
|
1965
|
+
}
|
|
1966
|
+
return "active";
|
|
1967
|
+
}
|
|
1968
|
+
function normalizePhase(value, status) {
|
|
1969
|
+
if (status === "completed") {
|
|
1970
|
+
return "active";
|
|
1971
|
+
}
|
|
1972
|
+
return value === "recovery" ? value : "active";
|
|
1973
|
+
}
|
|
1974
|
+
function normalizeRunState(input) {
|
|
1975
|
+
const normalizedStatus = input.status === "completed" ? "idle" : input.override?.status === "busy" || input.override?.status === "idle" ? input.override.status : input.current?.status === "busy" ? "busy" : "idle";
|
|
1976
|
+
const source = normalizeRunStateSource(
|
|
1977
|
+
input.status === "completed" ? "checkpoint" : input.override?.source ?? input.current?.source,
|
|
1978
|
+
normalizedStatus
|
|
1979
|
+
);
|
|
1980
|
+
return {
|
|
1981
|
+
status: normalizedStatus,
|
|
1982
|
+
source,
|
|
1983
|
+
pendingToolCallCount: 0,
|
|
1984
|
+
updatedAt: input.timestamp
|
|
1985
|
+
};
|
|
1986
|
+
}
|
|
1987
|
+
function normalizeRunStateSource(source, status) {
|
|
1988
|
+
if (status === "idle") {
|
|
1989
|
+
return source === "turn" ? "checkpoint" : source ?? "checkpoint";
|
|
1990
|
+
}
|
|
1991
|
+
if (source === "turn" || source === "tool_batch") {
|
|
1992
|
+
return source;
|
|
1993
|
+
}
|
|
1994
|
+
return "checkpoint";
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
// src/session/checkpoint/shared.ts
|
|
1998
|
+
import crypto2 from "crypto";
|
|
1999
|
+
import path4 from "path";
|
|
2000
|
+
var MAX_COMPLETED_STEPS = 8;
|
|
2001
|
+
var MAX_BATCH_TOOLS = 6;
|
|
2002
|
+
var MAX_BATCH_PATHS = 6;
|
|
2003
|
+
var MAX_SUMMARY_CHARS = 220;
|
|
2004
|
+
function fingerprintFocus(focus) {
|
|
2005
|
+
return crypto2.createHash("sha1").update(focus.trim().toLowerCase()).digest("hex");
|
|
2006
|
+
}
|
|
2007
|
+
function normalizeText4(value) {
|
|
2008
|
+
return String(value ?? "").replace(/\s+/g, " ").trim();
|
|
2009
|
+
}
|
|
2010
|
+
function truncate4(value, maxChars) {
|
|
2011
|
+
if (!value) {
|
|
2012
|
+
return void 0;
|
|
2013
|
+
}
|
|
2014
|
+
return value.length <= maxChars ? value : `${value.slice(0, maxChars)}...`;
|
|
2015
|
+
}
|
|
2016
|
+
function takeLastUnique3(values, limit) {
|
|
2017
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2018
|
+
const result = [];
|
|
2019
|
+
for (let index = values.length - 1; index >= 0; index -= 1) {
|
|
2020
|
+
const normalized = normalizeText4(values[index]);
|
|
2021
|
+
if (!normalized || seen.has(normalized)) {
|
|
2022
|
+
continue;
|
|
2023
|
+
}
|
|
2024
|
+
seen.add(normalized);
|
|
2025
|
+
result.unshift(normalized);
|
|
2026
|
+
if (result.length >= limit) {
|
|
2027
|
+
break;
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
return result;
|
|
2031
|
+
}
|
|
2032
|
+
function safeParseObject2(raw) {
|
|
2033
|
+
if (typeof raw !== "string" || raw.trim().length === 0) {
|
|
2034
|
+
return null;
|
|
2035
|
+
}
|
|
2036
|
+
try {
|
|
2037
|
+
const parsed = JSON.parse(raw);
|
|
2038
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
2039
|
+
return null;
|
|
2040
|
+
}
|
|
2041
|
+
return parsed;
|
|
2042
|
+
} catch {
|
|
2043
|
+
return null;
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
function readString(value) {
|
|
2047
|
+
const normalized = normalizeText4(value);
|
|
2048
|
+
return normalized || void 0;
|
|
2049
|
+
}
|
|
2050
|
+
function normalizeToolBatch(toolBatch) {
|
|
2051
|
+
if (!toolBatch) {
|
|
2052
|
+
return void 0;
|
|
2053
|
+
}
|
|
2054
|
+
const tools = takeLastUnique3(toolBatch.tools ?? [], MAX_BATCH_TOOLS);
|
|
2055
|
+
if (tools.length === 0) {
|
|
2056
|
+
return void 0;
|
|
2057
|
+
}
|
|
2058
|
+
return {
|
|
2059
|
+
tools,
|
|
2060
|
+
summary: truncate4(normalizeText4(toolBatch.summary) || `Ran ${tools.join(", ")}`, MAX_SUMMARY_CHARS),
|
|
2061
|
+
changedPaths: takeLastUnique3(toolBatch.changedPaths ?? [], MAX_BATCH_PATHS),
|
|
2062
|
+
recordedAt: normalizeTimestamp(toolBatch.recordedAt, (/* @__PURE__ */ new Date()).toISOString())
|
|
2063
|
+
};
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
// src/session/checkpoint/derivation.ts
|
|
2067
|
+
function deriveCompletedSteps(session) {
|
|
2068
|
+
const completedActions = session.taskState?.completedActions ?? [];
|
|
2069
|
+
return takeLastUnique3(completedActions, MAX_COMPLETED_STEPS);
|
|
2070
|
+
}
|
|
2071
|
+
function deriveRecentToolBatchFromMessages(messages, timestamp) {
|
|
2072
|
+
let lastToolIndex = messages.length - 1;
|
|
2073
|
+
while (lastToolIndex >= 0 && messages[lastToolIndex]?.role !== "tool") {
|
|
2074
|
+
lastToolIndex -= 1;
|
|
2075
|
+
}
|
|
2076
|
+
if (lastToolIndex < 0) {
|
|
2077
|
+
return void 0;
|
|
2078
|
+
}
|
|
2079
|
+
let startIndex = lastToolIndex;
|
|
2080
|
+
while (startIndex >= 0 && messages[startIndex]?.role === "tool") {
|
|
2081
|
+
startIndex -= 1;
|
|
2082
|
+
}
|
|
2083
|
+
const toolMessages = messages.slice(startIndex + 1, lastToolIndex + 1).filter((message) => message.role === "tool");
|
|
2084
|
+
const toolNames = toolMessages.map((message) => normalizeText4(message.name)).filter(Boolean);
|
|
2085
|
+
return buildToolBatch(toolNames, toolMessages, void 0, timestamp);
|
|
2086
|
+
}
|
|
2087
|
+
function buildToolBatch(toolNames, toolMessages, changedPaths, timestamp) {
|
|
2088
|
+
const tools = takeLastUnique3(toolNames, MAX_BATCH_TOOLS);
|
|
2089
|
+
if (tools.length === 0) {
|
|
2090
|
+
return void 0;
|
|
2091
|
+
}
|
|
2092
|
+
const batchChangedPaths = takeLastUnique3(
|
|
2093
|
+
[
|
|
2094
|
+
...changedPaths ?? [],
|
|
2095
|
+
...toolMessages.map((message) => readPathFromMessage(message)).filter(Boolean)
|
|
2096
|
+
],
|
|
2097
|
+
MAX_BATCH_PATHS
|
|
2098
|
+
);
|
|
2099
|
+
const recordedAt = normalizeTimestamp(
|
|
2100
|
+
toolMessages[toolMessages.length - 1]?.createdAt,
|
|
2101
|
+
timestamp
|
|
2102
|
+
);
|
|
2103
|
+
return {
|
|
2104
|
+
tools,
|
|
2105
|
+
summary: buildToolBatchSummary(tools, batchChangedPaths),
|
|
2106
|
+
changedPaths: batchChangedPaths,
|
|
2107
|
+
recordedAt
|
|
2108
|
+
};
|
|
2109
|
+
}
|
|
2110
|
+
function readPathFromMessage(message) {
|
|
2111
|
+
const payload = safeParseObject2(message.content);
|
|
2112
|
+
return readString(payload?.path) ?? readString(payload?.requestedPath);
|
|
2113
|
+
}
|
|
2114
|
+
function buildToolBatchSummary(toolNames, changedPaths) {
|
|
2115
|
+
const fragments = [`Ran ${toolNames.join(", ")}`];
|
|
2116
|
+
if (changedPaths.length > 0) {
|
|
2117
|
+
fragments.push(`changed ${changedPaths.join(" | ")}`);
|
|
2118
|
+
}
|
|
2119
|
+
return truncate4(fragments.join("; "), MAX_SUMMARY_CHARS);
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
// src/session/checkpoint/base.ts
|
|
2123
|
+
function createEmptyCheckpoint(timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
2124
|
+
return {
|
|
2125
|
+
version: 1,
|
|
2126
|
+
status: "active",
|
|
2127
|
+
completedSteps: [],
|
|
2128
|
+
flow: {
|
|
2129
|
+
phase: "active",
|
|
2130
|
+
runState: {
|
|
2131
|
+
status: "idle",
|
|
2132
|
+
source: "checkpoint",
|
|
2133
|
+
pendingToolCallCount: 0,
|
|
2134
|
+
updatedAt: timestamp
|
|
2135
|
+
},
|
|
2136
|
+
updatedAt: timestamp
|
|
2137
|
+
},
|
|
2138
|
+
updatedAt: timestamp
|
|
2139
|
+
};
|
|
2140
|
+
}
|
|
2141
|
+
function createCheckpointForFocus(focus, timestamp) {
|
|
2142
|
+
return {
|
|
2143
|
+
...createEmptyCheckpoint(timestamp),
|
|
2144
|
+
focus,
|
|
2145
|
+
focusFingerprint: focus ? fingerprintFocus(focus) : void 0
|
|
2146
|
+
};
|
|
2147
|
+
}
|
|
2148
|
+
function deriveCheckpointFromSession(session, timestamp) {
|
|
2149
|
+
const recentToolBatch = deriveRecentToolBatchFromMessages(session.messages, timestamp);
|
|
2150
|
+
return {
|
|
2151
|
+
...createCheckpointForFocus(normalizeText4(session.taskState?.focus) || void 0, timestamp),
|
|
2152
|
+
completedSteps: deriveCompletedSteps(session),
|
|
2153
|
+
recentToolBatch
|
|
2154
|
+
};
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
// src/session/checkpoint/state.ts
|
|
2158
|
+
function normalizeCheckpoint(checkpoint, timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
2159
|
+
if (!checkpoint) {
|
|
2160
|
+
return void 0;
|
|
2161
|
+
}
|
|
2162
|
+
const focus = normalizeText4(checkpoint.focus) || void 0;
|
|
2163
|
+
const status = checkpoint.status === "completed" ? "completed" : "active";
|
|
2164
|
+
return {
|
|
2165
|
+
version: 1,
|
|
2166
|
+
focus,
|
|
2167
|
+
focusFingerprint: normalizeText4(checkpoint.focusFingerprint) || (focus ? fingerprintFocus(focus) : void 0),
|
|
2168
|
+
status,
|
|
2169
|
+
completedSteps: takeLastUnique3(checkpoint.completedSteps ?? [], 8),
|
|
2170
|
+
recentToolBatch: normalizeToolBatch(checkpoint.recentToolBatch),
|
|
2171
|
+
flow: normalizeCheckpointFlow(checkpoint.flow, status, timestamp),
|
|
2172
|
+
updatedAt: normalizeTimestamp(checkpoint.updatedAt, timestamp)
|
|
2173
|
+
};
|
|
2174
|
+
}
|
|
2175
|
+
function normalizeSessionCheckpoint(session) {
|
|
2176
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
2177
|
+
const normalized = normalizeCheckpoint(session.checkpoint, timestamp);
|
|
2178
|
+
const checkpoint = normalized ?? deriveCheckpointFromSession(session, timestamp);
|
|
2179
|
+
if (checkpoint.completedSteps.length === 0) {
|
|
2180
|
+
checkpoint.completedSteps = deriveCompletedSteps(session);
|
|
2181
|
+
}
|
|
2182
|
+
checkpoint.flow = normalizeCheckpointFlow(checkpoint.flow, checkpoint.status, timestamp);
|
|
2183
|
+
checkpoint.updatedAt = normalizeTimestamp(checkpoint.updatedAt, timestamp);
|
|
2184
|
+
return {
|
|
2185
|
+
...session,
|
|
2186
|
+
checkpoint
|
|
2187
|
+
};
|
|
2188
|
+
}
|
|
2189
|
+
function noteCheckpointTurnInput(session, input, timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
2190
|
+
const checkpoint = normalizeSessionCheckpoint(session).checkpoint ?? createEmptyCheckpoint(timestamp);
|
|
2191
|
+
const transition = getTurnInputTransition(input, timestamp);
|
|
2192
|
+
return {
|
|
2193
|
+
...session,
|
|
2194
|
+
checkpoint: {
|
|
2195
|
+
...checkpoint,
|
|
2196
|
+
flow: buildCheckpointFlow({
|
|
2197
|
+
current: checkpoint.flow,
|
|
2198
|
+
status: checkpoint.status,
|
|
2199
|
+
transition,
|
|
2200
|
+
runState: checkpoint.status === "completed" ? {
|
|
2201
|
+
status: "idle",
|
|
2202
|
+
source: "checkpoint"
|
|
2203
|
+
} : {
|
|
2204
|
+
status: "busy",
|
|
2205
|
+
source: "turn"
|
|
2206
|
+
},
|
|
2207
|
+
defaultPhase: "active",
|
|
2208
|
+
timestamp
|
|
2209
|
+
}),
|
|
2210
|
+
updatedAt: timestamp
|
|
2211
|
+
}
|
|
2212
|
+
};
|
|
2213
|
+
}
|
|
2214
|
+
function resolveCurrentFocusCheckpoint(session, timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
2215
|
+
const focus = normalizeText4(session.taskState?.focus) || void 0;
|
|
2216
|
+
const fingerprint = focus ? fingerprintFocus(focus) : void 0;
|
|
2217
|
+
const checkpoint = normalizeCheckpoint(session.checkpoint, timestamp) ?? createEmptyCheckpoint(timestamp);
|
|
2218
|
+
if (!focus) {
|
|
2219
|
+
return checkpoint;
|
|
2220
|
+
}
|
|
2221
|
+
if (checkpoint.focusFingerprint === fingerprint) {
|
|
2222
|
+
return checkpoint;
|
|
2223
|
+
}
|
|
2224
|
+
return createCheckpointForFocus(focus, timestamp);
|
|
2225
|
+
}
|
|
2226
|
+
function noteCheckpointToolBatch(session, input, timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
2227
|
+
const checkpoint = resolveCurrentFocusCheckpoint(session, timestamp);
|
|
2228
|
+
const recentToolBatch = buildToolBatch(input.toolNames, input.toolMessages, input.changedPaths, timestamp);
|
|
2229
|
+
const phase = checkpoint.flow.phase === "recovery" ? "active" : checkpoint.flow.phase;
|
|
2230
|
+
const transition = createToolBatchTransition({
|
|
2231
|
+
toolNames: input.toolNames,
|
|
2232
|
+
changedPaths: input.changedPaths
|
|
2233
|
+
}, timestamp);
|
|
2234
|
+
return {
|
|
2235
|
+
...session,
|
|
2236
|
+
checkpoint: {
|
|
2237
|
+
...checkpoint,
|
|
2238
|
+
completedSteps: deriveCompletedSteps(session),
|
|
2239
|
+
recentToolBatch,
|
|
2240
|
+
flow: buildCheckpointFlow({
|
|
2241
|
+
current: checkpoint.flow,
|
|
2242
|
+
status: checkpoint.status,
|
|
2243
|
+
transition,
|
|
2244
|
+
defaultPhase: phase,
|
|
2245
|
+
timestamp
|
|
2246
|
+
}),
|
|
2247
|
+
updatedAt: timestamp
|
|
2248
|
+
}
|
|
2249
|
+
};
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
// src/session/checkpoint/transitions.ts
|
|
2253
|
+
function noteCheckpointTransition(session, transition, timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
2254
|
+
const checkpoint = resolveCurrentFocusCheckpoint(session, timestamp);
|
|
2255
|
+
return {
|
|
2256
|
+
...session,
|
|
2257
|
+
checkpoint: {
|
|
2258
|
+
...checkpoint,
|
|
2259
|
+
flow: buildCheckpointFlow({
|
|
2260
|
+
current: checkpoint.flow,
|
|
2261
|
+
status: checkpoint.status,
|
|
2262
|
+
transition,
|
|
2263
|
+
defaultPhase: checkpoint.flow.phase,
|
|
2264
|
+
timestamp
|
|
2265
|
+
}),
|
|
2266
|
+
updatedAt: timestamp
|
|
2267
|
+
}
|
|
2268
|
+
};
|
|
2269
|
+
}
|
|
2270
|
+
function noteCheckpointRecovery(session, transition, timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
2271
|
+
const checkpoint = resolveCurrentFocusCheckpoint(session, timestamp);
|
|
2272
|
+
if (checkpoint.status === "completed") {
|
|
2273
|
+
return {
|
|
2274
|
+
...session,
|
|
2275
|
+
checkpoint
|
|
2276
|
+
};
|
|
2277
|
+
}
|
|
2278
|
+
return {
|
|
2279
|
+
...session,
|
|
2280
|
+
checkpoint: {
|
|
2281
|
+
...checkpoint,
|
|
2282
|
+
flow: buildCheckpointFlow({
|
|
2283
|
+
current: checkpoint.flow,
|
|
2284
|
+
status: checkpoint.status,
|
|
2285
|
+
transition,
|
|
2286
|
+
defaultPhase: "recovery",
|
|
2287
|
+
timestamp
|
|
2288
|
+
}),
|
|
2289
|
+
updatedAt: timestamp
|
|
2290
|
+
}
|
|
2291
|
+
};
|
|
2292
|
+
}
|
|
2293
|
+
function noteCheckpointCompleted(session, transition, timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
2294
|
+
const checkpoint = resolveCurrentFocusCheckpoint(session, timestamp);
|
|
2295
|
+
return {
|
|
2296
|
+
...session,
|
|
2297
|
+
checkpoint: {
|
|
2298
|
+
...checkpoint,
|
|
2299
|
+
status: "completed",
|
|
2300
|
+
completedSteps: checkpoint.completedSteps.length > 0 ? checkpoint.completedSteps : deriveCompletedSteps(session),
|
|
2301
|
+
flow: buildCheckpointFlow({
|
|
2302
|
+
current: checkpoint.flow,
|
|
2303
|
+
status: "completed",
|
|
2304
|
+
transition,
|
|
2305
|
+
defaultPhase: "active",
|
|
2306
|
+
timestamp
|
|
2307
|
+
}),
|
|
2308
|
+
updatedAt: timestamp
|
|
2309
|
+
}
|
|
2310
|
+
};
|
|
2311
|
+
}
|
|
2312
|
+
|
|
2313
|
+
// src/session/todos.ts
|
|
2314
|
+
var MAX_TODO_ITEMS = 20;
|
|
2315
|
+
var MAX_TODO_TEXT_CHARS = 240;
|
|
2316
|
+
function deriveTodoItems(messages, previous = []) {
|
|
2317
|
+
const turnBoundaryIndex = findLatestUserInputBoundaryIndex(messages);
|
|
2318
|
+
const stopIndex = turnBoundaryIndex >= 0 ? turnBoundaryIndex : -1;
|
|
2319
|
+
for (let index = messages.length - 1; index > stopIndex; index -= 1) {
|
|
2320
|
+
const message = messages[index];
|
|
2321
|
+
if (message?.role !== "tool" || message.name !== "todo_write" || typeof message.content !== "string") {
|
|
2322
|
+
continue;
|
|
2323
|
+
}
|
|
2324
|
+
const parsed = safeParseJson(message.content);
|
|
2325
|
+
if (!parsed || typeof parsed !== "object" || !("items" in parsed)) {
|
|
2326
|
+
continue;
|
|
2327
|
+
}
|
|
2328
|
+
try {
|
|
2329
|
+
return normalizeTodoItems(parsed.items);
|
|
2330
|
+
} catch {
|
|
2331
|
+
continue;
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
return normalizeTodoItems(previous);
|
|
2335
|
+
}
|
|
2336
|
+
function normalizeTodoItems(value) {
|
|
2337
|
+
if (!Array.isArray(value)) {
|
|
2338
|
+
return [];
|
|
2339
|
+
}
|
|
2340
|
+
if (value.length > MAX_TODO_ITEMS) {
|
|
2341
|
+
throw new Error(`Too many todo items: max ${MAX_TODO_ITEMS}.`);
|
|
2342
|
+
}
|
|
2343
|
+
const normalized = [];
|
|
2344
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
2345
|
+
let inProgressCount = 0;
|
|
2346
|
+
for (const entry of value) {
|
|
2347
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
2348
|
+
throw new Error("Each todo item must be an object.");
|
|
2349
|
+
}
|
|
2350
|
+
const record = entry;
|
|
2351
|
+
const id = String(record.id ?? "").trim();
|
|
2352
|
+
const text = compactTodoText(record.text);
|
|
2353
|
+
const status = normalizeTodoStatus(record.status);
|
|
2354
|
+
if (!id) {
|
|
2355
|
+
throw new Error("Todo item id is required.");
|
|
2356
|
+
}
|
|
2357
|
+
if (!text) {
|
|
2358
|
+
throw new Error(`Todo item ${id} text is required.`);
|
|
2359
|
+
}
|
|
2360
|
+
if (seenIds.has(id)) {
|
|
2361
|
+
throw new Error(`Duplicate todo item id: ${id}.`);
|
|
2362
|
+
}
|
|
2363
|
+
seenIds.add(id);
|
|
2364
|
+
if (status === "in_progress") {
|
|
2365
|
+
inProgressCount += 1;
|
|
2366
|
+
if (inProgressCount > 1) {
|
|
2367
|
+
throw new Error("Only one todo item can be in_progress.");
|
|
2368
|
+
}
|
|
2369
|
+
}
|
|
2370
|
+
normalized.push({
|
|
2371
|
+
id,
|
|
2372
|
+
text,
|
|
2373
|
+
status
|
|
2374
|
+
});
|
|
2375
|
+
}
|
|
2376
|
+
return normalized;
|
|
2377
|
+
}
|
|
2378
|
+
function formatTodoBlock(items) {
|
|
2379
|
+
const todos = normalizeTodoItems(items);
|
|
2380
|
+
if (todos.length === 0) {
|
|
2381
|
+
return "- none";
|
|
2382
|
+
}
|
|
2383
|
+
const lines = todos.map((item) => `${statusMarker(item.status)} #${item.id}: ${item.text}`);
|
|
2384
|
+
const completed = todos.filter((item) => item.status === "completed").length;
|
|
2385
|
+
lines.push(`- Progress: ${completed}/${todos.length} completed`);
|
|
2386
|
+
return lines.join("\n");
|
|
2387
|
+
}
|
|
2388
|
+
function normalizeSessionTodos(session) {
|
|
2389
|
+
return {
|
|
2390
|
+
...session,
|
|
2391
|
+
todoItems: deriveTodoItems(session.messages ?? [], session.todoItems ?? [])
|
|
2392
|
+
};
|
|
2393
|
+
}
|
|
2394
|
+
function findLatestUserInputBoundaryIndex(messages) {
|
|
2395
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
2396
|
+
const message = messages[index];
|
|
2397
|
+
if (message?.role === "user" && readUserInput(message)) {
|
|
2398
|
+
return index;
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
return -1;
|
|
2402
|
+
}
|
|
2403
|
+
function normalizeTodoStatus(value) {
|
|
2404
|
+
const normalized = String(value ?? "").trim().toLowerCase();
|
|
2405
|
+
if (normalized === "pending" || normalized === "in_progress" || normalized === "completed") {
|
|
2406
|
+
return normalized;
|
|
2407
|
+
}
|
|
2408
|
+
throw new Error(`Invalid todo status: ${String(value ?? "")}.`);
|
|
2409
|
+
}
|
|
2410
|
+
function compactTodoText(value) {
|
|
2411
|
+
const normalized = String(value ?? "").replace(/\s+/g, " ").trim();
|
|
2412
|
+
if (normalized.length <= MAX_TODO_TEXT_CHARS) {
|
|
2413
|
+
return normalized;
|
|
2414
|
+
}
|
|
2415
|
+
return `${normalized.slice(0, MAX_TODO_TEXT_CHARS)}...`;
|
|
2416
|
+
}
|
|
2417
|
+
function statusMarker(status) {
|
|
2418
|
+
switch (status) {
|
|
2419
|
+
case "completed":
|
|
2420
|
+
return "[x]";
|
|
2421
|
+
case "in_progress":
|
|
2422
|
+
return "[>]";
|
|
2423
|
+
default:
|
|
2424
|
+
return "[ ]";
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
function safeParseJson(raw) {
|
|
2428
|
+
try {
|
|
2429
|
+
return JSON.parse(raw);
|
|
2430
|
+
} catch {
|
|
2431
|
+
return null;
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
// src/session/memory.ts
|
|
2436
|
+
var MAX_SESSION_MEMORY_CHARS = 12e3;
|
|
2437
|
+
var SESSION_MEMORY_SECTIONS = [
|
|
2438
|
+
{
|
|
2439
|
+
title: "Current Focus",
|
|
2440
|
+
description: "Model-written current work focus that still affects the next turn."
|
|
2441
|
+
},
|
|
2442
|
+
{
|
|
2443
|
+
title: "User Constraints",
|
|
2444
|
+
description: "Stable user constraints or preferences that affect future action."
|
|
2445
|
+
},
|
|
2446
|
+
{
|
|
2447
|
+
title: "Decisions",
|
|
2448
|
+
description: "Decisions already made that should guide later work."
|
|
2449
|
+
},
|
|
2450
|
+
{
|
|
2451
|
+
title: "Open Threads",
|
|
2452
|
+
description: "Unresolved work, next steps, blockers, or questions."
|
|
2453
|
+
},
|
|
2454
|
+
{
|
|
2455
|
+
title: "Verification Facts",
|
|
2456
|
+
description: "Concrete tool, test, file-change, or runtime facts."
|
|
2457
|
+
},
|
|
2458
|
+
{
|
|
2459
|
+
title: "Reusable Lessons",
|
|
2460
|
+
description: "Stable lessons worth moving into spec notes or skill references."
|
|
2461
|
+
}
|
|
2462
|
+
];
|
|
2463
|
+
function formatSessionMemorySectionTemplate() {
|
|
2464
|
+
return SESSION_MEMORY_SECTIONS.map((section) => `## ${section.title}
|
|
2465
|
+
${section.description}
|
|
2466
|
+
Write None when no supplied fact belongs here.`).join("\n\n");
|
|
2467
|
+
}
|
|
2468
|
+
function formatSessionMemorySectionList() {
|
|
2469
|
+
return SESSION_MEMORY_SECTIONS.map((section) => `- ${section.title}: ${section.description}`).join("\n");
|
|
2470
|
+
}
|
|
2471
|
+
function createSessionMemoryState(summary, timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
2472
|
+
return {
|
|
2473
|
+
version: 1,
|
|
2474
|
+
summary: normalizeSummary(summary),
|
|
2475
|
+
updatedAt: timestamp
|
|
2476
|
+
};
|
|
2477
|
+
}
|
|
2478
|
+
function updateSessionMemory(session, summary, timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
2479
|
+
const memory = createSessionMemoryState(summary, timestamp);
|
|
2480
|
+
const focus = readSessionMemoryCurrentFocus(memory.summary);
|
|
2481
|
+
return {
|
|
2482
|
+
...session,
|
|
2483
|
+
sessionMemory: memory,
|
|
2484
|
+
taskState: {
|
|
2485
|
+
...session.taskState ?? {
|
|
2486
|
+
activeFiles: [],
|
|
2487
|
+
plannedActions: [],
|
|
2488
|
+
completedActions: [],
|
|
2489
|
+
blockers: [],
|
|
2490
|
+
lastUpdatedAt: timestamp
|
|
2491
|
+
},
|
|
2492
|
+
focus,
|
|
2493
|
+
lastUpdatedAt: timestamp
|
|
2494
|
+
}
|
|
2495
|
+
};
|
|
2496
|
+
}
|
|
2497
|
+
function normalizeSessionMemory(value, timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
2498
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2499
|
+
return void 0;
|
|
2500
|
+
}
|
|
2501
|
+
const record = value;
|
|
2502
|
+
if (record.version !== 1 || typeof record.summary !== "string") {
|
|
2503
|
+
return void 0;
|
|
2504
|
+
}
|
|
2505
|
+
return {
|
|
2506
|
+
version: 1,
|
|
2507
|
+
summary: normalizeSummary(record.summary),
|
|
2508
|
+
updatedAt: typeof record.updatedAt === "string" && record.updatedAt.trim() ? record.updatedAt : timestamp
|
|
2509
|
+
};
|
|
2510
|
+
}
|
|
2511
|
+
function normalizeSummary(value) {
|
|
2512
|
+
return value.replace(/\r\n/g, "\n").trim().slice(0, MAX_SESSION_MEMORY_CHARS);
|
|
2513
|
+
}
|
|
2514
|
+
function readSessionMemoryCurrentFocus(summary) {
|
|
2515
|
+
const lines = summary.split("\n");
|
|
2516
|
+
let inFocusSection = false;
|
|
2517
|
+
const values = [];
|
|
2518
|
+
for (const line of lines) {
|
|
2519
|
+
if (line.startsWith("## ")) {
|
|
2520
|
+
if (inFocusSection) {
|
|
2521
|
+
break;
|
|
2522
|
+
}
|
|
2523
|
+
inFocusSection = line.slice(3).trim() === "Current Focus";
|
|
2524
|
+
continue;
|
|
2525
|
+
}
|
|
2526
|
+
if (inFocusSection) {
|
|
2527
|
+
values.push(line);
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
const focus = values.join("\n").trim();
|
|
2531
|
+
return focus && focus.toLowerCase() !== "none" ? focus : void 0;
|
|
2532
|
+
}
|
|
2533
|
+
|
|
2534
|
+
// src/session/workset.ts
|
|
2535
|
+
import path5 from "path";
|
|
2536
|
+
var MAX_WORKSET_FILES = 20;
|
|
2537
|
+
function createEmptySessionWorkset(timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
2538
|
+
return {
|
|
2539
|
+
version: 1,
|
|
2540
|
+
files: [],
|
|
2541
|
+
updatedAt: timestamp
|
|
2542
|
+
};
|
|
2543
|
+
}
|
|
2544
|
+
function normalizeSessionWorkset(value) {
|
|
2545
|
+
if (value === void 0) {
|
|
2546
|
+
return void 0;
|
|
2547
|
+
}
|
|
2548
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2549
|
+
return createEmptySessionWorkset();
|
|
2550
|
+
}
|
|
2551
|
+
const record = value;
|
|
2552
|
+
if (record.version !== 1 || !Array.isArray(record.files)) {
|
|
2553
|
+
return createEmptySessionWorkset();
|
|
2554
|
+
}
|
|
2555
|
+
const files = record.files.map(normalizeWorksetEntry).filter((entry) => Boolean(entry)).slice(-MAX_WORKSET_FILES);
|
|
2556
|
+
return {
|
|
2557
|
+
version: 1,
|
|
2558
|
+
files,
|
|
2559
|
+
updatedAt: typeof record.updatedAt === "string" ? record.updatedAt : latestWorksetTimestamp(files)
|
|
2560
|
+
};
|
|
2561
|
+
}
|
|
2562
|
+
function recordSessionWorksetFile(session, input) {
|
|
2563
|
+
const timestamp = input.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
2564
|
+
const workset = normalizeSessionWorkset(session.workset) ?? createEmptySessionWorkset(timestamp);
|
|
2565
|
+
const displayPath = normalizeDisplayPath(input.cwd, input.path);
|
|
2566
|
+
const existing = workset.files.find((entry) => entry.path === displayPath);
|
|
2567
|
+
const nextEntry = existing ? {
|
|
2568
|
+
...existing,
|
|
2569
|
+
lastSeenAt: timestamp,
|
|
2570
|
+
readCount: existing.readCount + (input.changed ? 0 : 1),
|
|
2571
|
+
changedCount: existing.changedCount + (input.changed ? 1 : 0),
|
|
2572
|
+
lastTool: input.toolName,
|
|
2573
|
+
lastChangeId: input.changeId ?? existing.lastChangeId,
|
|
2574
|
+
reason: input.reason ?? existing.reason
|
|
2575
|
+
} : {
|
|
2576
|
+
path: displayPath,
|
|
2577
|
+
firstSeenAt: timestamp,
|
|
2578
|
+
lastSeenAt: timestamp,
|
|
2579
|
+
readCount: input.changed ? 0 : 1,
|
|
2580
|
+
changedCount: input.changed ? 1 : 0,
|
|
2581
|
+
lastTool: input.toolName,
|
|
2582
|
+
lastChangeId: input.changeId,
|
|
2583
|
+
reason: input.reason
|
|
2584
|
+
};
|
|
2585
|
+
const files = [
|
|
2586
|
+
...workset.files.filter((entry) => entry.path !== displayPath),
|
|
2587
|
+
nextEntry
|
|
2588
|
+
].slice(-MAX_WORKSET_FILES);
|
|
2589
|
+
return {
|
|
2590
|
+
...session,
|
|
2591
|
+
workset: {
|
|
2592
|
+
version: 1,
|
|
2593
|
+
files,
|
|
2594
|
+
updatedAt: timestamp
|
|
2595
|
+
}
|
|
2596
|
+
};
|
|
2597
|
+
}
|
|
2598
|
+
function normalizeWorksetEntry(value) {
|
|
2599
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2600
|
+
return void 0;
|
|
2601
|
+
}
|
|
2602
|
+
const record = value;
|
|
2603
|
+
const filePath = typeof record.path === "string" ? record.path.trim() : "";
|
|
2604
|
+
if (!filePath) {
|
|
2605
|
+
return void 0;
|
|
2606
|
+
}
|
|
2607
|
+
return {
|
|
2608
|
+
path: filePath,
|
|
2609
|
+
firstSeenAt: readString2(record.firstSeenAt),
|
|
2610
|
+
lastSeenAt: readString2(record.lastSeenAt),
|
|
2611
|
+
readCount: readCount(record.readCount),
|
|
2612
|
+
changedCount: readCount(record.changedCount),
|
|
2613
|
+
lastTool: typeof record.lastTool === "string" && record.lastTool.trim() ? record.lastTool.trim() : "unknown",
|
|
2614
|
+
lastChangeId: typeof record.lastChangeId === "string" && record.lastChangeId.trim() ? record.lastChangeId.trim() : void 0,
|
|
2615
|
+
reason: typeof record.reason === "string" && record.reason.trim() ? record.reason.trim() : void 0
|
|
2616
|
+
};
|
|
2617
|
+
}
|
|
2618
|
+
function normalizeDisplayPath(cwd, targetPath) {
|
|
2619
|
+
const absolutePath = path5.resolve(cwd, targetPath);
|
|
2620
|
+
const relative = path5.relative(cwd, absolutePath);
|
|
2621
|
+
return relative && !relative.startsWith("..") && !path5.isAbsolute(relative) ? relative : absolutePath;
|
|
2622
|
+
}
|
|
2623
|
+
function readString2(value) {
|
|
2624
|
+
return typeof value === "string" && value.trim() ? value : (/* @__PURE__ */ new Date()).toISOString();
|
|
2625
|
+
}
|
|
2626
|
+
function readCount(value) {
|
|
2627
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.trunc(value) : 0;
|
|
2628
|
+
}
|
|
2629
|
+
function latestWorksetTimestamp(files) {
|
|
2630
|
+
return files.map((entry) => entry.lastSeenAt).sort().at(-1) ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
2631
|
+
}
|
|
2632
|
+
|
|
2633
|
+
export {
|
|
2634
|
+
PROJECT_STATE_DIR_NAME,
|
|
2635
|
+
PROJECT_STATE_ENV_FILE_NAME,
|
|
2636
|
+
PROJECT_STATE_ENV_EXAMPLE_FILE_NAME,
|
|
2637
|
+
PROJECT_STATE_IGNORE_FILE_NAME,
|
|
2638
|
+
PRESERVED_PROJECT_STATE_ENTRY_NAMES,
|
|
2639
|
+
getProjectStatePaths,
|
|
2640
|
+
ensureProjectStateDirectories,
|
|
2641
|
+
createEmptyAssistantResponseTransition,
|
|
2642
|
+
createProviderRecoveryTransition,
|
|
2643
|
+
createFinalizeTransition,
|
|
2644
|
+
createExecutionWaitYieldTransition,
|
|
2645
|
+
buildRunTurnResult,
|
|
2646
|
+
fingerprintFocus,
|
|
2647
|
+
normalizeText4 as normalizeText,
|
|
2648
|
+
takeLastUnique3 as takeLastUnique,
|
|
2649
|
+
createEmptyCheckpoint,
|
|
2650
|
+
normalizeCheckpoint,
|
|
2651
|
+
normalizeSessionCheckpoint,
|
|
2652
|
+
noteCheckpointTurnInput,
|
|
2653
|
+
noteCheckpointToolBatch,
|
|
2654
|
+
noteCheckpointTransition,
|
|
2655
|
+
noteCheckpointRecovery,
|
|
2656
|
+
noteCheckpointCompleted,
|
|
2657
|
+
isAbortError,
|
|
2658
|
+
throwIfAborted,
|
|
2659
|
+
sleepWithSignal,
|
|
2660
|
+
isRetryableApiError,
|
|
2661
|
+
recordObservabilityEvent,
|
|
2662
|
+
createProviderClientPool,
|
|
2663
|
+
fetchAssistantResponse,
|
|
2664
|
+
buildChatMessages,
|
|
2665
|
+
createMessage,
|
|
2666
|
+
createToolMessage,
|
|
2667
|
+
readReasoningContent,
|
|
2668
|
+
collapseContentParts,
|
|
2669
|
+
toChatMessage,
|
|
2670
|
+
findLatestUserIndex,
|
|
2671
|
+
expandStartToToolBoundary,
|
|
2672
|
+
modelUsesReasoningContent,
|
|
2673
|
+
isAssistantMessageInLatestTurn,
|
|
2674
|
+
shouldIncludeStoredAssistantReasoning,
|
|
2675
|
+
isInternalMessage,
|
|
2676
|
+
createInternalReminder,
|
|
2677
|
+
readUserInput,
|
|
2678
|
+
findLatestUserInputIndex,
|
|
2679
|
+
sliceCurrentUserInputFrame,
|
|
2680
|
+
oneLine,
|
|
2681
|
+
createEmptyTaskState,
|
|
2682
|
+
deriveTaskState,
|
|
2683
|
+
normalizeTaskState,
|
|
2684
|
+
formatTaskStateBlock,
|
|
2685
|
+
normalizeSessionRecord,
|
|
2686
|
+
applyCurrentTurnFrame,
|
|
2687
|
+
deriveTodoItems,
|
|
2688
|
+
normalizeTodoItems,
|
|
2689
|
+
formatTodoBlock,
|
|
2690
|
+
normalizeSessionTodos,
|
|
2691
|
+
formatSessionMemorySectionTemplate,
|
|
2692
|
+
formatSessionMemorySectionList,
|
|
2693
|
+
updateSessionMemory,
|
|
2694
|
+
normalizeSessionMemory,
|
|
2695
|
+
createEmptySessionDiff,
|
|
2696
|
+
normalizeSessionDiff,
|
|
2697
|
+
normalizeSessionDiffState,
|
|
2698
|
+
noteSessionDiff,
|
|
2699
|
+
normalizeSessionWorkset,
|
|
2700
|
+
recordSessionWorksetFile
|
|
2701
|
+
};
|