@jun133/kitty 0.0.13 → 0.0.15

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;
162
+ }
163
+ if (normalizedProvider === "openai-compatible") {
164
+ return createOpenAiCompatibleModelInfo(normalizedModel);
937
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,7 +725,7 @@ 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) {
728
+ function normalizeText(value) {
1700
729
  return String(value ?? "").trim();
1701
730
  }
1702
731
  function truncate2(value, maxChars) {
@@ -1713,7 +742,7 @@ function takeLastUnique2(values) {
1713
742
  const seen = /* @__PURE__ */ new Set();
1714
743
  const result = [];
1715
744
  for (let index = values.length - 1; index >= 0; index -= 1) {
1716
- const normalized = normalizeText3(values[index]);
745
+ const normalized = normalizeText2(values[index]);
1717
746
  if (!normalized || seen.has(normalized)) {
1718
747
  continue;
1719
748
  }
@@ -1728,7 +757,7 @@ function takeLastUnique2(values) {
1728
757
  function truncate3(value) {
1729
758
  return value.length <= MAX_REASON_TEXT_CHARS ? value : `${value.slice(0, MAX_REASON_TEXT_CHARS)}...`;
1730
759
  }
1731
- function normalizeText3(value) {
760
+ function normalizeText2(value) {
1732
761
  return String(value ?? "").replace(/\s+/g, " ").trim();
1733
762
  }
1734
763
  function clampWholeNumber(value, min, max, fallback) {
@@ -1765,9 +794,9 @@ function createProviderRecoveryTransition(input, timestamp = (/* @__PURE__ */ ne
1765
794
  reason: {
1766
795
  code: "recover.provider_request_retry",
1767
796
  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",
797
+ error: truncate3(normalizeText2(input.error?.message ?? input.error) || "request failed"),
798
+ configuredModel: normalizeText2(input.configuredModel) || "unknown_model",
799
+ requestModel: normalizeText2(input.requestModel) || "unknown_model",
1771
800
  contextWindowMessages: Math.max(1, Math.trunc(input.requestConfig.contextWindowMessages)),
1772
801
  maxContextChars: Math.max(1, Math.trunc(input.requestConfig.maxContextChars)),
1773
802
  contextSummaryChars: Math.max(1, Math.trunc(input.requestConfig.contextSummaryChars)),
@@ -1867,9 +896,9 @@ function normalizeRecoverTransition(reason, timestamp) {
1867
896
  reason: {
1868
897
  code: reason.code,
1869
898
  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",
899
+ error: truncate3(normalizeText2(reason.error) || "request failed"),
900
+ configuredModel: normalizeText2(reason.configuredModel) || "unknown_model",
901
+ requestModel: normalizeText2(reason.requestModel) || "unknown_model",
1873
902
  contextWindowMessages: clampWholeNumber(reason.contextWindowMessages, 1, 999, 1) ?? 1,
1874
903
  maxContextChars: clampWholeNumber(reason.maxContextChars, 1, 1e6, 1) ?? 1,
1875
904
  contextSummaryChars: clampWholeNumber(reason.contextSummaryChars, 1, 1e6, 1) ?? 1,
@@ -1924,7 +953,7 @@ function normalizeCheckpointFlow(flow, status, timestamp = (/* @__PURE__ */ new
1924
953
  });
1925
954
  return {
1926
955
  phase,
1927
- reason: lastTransition ? formatRuntimeTransitionReason(lastTransition) : normalizeText3(flow?.reason) || void 0,
956
+ reason: lastTransition ? formatRuntimeTransitionReason(lastTransition) : normalizeText2(flow?.reason) || void 0,
1928
957
  recoveryFailures: lastTransition?.action === "recover" ? lastTransition.reason.consecutiveFailures : phase === "recovery" ? clampWholeNumber(flow?.recoveryFailures, 1, 50, void 0) : void 0,
1929
958
  runState,
1930
959
  lastTransition,
@@ -1995,16 +1024,16 @@ function normalizeRunStateSource(source, status) {
1995
1024
  }
1996
1025
 
1997
1026
  // src/session/checkpoint/shared.ts
1998
- import crypto2 from "crypto";
1999
- import path4 from "path";
1027
+ import crypto from "crypto";
1028
+ import path2 from "path";
2000
1029
  var MAX_COMPLETED_STEPS = 8;
2001
1030
  var MAX_BATCH_TOOLS = 6;
2002
1031
  var MAX_BATCH_PATHS = 6;
2003
1032
  var MAX_SUMMARY_CHARS = 220;
2004
1033
  function fingerprintFocus(focus) {
2005
- return crypto2.createHash("sha1").update(focus.trim().toLowerCase()).digest("hex");
1034
+ return crypto.createHash("sha1").update(focus.trim().toLowerCase()).digest("hex");
2006
1035
  }
2007
- function normalizeText4(value) {
1036
+ function normalizeText3(value) {
2008
1037
  return String(value ?? "").replace(/\s+/g, " ").trim();
2009
1038
  }
2010
1039
  function truncate4(value, maxChars) {
@@ -2017,7 +1046,7 @@ function takeLastUnique3(values, limit) {
2017
1046
  const seen = /* @__PURE__ */ new Set();
2018
1047
  const result = [];
2019
1048
  for (let index = values.length - 1; index >= 0; index -= 1) {
2020
- const normalized = normalizeText4(values[index]);
1049
+ const normalized = normalizeText3(values[index]);
2021
1050
  if (!normalized || seen.has(normalized)) {
2022
1051
  continue;
2023
1052
  }
@@ -2044,7 +1073,7 @@ function safeParseObject2(raw) {
2044
1073
  }
2045
1074
  }
2046
1075
  function readString(value) {
2047
- const normalized = normalizeText4(value);
1076
+ const normalized = normalizeText3(value);
2048
1077
  return normalized || void 0;
2049
1078
  }
2050
1079
  function normalizeToolBatch(toolBatch) {
@@ -2057,7 +1086,7 @@ function normalizeToolBatch(toolBatch) {
2057
1086
  }
2058
1087
  return {
2059
1088
  tools,
2060
- summary: truncate4(normalizeText4(toolBatch.summary) || `Ran ${tools.join(", ")}`, MAX_SUMMARY_CHARS),
1089
+ summary: truncate4(normalizeText3(toolBatch.summary) || `Ran ${tools.join(", ")}`, MAX_SUMMARY_CHARS),
2061
1090
  changedPaths: takeLastUnique3(toolBatch.changedPaths ?? [], MAX_BATCH_PATHS),
2062
1091
  recordedAt: normalizeTimestamp(toolBatch.recordedAt, (/* @__PURE__ */ new Date()).toISOString())
2063
1092
  };
@@ -2081,7 +1110,7 @@ function deriveRecentToolBatchFromMessages(messages, timestamp) {
2081
1110
  startIndex -= 1;
2082
1111
  }
2083
1112
  const toolMessages = messages.slice(startIndex + 1, lastToolIndex + 1).filter((message) => message.role === "tool");
2084
- const toolNames = toolMessages.map((message) => normalizeText4(message.name)).filter(Boolean);
1113
+ const toolNames = toolMessages.map((message) => normalizeText3(message.name)).filter(Boolean);
2085
1114
  return buildToolBatch(toolNames, toolMessages, void 0, timestamp);
2086
1115
  }
2087
1116
  function buildToolBatch(toolNames, toolMessages, changedPaths, timestamp) {
@@ -2148,7 +1177,7 @@ function createCheckpointForFocus(focus, timestamp) {
2148
1177
  function deriveCheckpointFromSession(session, timestamp) {
2149
1178
  const recentToolBatch = deriveRecentToolBatchFromMessages(session.messages, timestamp);
2150
1179
  return {
2151
- ...createCheckpointForFocus(normalizeText4(session.taskState?.focus) || void 0, timestamp),
1180
+ ...createCheckpointForFocus(normalizeText3(session.taskState?.focus) || void 0, timestamp),
2152
1181
  completedSteps: deriveCompletedSteps(session),
2153
1182
  recentToolBatch
2154
1183
  };
@@ -2159,12 +1188,12 @@ 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 = normalizeText3(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: normalizeText3(checkpoint.focusFingerprint) || (focus ? fingerprintFocus(focus) : void 0),
2168
1197
  status,
2169
1198
  completedSteps: takeLastUnique3(checkpoint.completedSteps ?? [], 8),
2170
1199
  recentToolBatch: normalizeToolBatch(checkpoint.recentToolBatch),
@@ -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 = normalizeText3(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,20 +1660,13 @@ 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,
1669
+ normalizeText3 as normalizeText,
2648
1670
  takeLastUnique3 as takeLastUnique,
2649
1671
  createEmptyCheckpoint,
2650
1672
  normalizeCheckpoint,
@@ -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,