@jun133/kitty 0.0.14 → 0.0.16

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.
@@ -1,1212 +1,237 @@
1
- // src/provider/capabilities.ts
2
- var DEFAULT_PROVIDER = "openai-compatible";
1
+ // src/provider/catalog.ts
3
2
  var DEFAULT_REQUEST_TIMEOUT_MS = 10 * 60 * 1e3;
4
3
  var DEFAULT_DOCTOR_PROBE_TIMEOUT_MS = 1e4;
5
4
  var RELAY_REQUEST_TIMEOUT_MS = 15 * 60 * 1e3;
6
5
  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,
6
+ var PROVIDER_CATALOG = [
7
+ {
8
+ id: "deepseek",
9
+ label: "DeepSeek official",
10
+ apiKind: "deepseek-openai-compatible",
11
+ transport: "standard",
12
+ defaultBaseUrl: "https://api.deepseek.com",
13
+ requestTimeoutMs: DEFAULT_REQUEST_TIMEOUT_MS,
14
+ doctorProbeTimeoutMs: DEFAULT_DOCTOR_PROBE_TIMEOUT_MS
15
+ },
16
+ {
17
+ id: "yls",
18
+ label: "YLS Codex",
19
+ apiKind: "openai-sdk",
20
+ transport: "relay",
21
+ defaultBaseUrl: "https://code.ylsagi.com/codex",
22
+ requestTimeoutMs: RELAY_REQUEST_TIMEOUT_MS,
23
+ doctorProbeTimeoutMs: RELAY_DOCTOR_PROBE_TIMEOUT_MS
24
+ },
25
+ {
26
+ id: "ttapi",
27
+ label: "TTAPI",
28
+ apiKind: "openai-sdk",
29
+ transport: "relay",
30
+ defaultBaseUrl: "https://w.ciykj.cn",
31
+ requestTimeoutMs: RELAY_REQUEST_TIMEOUT_MS,
32
+ doctorProbeTimeoutMs: RELAY_DOCTOR_PROBE_TIMEOUT_MS
33
+ },
34
+ {
35
+ id: "openai",
36
+ label: "OpenAI official",
37
+ apiKind: "openai-sdk",
38
+ transport: "standard",
39
+ defaultBaseUrl: "https://api.openai.com/v1",
40
+ requestTimeoutMs: RELAY_REQUEST_TIMEOUT_MS,
41
+ doctorProbeTimeoutMs: RELAY_DOCTOR_PROBE_TIMEOUT_MS
42
+ },
43
+ {
44
+ id: "openai-compatible",
45
+ label: "OpenAI-compatible",
46
+ apiKind: "openai-compatible",
47
+ transport: "standard",
48
+ defaultBaseUrl: "",
40
49
  requestTimeoutMs: DEFAULT_REQUEST_TIMEOUT_MS,
41
50
  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
51
  }
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
52
  ];
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 = {
53
+ var DEEPSEEK_MODEL_BASE = {
491
54
  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
- }
55
+ capabilities: {
56
+ tools: true,
57
+ reasoning: true,
58
+ reasoningContentReplay: "tool-call-required",
59
+ streaming: true,
60
+ usage: true,
61
+ cache: "provider-automatic"
581
62
  },
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
- }
63
+ request: {
64
+ thinkingDefault: "enabled",
65
+ reasoningEffortDefault: "max",
66
+ maxOutputTokensParam: "max_tokens"
67
+ },
68
+ limit: {
69
+ context: 128e3,
70
+ output: 8e3
630
71
  }
631
72
  };
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 = {
73
+ var GPT_RESPONSES_MODEL_BASE = {
672
74
  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
- }
75
+ capabilities: {
76
+ tools: true,
77
+ reasoning: true,
78
+ reasoningContentReplay: "never",
79
+ streaming: true,
80
+ usage: true,
81
+ cache: "prompt-cache-key"
766
82
  },
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
- }
83
+ request: {
84
+ thinkingDefault: "enabled",
85
+ reasoningEffortDefault: "high",
86
+ maxOutputTokensParam: "max_output_tokens"
87
+ },
88
+ limit: {
89
+ context: 4e5,
90
+ output: 128e3
795
91
  }
796
92
  };
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;
93
+ var MODEL_CATALOG = [
94
+ {
95
+ id: "deepseek-v4-flash",
96
+ providerId: "deepseek",
97
+ label: "DeepSeek V4 Flash",
98
+ ...DEEPSEEK_MODEL_BASE
99
+ },
100
+ {
101
+ id: "deepseek-v4-pro",
102
+ providerId: "deepseek",
103
+ label: "DeepSeek V4 Pro",
104
+ ...DEEPSEEK_MODEL_BASE
105
+ },
106
+ {
107
+ id: "gpt-5.5",
108
+ providerId: "yls",
109
+ label: "GPT-5.5 via YLS",
110
+ ...GPT_RESPONSES_MODEL_BASE
111
+ },
112
+ {
113
+ id: "gpt-5.4",
114
+ providerId: "yls",
115
+ label: "GPT-5.4 via YLS",
116
+ ...GPT_RESPONSES_MODEL_BASE,
117
+ request: {
118
+ ...GPT_RESPONSES_MODEL_BASE.request,
119
+ reasoningEffortDefault: "xhigh"
868
120
  }
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 [];
121
+ },
122
+ {
123
+ id: "gpt-5.4",
124
+ providerId: "ttapi",
125
+ label: "GPT-5.4 via TTAPI",
126
+ ...GPT_RESPONSES_MODEL_BASE,
127
+ request: {
128
+ ...GPT_RESPONSES_MODEL_BASE.request,
129
+ thinkingDefault: "disabled",
130
+ reasoningEffortDefault: "xhigh"
889
131
  }
890
- const content = item.content;
891
- if (!Array.isArray(content)) {
892
- return [];
132
+ },
133
+ {
134
+ id: "gpt-5.5",
135
+ providerId: "openai",
136
+ label: "GPT-5.5",
137
+ ...GPT_RESPONSES_MODEL_BASE
138
+ },
139
+ {
140
+ id: "gpt-5.4",
141
+ providerId: "openai",
142
+ label: "GPT-5.4",
143
+ ...GPT_RESPONSES_MODEL_BASE,
144
+ request: {
145
+ ...GPT_RESPONSES_MODEL_BASE.request,
146
+ reasoningEffortDefault: "xhigh"
893
147
  }
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
148
  }
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
- }));
149
+ ];
150
+ function listModelInfos() {
151
+ return [...MODEL_CATALOG];
916
152
  }
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;
153
+ function findProviderInfo(providerId) {
154
+ return PROVIDER_CATALOG.find((provider) => provider.id === normalizeProviderId(providerId));
932
155
  }
933
- function abortStream2(stream) {
934
- try {
935
- stream?.controller?.abort();
936
- } catch {
156
+ function findModelInfo(providerId, modelId) {
157
+ const normalizedProvider = normalizeProviderId(providerId);
158
+ const normalizedModel = normalizeModelId(modelId);
159
+ const known = MODEL_CATALOG.find((model) => model.providerId === normalizedProvider && model.id === normalizedModel);
160
+ if (known) {
161
+ return known;
937
162
  }
163
+ if (normalizedProvider === "openai-compatible") {
164
+ return createOpenAiCompatibleModelInfo(normalizedModel);
165
+ }
166
+ return void 0;
938
167
  }
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];
168
+ function resolveModelProfile(input) {
169
+ const configuredProvider = normalizeProviderId(input.provider);
170
+ const configuredModel = normalizeModelId(input.model);
171
+ const provider = findProviderInfo(configuredProvider);
172
+ if (!provider) {
173
+ throw new Error(`Unknown provider: ${configuredProvider}. Check KITTY_PROVIDER.`);
948
174
  }
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;
175
+ const model = findModelInfo(configuredProvider, configuredModel);
176
+ if (!model) {
177
+ throw new Error(`Unknown model for provider ${configuredProvider}: ${configuredModel}. Check KITTY_MODEL.`);
957
178
  }
958
- return [...new Set(candidates)];
179
+ return {
180
+ provider,
181
+ model,
182
+ configuredProvider,
183
+ configuredModel
184
+ };
959
185
  }
960
- function ensureTrailingSlash(baseUrl) {
961
- return baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
186
+ function normalizeProviderId(value) {
187
+ const normalized = String(value ?? "").trim().toLowerCase();
188
+ return normalized || "openai-compatible";
962
189
  }
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;
190
+ function normalizeModelId(value) {
191
+ return String(value ?? "").trim();
969
192
  }
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;
193
+ function createOpenAiCompatibleModelInfo(modelId) {
980
194
  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
- }));
195
+ id: modelId,
196
+ providerId: "openai-compatible",
197
+ label: modelId,
198
+ wireApi: "chat.completions",
199
+ capabilities: {
200
+ tools: true,
201
+ reasoning: false,
202
+ reasoningContentReplay: "never",
203
+ streaming: true,
204
+ usage: true,
205
+ cache: "none"
206
+ },
207
+ request: {
208
+ thinkingDefault: "disabled",
209
+ maxOutputTokensParam: "max_tokens"
987
210
  },
988
- markHealthy(baseUrl) {
989
- preferredBaseUrl = baseUrl;
211
+ limit: {
212
+ context: 128e3,
213
+ output: 8e3
990
214
  }
991
215
  };
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
216
  }
1012
217
 
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);
218
+ // src/provider/capabilities.ts
219
+ function resolveProviderCapabilities(input) {
220
+ const profile = resolveModelProfile(input);
221
+ return {
222
+ provider: profile.provider.id,
223
+ model: profile.model.id,
224
+ wireApi: profile.model.wireApi,
225
+ supportsReasoningContent: profile.model.capabilities.reasoningContentReplay !== "never",
226
+ defaultReasoningEnabled: profile.model.capabilities.reasoning,
227
+ defaultReasoningEffort: profile.model.request.reasoningEffortDefault,
228
+ requestTimeoutMs: profile.provider.requestTimeoutMs,
229
+ doctorProbeTimeoutMs: profile.provider.doctorProbeTimeoutMs
1037
230
  };
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
231
  }
1207
232
 
1208
233
  // src/session/messages.ts
1209
- function buildChatMessages(systemPrompt, messages, contextWindowMessages, model) {
234
+ function buildChatMessages(systemPrompt, messages, contextWindowMessages, model, provider) {
1210
235
  const recentMessages = messages.slice(-contextWindowMessages);
1211
236
  return [
1212
237
  {
@@ -1215,7 +240,8 @@ function buildChatMessages(systemPrompt, messages, contextWindowMessages, model)
1215
240
  },
1216
241
  ...recentMessages.map(
1217
242
  (message, index) => toChatMessage(message, {
1218
- includeReasoning: shouldIncludeStoredAssistantReasoning(recentMessages, index, model)
243
+ includeReasoning: shouldIncludeStoredAssistantReasoning(recentMessages, index, model, provider),
244
+ provider
1219
245
  })
1220
246
  )
1221
247
  ];
@@ -1300,14 +326,17 @@ function expandStartToToolBoundary(messages, startIndex) {
1300
326
  }
1301
327
  return index;
1302
328
  }
1303
- function modelUsesReasoningContent(model) {
1304
- return resolveProviderCapabilities({ model }).supportsReasoningContent;
329
+ function modelUsesReasoningContent(model, provider) {
330
+ if (provider) {
331
+ return resolveProviderCapabilities({ provider, model }).supportsReasoningContent;
332
+ }
333
+ return listModelInfos().some((item) => item.id === model && item.capabilities.reasoningContentReplay !== "never");
1305
334
  }
1306
335
  function isAssistantMessageInLatestTurn(messages, index) {
1307
336
  return messages[index]?.role === "assistant" && index > findLatestUserIndex(messages);
1308
337
  }
1309
- function shouldIncludeStoredAssistantReasoning(messages, index, model) {
1310
- return modelUsesReasoningContent(model) && messages[index]?.role === "assistant";
338
+ function shouldIncludeStoredAssistantReasoning(messages, index, model, provider) {
339
+ return resolveProviderCapabilities({ provider, model }).supportsReasoningContent && messages[index]?.role === "assistant";
1311
340
  }
1312
341
 
1313
342
  // src/session/turnFrame.ts
@@ -1356,7 +385,7 @@ function oneLine(value) {
1356
385
  }
1357
386
 
1358
387
  // src/session/taskStateHistory.ts
1359
- import path3 from "path";
388
+ import path from "path";
1360
389
  function collectActiveFiles(messages) {
1361
390
  const files = [];
1362
391
  for (const message of messages) {
@@ -1485,7 +514,7 @@ function normalizeFilePath(value) {
1485
514
  if (trimmed.length > 260) {
1486
515
  return truncate(trimmed, 260);
1487
516
  }
1488
- return trimmed.includes(path3.sep) || trimmed.includes("/") || trimmed.includes(".") ? trimmed : void 0;
517
+ return trimmed.includes(path.sep) || trimmed.includes("/") || trimmed.includes(".") ? trimmed : void 0;
1489
518
  }
1490
519
 
1491
520
  // src/session/taskState.ts
@@ -1658,14 +687,14 @@ function noteSessionDiff(session, change, timestamp = (/* @__PURE__ */ new Date(
1658
687
  };
1659
688
  }
1660
689
  function normalizeSessionDiffChange(change, timestamp) {
1661
- const toolName = normalizeText2(change?.toolName);
690
+ const toolName = normalizeText(change?.toolName);
1662
691
  const changedPaths = takeLastUniquePaths(change?.changedPaths ?? []);
1663
692
  if (!toolName || changedPaths.length === 0) {
1664
693
  return null;
1665
694
  }
1666
695
  return {
1667
696
  toolName,
1668
- changeId: normalizeText2(change?.changeId) || void 0,
697
+ changeId: normalizeText(change?.changeId) || void 0,
1669
698
  changedPaths,
1670
699
  diff: truncate2(change?.diff, MAX_DIFF_PREVIEW_CHARS),
1671
700
  diagnosticsStatus: normalizeDiagnosticsStatus(change?.diagnosticsStatus),
@@ -1678,7 +707,7 @@ function takeLastUniquePaths(paths) {
1678
707
  const seen = /* @__PURE__ */ new Set();
1679
708
  const result = [];
1680
709
  for (let index = paths.length - 1; index >= 0; index -= 1) {
1681
- const normalized = normalizeText2(paths[index]);
710
+ const normalized = normalizeText(paths[index]);
1682
711
  if (!normalized || seen.has(normalized)) {
1683
712
  continue;
1684
713
  }
@@ -1696,20 +725,180 @@ function normalizeDiagnosticsStatus(value) {
1696
725
  function normalizeCount(value) {
1697
726
  return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.trunc(value) : 0;
1698
727
  }
1699
- function normalizeText2(value) {
1700
- return String(value ?? "").trim();
728
+ function normalizeText(value) {
729
+ return String(value ?? "").trim();
730
+ }
731
+ function truncate2(value, maxChars) {
732
+ if (!value) {
733
+ return void 0;
734
+ }
735
+ return value.length <= maxChars ? value : `${value.slice(0, maxChars)}...`;
736
+ }
737
+
738
+ // src/session/checkpoint/shared.ts
739
+ import crypto from "crypto";
740
+ import path2 from "path";
741
+ var MAX_COMPLETED_STEPS = 8;
742
+ var MAX_BATCH_TOOLS = 6;
743
+ var MAX_BATCH_PATHS = 6;
744
+ var MAX_SUMMARY_CHARS = 220;
745
+ function fingerprintFocus(focus) {
746
+ return crypto.createHash("sha1").update(focus.trim().toLowerCase()).digest("hex");
747
+ }
748
+ function normalizeText2(value) {
749
+ return String(value ?? "").replace(/\s+/g, " ").trim();
750
+ }
751
+ function truncate3(value, maxChars) {
752
+ if (!value) {
753
+ return void 0;
754
+ }
755
+ return value.length <= maxChars ? value : `${value.slice(0, maxChars)}...`;
756
+ }
757
+ function takeLastUnique2(values, limit) {
758
+ const seen = /* @__PURE__ */ new Set();
759
+ const result = [];
760
+ for (let index = values.length - 1; index >= 0; index -= 1) {
761
+ const normalized = normalizeText2(values[index]);
762
+ if (!normalized || seen.has(normalized)) {
763
+ continue;
764
+ }
765
+ seen.add(normalized);
766
+ result.unshift(normalized);
767
+ if (result.length >= limit) {
768
+ break;
769
+ }
770
+ }
771
+ return result;
772
+ }
773
+ function safeParseObject2(raw) {
774
+ if (typeof raw !== "string" || raw.trim().length === 0) {
775
+ return null;
776
+ }
777
+ try {
778
+ const parsed = JSON.parse(raw);
779
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
780
+ return null;
781
+ }
782
+ return parsed;
783
+ } catch {
784
+ return null;
785
+ }
786
+ }
787
+ function readString(value) {
788
+ const normalized = normalizeText2(value);
789
+ return normalized || void 0;
790
+ }
791
+ function normalizeToolBatch(toolBatch) {
792
+ if (!toolBatch) {
793
+ return void 0;
794
+ }
795
+ const tools = takeLastUnique2(toolBatch.tools ?? [], MAX_BATCH_TOOLS);
796
+ if (tools.length === 0) {
797
+ return void 0;
798
+ }
799
+ return {
800
+ tools,
801
+ summary: truncate3(normalizeText2(toolBatch.summary) || `Ran ${tools.join(", ")}`, MAX_SUMMARY_CHARS),
802
+ changedPaths: takeLastUnique2(toolBatch.changedPaths ?? [], MAX_BATCH_PATHS),
803
+ recordedAt: normalizeTimestamp(toolBatch.recordedAt, (/* @__PURE__ */ new Date()).toISOString())
804
+ };
805
+ }
806
+
807
+ // src/session/checkpoint/derivation.ts
808
+ function deriveCompletedSteps(session) {
809
+ const completedActions = session.taskState?.completedActions ?? [];
810
+ return takeLastUnique2(completedActions, MAX_COMPLETED_STEPS);
811
+ }
812
+ function deriveRecentToolBatchFromMessages(messages, timestamp) {
813
+ let lastToolIndex = messages.length - 1;
814
+ while (lastToolIndex >= 0 && messages[lastToolIndex]?.role !== "tool") {
815
+ lastToolIndex -= 1;
816
+ }
817
+ if (lastToolIndex < 0) {
818
+ return void 0;
819
+ }
820
+ let startIndex = lastToolIndex;
821
+ while (startIndex >= 0 && messages[startIndex]?.role === "tool") {
822
+ startIndex -= 1;
823
+ }
824
+ const toolMessages = messages.slice(startIndex + 1, lastToolIndex + 1).filter((message) => message.role === "tool");
825
+ const toolNames = toolMessages.map((message) => normalizeText2(message.name)).filter(Boolean);
826
+ return buildToolBatch(toolNames, toolMessages, void 0, timestamp);
827
+ }
828
+ function buildToolBatch(toolNames, toolMessages, changedPaths, timestamp) {
829
+ const tools = takeLastUnique2(toolNames, MAX_BATCH_TOOLS);
830
+ if (tools.length === 0) {
831
+ return void 0;
832
+ }
833
+ const batchChangedPaths = takeLastUnique2(
834
+ [
835
+ ...changedPaths ?? [],
836
+ ...toolMessages.map((message) => readPathFromMessage(message)).filter(Boolean)
837
+ ],
838
+ MAX_BATCH_PATHS
839
+ );
840
+ const recordedAt = normalizeTimestamp(
841
+ toolMessages[toolMessages.length - 1]?.createdAt,
842
+ timestamp
843
+ );
844
+ return {
845
+ tools,
846
+ summary: buildToolBatchSummary(tools, batchChangedPaths),
847
+ changedPaths: batchChangedPaths,
848
+ recordedAt
849
+ };
850
+ }
851
+ function readPathFromMessage(message) {
852
+ const payload = safeParseObject2(message.content);
853
+ return readString(payload?.path) ?? readString(payload?.requestedPath);
854
+ }
855
+ function buildToolBatchSummary(toolNames, changedPaths) {
856
+ const fragments = [`Ran ${toolNames.join(", ")}`];
857
+ if (changedPaths.length > 0) {
858
+ fragments.push(`changed ${changedPaths.join(" | ")}`);
859
+ }
860
+ return truncate3(fragments.join("; "), MAX_SUMMARY_CHARS);
861
+ }
862
+
863
+ // src/session/checkpoint/base.ts
864
+ function createEmptyCheckpoint(timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
865
+ return {
866
+ version: 1,
867
+ status: "active",
868
+ completedSteps: [],
869
+ flow: {
870
+ phase: "active",
871
+ runState: {
872
+ status: "idle",
873
+ source: "checkpoint",
874
+ pendingToolCallCount: 0,
875
+ updatedAt: timestamp
876
+ },
877
+ updatedAt: timestamp
878
+ },
879
+ updatedAt: timestamp
880
+ };
881
+ }
882
+ function createCheckpointForFocus(focus, timestamp) {
883
+ return {
884
+ ...createEmptyCheckpoint(timestamp),
885
+ focus,
886
+ focusFingerprint: focus ? fingerprintFocus(focus) : void 0
887
+ };
1701
888
  }
1702
- function truncate2(value, maxChars) {
1703
- if (!value) {
1704
- return void 0;
1705
- }
1706
- return value.length <= maxChars ? value : `${value.slice(0, maxChars)}...`;
889
+ function deriveCheckpointFromSession(session, timestamp) {
890
+ const recentToolBatch = deriveRecentToolBatchFromMessages(session.messages, timestamp);
891
+ return {
892
+ ...createCheckpointForFocus(normalizeText2(session.taskState?.focus) || void 0, timestamp),
893
+ completedSteps: deriveCompletedSteps(session),
894
+ recentToolBatch
895
+ };
1707
896
  }
1708
897
 
1709
898
  // src/agent/runtimeTransition/shared.ts
1710
899
  var MAX_REASON_ITEMS = 6;
1711
900
  var MAX_REASON_TEXT_CHARS = 220;
1712
- function takeLastUnique2(values) {
901
+ function takeLastUnique3(values) {
1713
902
  const seen = /* @__PURE__ */ new Set();
1714
903
  const result = [];
1715
904
  for (let index = values.length - 1; index >= 0; index -= 1) {
@@ -1725,7 +914,7 @@ function takeLastUnique2(values) {
1725
914
  }
1726
915
  return result;
1727
916
  }
1728
- function truncate3(value) {
917
+ function truncate4(value) {
1729
918
  return value.length <= MAX_REASON_TEXT_CHARS ? value : `${value.slice(0, MAX_REASON_TEXT_CHARS)}...`;
1730
919
  }
1731
920
  function normalizeText3(value) {
@@ -1738,73 +927,6 @@ function clampWholeNumber(value, min, max, fallback) {
1738
927
  return Math.max(min, Math.min(max, Math.trunc(value)));
1739
928
  }
1740
929
 
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
930
  // src/agent/runtimeTransition/normalize.ts
1809
931
  function normalizeRuntimeTransition(transition, timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
1810
932
  if (!transition || typeof transition !== "object") {
@@ -1832,7 +954,7 @@ function normalizeRuntimeTransition(transition, timestamp = (/* @__PURE__ */ new
1832
954
  function normalizeContinueTransition(reason, timestamp) {
1833
955
  switch (reason.code) {
1834
956
  case "continue.after_tool_batch": {
1835
- const toolNames = takeLastUnique2(reason.toolNames);
957
+ const toolNames = takeLastUnique3(reason.toolNames);
1836
958
  if (toolNames.length === 0) {
1837
959
  return void 0;
1838
960
  }
@@ -1841,7 +963,7 @@ function normalizeContinueTransition(reason, timestamp) {
1841
963
  reason: {
1842
964
  code: reason.code,
1843
965
  toolNames,
1844
- changedPaths: takeLastUnique2(reason.changedPaths ?? [])
966
+ changedPaths: takeLastUnique3(reason.changedPaths ?? [])
1845
967
  },
1846
968
  timestamp
1847
969
  };
@@ -1867,7 +989,7 @@ function normalizeRecoverTransition(reason, timestamp) {
1867
989
  reason: {
1868
990
  code: reason.code,
1869
991
  consecutiveFailures: clampWholeNumber(reason.consecutiveFailures, 1, 50, 1) ?? 1,
1870
- error: truncate3(normalizeText3(reason.error) || "request failed"),
992
+ error: truncate4(normalizeText3(reason.error) || "request failed"),
1871
993
  configuredModel: normalizeText3(reason.configuredModel) || "unknown_model",
1872
994
  requestModel: normalizeText3(reason.requestModel) || "unknown_model",
1873
995
  contextWindowMessages: clampWholeNumber(reason.contextWindowMessages, 1, 999, 1) ?? 1,
@@ -1886,7 +1008,7 @@ function normalizeFinalizeTransition(reason, timestamp) {
1886
1008
  action: "finalize",
1887
1009
  reason: {
1888
1010
  code: reason.code,
1889
- changedPaths: takeLastUnique2(reason.changedPaths ?? [])
1011
+ changedPaths: takeLastUnique3(reason.changedPaths ?? [])
1890
1012
  },
1891
1013
  timestamp
1892
1014
  };
@@ -1895,7 +1017,7 @@ function normalizeYieldTransition(reason, timestamp) {
1895
1017
  if (reason.code !== "yield.execution_wait") {
1896
1018
  return void 0;
1897
1019
  }
1898
- const executionIds = takeLastUnique2(reason.executionIds ?? []);
1020
+ const executionIds = takeLastUnique3(reason.executionIds ?? []);
1899
1021
  if (executionIds.length === 0) {
1900
1022
  return void 0;
1901
1023
  }
@@ -1904,7 +1026,7 @@ function normalizeYieldTransition(reason, timestamp) {
1904
1026
  reason: {
1905
1027
  code: reason.code,
1906
1028
  executionIds,
1907
- toolNames: takeLastUnique2(reason.toolNames ?? [])
1029
+ toolNames: takeLastUnique3(reason.toolNames ?? [])
1908
1030
  },
1909
1031
  timestamp
1910
1032
  };
@@ -1994,163 +1116,70 @@ function normalizeRunStateSource(source, status) {
1994
1116
  return "checkpoint";
1995
1117
  }
1996
1118
 
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
- }
1119
+ // src/agent/runtimeTransition/builders.ts
1120
+ function createToolBatchTransition(input, timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
2058
1121
  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())
1122
+ action: "continue",
1123
+ reason: {
1124
+ code: "continue.after_tool_batch",
1125
+ toolNames: takeLastUnique3(input.toolNames),
1126
+ changedPaths: takeLastUnique3(input.changedPaths ?? [])
1127
+ },
1128
+ timestamp
2063
1129
  };
2064
1130
  }
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
- );
1131
+ function createEmptyAssistantResponseTransition(timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
2103
1132
  return {
2104
- tools,
2105
- summary: buildToolBatchSummary(tools, batchChangedPaths),
2106
- changedPaths: batchChangedPaths,
2107
- recordedAt
1133
+ action: "continue",
1134
+ reason: {
1135
+ code: "continue.empty_assistant_response"
1136
+ },
1137
+ timestamp
2108
1138
  };
2109
1139
  }
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);
1140
+ function createProviderRecoveryTransition(input, timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
1141
+ return {
1142
+ action: "recover",
1143
+ reason: {
1144
+ code: "recover.provider_request_retry",
1145
+ consecutiveFailures: Math.max(1, Math.trunc(input.consecutiveFailures)),
1146
+ error: truncate4(normalizeText3(input.error?.message ?? input.error) || "request failed"),
1147
+ configuredModel: normalizeText3(input.configuredModel) || "unknown_model",
1148
+ requestModel: normalizeText3(input.requestModel) || "unknown_model",
1149
+ contextWindowMessages: Math.max(1, Math.trunc(input.requestConfig.contextWindowMessages)),
1150
+ maxContextChars: Math.max(1, Math.trunc(input.requestConfig.maxContextChars)),
1151
+ contextSummaryChars: Math.max(1, Math.trunc(input.requestConfig.contextSummaryChars)),
1152
+ delayMs: Math.max(0, Math.trunc(input.delayMs))
1153
+ },
1154
+ timestamp
1155
+ };
2120
1156
  }
2121
-
2122
- // src/session/checkpoint/base.ts
2123
- function createEmptyCheckpoint(timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
1157
+ function createFinalizeTransition(input, timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
2124
1158
  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
1159
+ action: "finalize",
1160
+ reason: {
1161
+ code: "finalize.completed",
1162
+ changedPaths: takeLastUnique3([...input.changedPaths])
2137
1163
  },
2138
- updatedAt: timestamp
1164
+ timestamp
2139
1165
  };
2140
1166
  }
2141
- function createCheckpointForFocus(focus, timestamp) {
1167
+ function createExecutionWaitYieldTransition(input, timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
2142
1168
  return {
2143
- ...createEmptyCheckpoint(timestamp),
2144
- focus,
2145
- focusFingerprint: focus ? fingerprintFocus(focus) : void 0
1169
+ action: "yield",
1170
+ reason: {
1171
+ code: "yield.execution_wait",
1172
+ executionIds: takeLastUnique3([...input.executionIds]),
1173
+ toolNames: takeLastUnique3([...input.toolNames])
1174
+ },
1175
+ timestamp
2146
1176
  };
2147
1177
  }
2148
- function deriveCheckpointFromSession(session, timestamp) {
2149
- const recentToolBatch = deriveRecentToolBatchFromMessages(session.messages, timestamp);
1178
+ function buildRunTurnResult(input) {
2150
1179
  return {
2151
- ...createCheckpointForFocus(normalizeText4(session.taskState?.focus) || void 0, timestamp),
2152
- completedSteps: deriveCompletedSteps(session),
2153
- recentToolBatch
1180
+ session: input.session,
1181
+ changedPaths: [...input.changedPaths],
1182
+ transition: input.transition
2154
1183
  };
2155
1184
  }
2156
1185
 
@@ -2159,14 +1188,14 @@ function normalizeCheckpoint(checkpoint, timestamp = (/* @__PURE__ */ new Date()
2159
1188
  if (!checkpoint) {
2160
1189
  return void 0;
2161
1190
  }
2162
- const focus = normalizeText4(checkpoint.focus) || void 0;
1191
+ const focus = normalizeText2(checkpoint.focus) || void 0;
2163
1192
  const status = checkpoint.status === "completed" ? "completed" : "active";
2164
1193
  return {
2165
1194
  version: 1,
2166
1195
  focus,
2167
- focusFingerprint: normalizeText4(checkpoint.focusFingerprint) || (focus ? fingerprintFocus(focus) : void 0),
1196
+ focusFingerprint: normalizeText2(checkpoint.focusFingerprint) || (focus ? fingerprintFocus(focus) : void 0),
2168
1197
  status,
2169
- completedSteps: takeLastUnique3(checkpoint.completedSteps ?? [], 8),
1198
+ completedSteps: takeLastUnique2(checkpoint.completedSteps ?? [], 8),
2170
1199
  recentToolBatch: normalizeToolBatch(checkpoint.recentToolBatch),
2171
1200
  flow: normalizeCheckpointFlow(checkpoint.flow, status, timestamp),
2172
1201
  updatedAt: normalizeTimestamp(checkpoint.updatedAt, timestamp)
@@ -2212,7 +1241,7 @@ function noteCheckpointTurnInput(session, input, timestamp = (/* @__PURE__ */ ne
2212
1241
  };
2213
1242
  }
2214
1243
  function resolveCurrentFocusCheckpoint(session, timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
2215
- const focus = normalizeText4(session.taskState?.focus) || void 0;
1244
+ const focus = normalizeText2(session.taskState?.focus) || void 0;
2216
1245
  const fingerprint = focus ? fingerprintFocus(focus) : void 0;
2217
1246
  const checkpoint = normalizeCheckpoint(session.checkpoint, timestamp) ?? createEmptyCheckpoint(timestamp);
2218
1247
  if (!focus) {
@@ -2532,7 +1561,7 @@ function readSessionMemoryCurrentFocus(summary) {
2532
1561
  }
2533
1562
 
2534
1563
  // src/session/workset.ts
2535
- import path5 from "path";
1564
+ import path3 from "path";
2536
1565
  var MAX_WORKSET_FILES = 20;
2537
1566
  function createEmptySessionWorkset(timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
2538
1567
  return {
@@ -2616,9 +1645,9 @@ function normalizeWorksetEntry(value) {
2616
1645
  };
2617
1646
  }
2618
1647
  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;
1648
+ const absolutePath = path3.resolve(cwd, targetPath);
1649
+ const relative = path3.relative(cwd, absolutePath);
1650
+ return relative && !relative.startsWith("..") && !path3.isAbsolute(relative) ? relative : absolutePath;
2622
1651
  }
2623
1652
  function readString2(value) {
2624
1653
  return typeof value === "string" && value.trim() ? value : (/* @__PURE__ */ new Date()).toISOString();
@@ -2631,21 +1660,14 @@ function latestWorksetTimestamp(files) {
2631
1660
  }
2632
1661
 
2633
1662
  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
1663
  createEmptyAssistantResponseTransition,
2642
1664
  createProviderRecoveryTransition,
2643
1665
  createFinalizeTransition,
2644
1666
  createExecutionWaitYieldTransition,
2645
1667
  buildRunTurnResult,
2646
1668
  fingerprintFocus,
2647
- normalizeText4 as normalizeText,
2648
- takeLastUnique3 as takeLastUnique,
1669
+ normalizeText2 as normalizeText,
1670
+ takeLastUnique2 as takeLastUnique,
2649
1671
  createEmptyCheckpoint,
2650
1672
  normalizeCheckpoint,
2651
1673
  normalizeSessionCheckpoint,
@@ -2654,13 +1676,8 @@ export {
2654
1676
  noteCheckpointTransition,
2655
1677
  noteCheckpointRecovery,
2656
1678
  noteCheckpointCompleted,
2657
- isAbortError,
2658
- throwIfAborted,
2659
- sleepWithSignal,
2660
- isRetryableApiError,
2661
- recordObservabilityEvent,
2662
- createProviderClientPool,
2663
- fetchAssistantResponse,
1679
+ resolveModelProfile,
1680
+ resolveProviderCapabilities,
2664
1681
  buildChatMessages,
2665
1682
  createMessage,
2666
1683
  createToolMessage,