@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.
- package/README.md +19 -133
- package/dist/{App-V6SLDWQH.mjs → App-CBTIS4IK.mjs} +30 -18
- package/dist/{chunk-MMIH75OY.mjs → chunk-7FDXKNTM.mjs} +1612 -767
- package/dist/{chunk-DWGFLIQA.mjs → chunk-AQEMM5VH.mjs} +1 -1
- package/dist/{chunk-4BN45TQG.mjs → chunk-KUP5OMPB.mjs} +1 -1
- package/dist/{chunk-4HIVDFN5.mjs → chunk-NBKU7KA4.mjs} +1 -12
- package/dist/{chunk-3KMC6H5K.mjs → chunk-S4QTRPZ7.mjs} +238 -1221
- package/dist/{chunk-6WGSABUQ.mjs → chunk-WIKLME2V.mjs} +82 -14
- package/dist/cli.js +1477 -1438
- package/dist/cli.js.map +1 -1
- package/dist/{interactive-RSJ35TB5.mjs → interactive-BQMJFYHZ.mjs} +5 -5
- package/dist/{oneShot-7P5FDWFX.mjs → oneShot-4QBXBCNN.mjs} +3 -3
- package/dist/{session-XKWJHRVY.mjs → session-V7AYOK2Q.mjs} +2 -2
- package/dist/tui.mjs +106 -97
- package/package.json +1 -1
|
@@ -1,1212 +1,237 @@
|
|
|
1
|
-
// src/provider/
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
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
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
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
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
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
|
-
|
|
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
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
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
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
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
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
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
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
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
|
-
|
|
891
|
-
|
|
892
|
-
|
|
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
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
function: {
|
|
912
|
-
name: item.name ?? "",
|
|
913
|
-
arguments: item.arguments ?? ""
|
|
914
|
-
}
|
|
915
|
-
}));
|
|
149
|
+
];
|
|
150
|
+
function listModelInfos() {
|
|
151
|
+
return [...MODEL_CATALOG];
|
|
916
152
|
}
|
|
917
|
-
function
|
|
918
|
-
|
|
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
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
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
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
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
|
|
950
|
-
|
|
951
|
-
|
|
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
|
|
179
|
+
return {
|
|
180
|
+
provider,
|
|
181
|
+
model,
|
|
182
|
+
configuredProvider,
|
|
183
|
+
configuredModel
|
|
184
|
+
};
|
|
959
185
|
}
|
|
960
|
-
function
|
|
961
|
-
|
|
186
|
+
function normalizeProviderId(value) {
|
|
187
|
+
const normalized = String(value ?? "").trim().toLowerCase();
|
|
188
|
+
return normalized || "openai-compatible";
|
|
962
189
|
}
|
|
963
|
-
function
|
|
964
|
-
|
|
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
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
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
|
-
|
|
989
|
-
|
|
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/
|
|
1014
|
-
|
|
1015
|
-
const
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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(
|
|
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 =
|
|
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:
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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(
|
|
1769
|
-
configuredModel:
|
|
1770
|
-
requestModel:
|
|
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(
|
|
1871
|
-
configuredModel:
|
|
1872
|
-
requestModel:
|
|
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) :
|
|
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
|
|
1999
|
-
import
|
|
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
|
|
1034
|
+
return crypto.createHash("sha1").update(focus.trim().toLowerCase()).digest("hex");
|
|
2006
1035
|
}
|
|
2007
|
-
function
|
|
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 =
|
|
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 =
|
|
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(
|
|
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) =>
|
|
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(
|
|
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 =
|
|
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:
|
|
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 =
|
|
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
|
|
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 =
|
|
2620
|
-
const relative =
|
|
2621
|
-
return relative && !relative.startsWith("..") && !
|
|
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
|
-
|
|
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
|
-
|
|
2658
|
-
|
|
2659
|
-
sleepWithSignal,
|
|
2660
|
-
isRetryableApiError,
|
|
2661
|
-
recordObservabilityEvent,
|
|
2662
|
-
createProviderClientPool,
|
|
2663
|
-
fetchAssistantResponse,
|
|
1679
|
+
resolveModelProfile,
|
|
1680
|
+
resolveProviderCapabilities,
|
|
2664
1681
|
buildChatMessages,
|
|
2665
1682
|
createMessage,
|
|
2666
1683
|
createToolMessage,
|