@lightharu/krouter 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/LICENSE +679 -0
  2. package/README.md +238 -0
  3. package/dist-web/assets/index-CM4-0adf.css +1 -0
  4. package/dist-web/assets/index-DCslvfUR.js +139 -0
  5. package/dist-web/favicon.svg +9 -0
  6. package/dist-web/icon.svg +9 -0
  7. package/dist-web/index.html +19 -0
  8. package/out-server/main/kiroAuthSync.js +249 -0
  9. package/out-server/main/kproxy/certManager.js +262 -0
  10. package/out-server/main/kproxy/index.js +254 -0
  11. package/out-server/main/kproxy/mitmProxy.js +475 -0
  12. package/out-server/main/kproxy/types.js +23 -0
  13. package/out-server/main/proxy/accountPool.js +543 -0
  14. package/out-server/main/proxy/clientConfig.js +596 -0
  15. package/out-server/main/proxy/index.js +25 -0
  16. package/out-server/main/proxy/kiroApi.js +1996 -0
  17. package/out-server/main/proxy/logger.js +407 -0
  18. package/out-server/main/proxy/modelCatalog.js +75 -0
  19. package/out-server/main/proxy/promptCacheTracker.js +301 -0
  20. package/out-server/main/proxy/proxyServer.js +3543 -0
  21. package/out-server/main/proxy/selfSignedCert.js +179 -0
  22. package/out-server/main/proxy/systemProxy.js +250 -0
  23. package/out-server/main/proxy/tokenCounter.js +164 -0
  24. package/out-server/main/proxy/toolNameRegistry.js +57 -0
  25. package/out-server/main/proxy/translator.js +1084 -0
  26. package/out-server/main/proxy/types.js +3 -0
  27. package/out-server/main/registration/browser-identity.js +184 -0
  28. package/out-server/main/registration/chainProxy.js +349 -0
  29. package/out-server/main/registration/config.js +58 -0
  30. package/out-server/main/registration/email-service.js +801 -0
  31. package/out-server/main/registration/fingerprint.js +352 -0
  32. package/out-server/main/registration/http-utils.js +148 -0
  33. package/out-server/main/registration/jwe.js +74 -0
  34. package/out-server/main/registration/names.js +142 -0
  35. package/out-server/main/registration/proton-mail-window.js +339 -0
  36. package/out-server/main/registration/registrar.js +1715 -0
  37. package/out-server/main/registration/tlsClientPool.js +70 -0
  38. package/out-server/main/registration/xxtea.js +161 -0
  39. package/out-server/main/runtimePaths.js +19 -0
  40. package/out-server/main/utils/redact.js +95 -0
  41. package/out-server/server/index.js +1272 -0
  42. package/out-server/server/services/accountExtras.js +105 -0
  43. package/out-server/server/services/accountProfileHydration.js +95 -0
  44. package/out-server/server/services/authFlows.js +509 -0
  45. package/out-server/server/services/dashboardTunnel.js +315 -0
  46. package/out-server/server/services/diagnostics.js +326 -0
  47. package/out-server/server/services/kiroAccounts.js +431 -0
  48. package/out-server/server/services/kiroSettings.js +260 -0
  49. package/out-server/server/services/kproxyRuntime.js +264 -0
  50. package/out-server/server/services/localKiroCredentials.js +320 -0
  51. package/out-server/server/services/machineIdRuntime.js +327 -0
  52. package/out-server/server/services/protonBrowserRuntime.js +724 -0
  53. package/out-server/server/services/proxyRuntime.js +523 -0
  54. package/out-server/server/services/registrationRuntime.js +106 -0
  55. package/out-server/server/store.js +266 -0
  56. package/package.json +113 -0
  57. package/resources/tls-client-xgo-1.14.0-windows-amd64.dll +0 -0
  58. package/scripts/kiro-manager-cli.cjs +3 -0
  59. package/scripts/krouter-cli.cjs +509 -0
  60. package/src/renderer/src/assets/krouter-logo.svg +11 -0
  61. package/src/renderer/src/assets/krouter-mark.svg +9 -0
@@ -0,0 +1,1084 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.responsesToOpenAIChat = responsesToOpenAIChat;
4
+ exports.openAIChatToResponsesResponse = openAIChatToResponsesResponse;
5
+ exports.openaiToKiro = openaiToKiro;
6
+ exports.kiroToOpenaiResponse = kiroToOpenaiResponse;
7
+ exports.createOpenaiStreamChunk = createOpenaiStreamChunk;
8
+ exports.claudeToKiro = claudeToKiro;
9
+ exports.kiroToClaudeResponse = kiroToClaudeResponse;
10
+ exports.createClaudeStreamEvent = createClaudeStreamEvent;
11
+ // OpenAI/Claude 格式与 Kiro 格式转换器
12
+ const uuid_1 = require("uuid");
13
+ const kiroApi_1 = require("./kiroApi");
14
+ const toolNameRegistry_1 = require("./toolNameRegistry");
15
+ const KIRO_CACHE_POINT = { type: 'default' };
16
+ // 判断模型是否支持 additionalModelRequestFields.thinking 参数
17
+ // 只有 Claude 4+ 系列模型支持,非 Claude 模型(deepseek/minimax/glm/qwen 等)不支持
18
+ function modelSupportsThinkingParam(modelId) {
19
+ const lower = modelId.toLowerCase();
20
+ // 必须是 claude 模型
21
+ if (!lower.includes('claude'))
22
+ return false;
23
+ // claude-3.x 不支持 thinking
24
+ if (lower.includes('claude-3-') || lower.includes('claude-3.'))
25
+ return false;
26
+ // auto 模型由后端决定,保守不传
27
+ if (lower === 'auto')
28
+ return false;
29
+ // claude-sonnet-4、claude-opus-4、claude-haiku-4.5 等都支持
30
+ return true;
31
+ }
32
+ function toKiroCachePoint(cacheControl) {
33
+ if (!cacheControl)
34
+ return undefined;
35
+ if (cacheControl.type !== 'ephemeral') {
36
+ throw new Error(`Unsupported cache_control type: ${cacheControl.type}`);
37
+ }
38
+ return KIRO_CACHE_POINT;
39
+ }
40
+ function mergeCachePoint(first, second) {
41
+ return first || second;
42
+ }
43
+ function responsesToOpenAIChat(request) {
44
+ if (!request || typeof request !== 'object') {
45
+ throw new Error('Responses request body must be an object');
46
+ }
47
+ if (!request.model) {
48
+ throw new Error('Responses request requires model');
49
+ }
50
+ if (request.input === undefined) {
51
+ throw new Error('Responses request requires input');
52
+ }
53
+ const messages = [];
54
+ if (request.instructions) {
55
+ messages.push({ role: 'system', content: request.instructions });
56
+ }
57
+ if (typeof request.input === 'string') {
58
+ messages.push({ role: 'user', content: request.input });
59
+ }
60
+ else {
61
+ if (!Array.isArray(request.input)) {
62
+ throw new Error('Responses input must be a string or an array');
63
+ }
64
+ for (const item of request.input) {
65
+ const itemType = item.type;
66
+ if (itemType === 'function_call_output') {
67
+ if (!item.call_id) {
68
+ throw new Error('function_call_output requires call_id');
69
+ }
70
+ if (item.output === undefined) {
71
+ throw new Error('function_call_output requires output');
72
+ }
73
+ messages.push({
74
+ role: 'tool',
75
+ content: item.output,
76
+ tool_call_id: item.call_id
77
+ });
78
+ }
79
+ else if (itemType === 'function_call') {
80
+ if (!item.call_id) {
81
+ throw new Error('function_call requires call_id');
82
+ }
83
+ if (!item.name) {
84
+ throw new Error('function_call requires name');
85
+ }
86
+ if (item.arguments === undefined) {
87
+ throw new Error('function_call requires arguments');
88
+ }
89
+ messages.push({
90
+ role: 'assistant',
91
+ content: '',
92
+ tool_calls: [{
93
+ id: item.call_id,
94
+ type: 'function',
95
+ function: {
96
+ name: item.name,
97
+ arguments: item.arguments
98
+ }
99
+ }]
100
+ });
101
+ }
102
+ else {
103
+ if (itemType !== undefined && itemType !== 'message') {
104
+ throw new Error(`Unsupported responses input item type: ${itemType}`);
105
+ }
106
+ if (item.content === undefined) {
107
+ throw new Error('message input item requires content');
108
+ }
109
+ messages.push({
110
+ role: item.role === 'assistant' ? 'assistant' : item.role === 'system' ? 'system' : 'user',
111
+ content: convertResponseInputContent(item.content)
112
+ });
113
+ }
114
+ }
115
+ }
116
+ const chatRequest = {
117
+ model: request.model,
118
+ messages
119
+ };
120
+ if (request.temperature !== undefined)
121
+ chatRequest.temperature = request.temperature;
122
+ if (request.top_p !== undefined)
123
+ chatRequest.top_p = request.top_p;
124
+ if (request.max_output_tokens !== undefined)
125
+ chatRequest.max_tokens = request.max_output_tokens;
126
+ if (request.stream !== undefined)
127
+ chatRequest.stream = request.stream;
128
+ if (request.tools !== undefined)
129
+ chatRequest.tools = request.tools;
130
+ const toolChoice = convertResponseToolChoice(request.tool_choice);
131
+ if (toolChoice !== undefined)
132
+ chatRequest.tool_choice = toolChoice;
133
+ if (request.previous_response_id !== undefined)
134
+ chatRequest.conversation_id = request.previous_response_id;
135
+ if (request.metadata !== undefined)
136
+ chatRequest.metadata = request.metadata;
137
+ if (request.kiro_context !== undefined)
138
+ chatRequest.kiro_context = request.kiro_context;
139
+ return chatRequest;
140
+ }
141
+ function convertResponseInputContent(content) {
142
+ if (typeof content === 'string')
143
+ return content;
144
+ if (content === undefined)
145
+ return '';
146
+ if (!Array.isArray(content)) {
147
+ throw new Error('message content must be a string or an array');
148
+ }
149
+ return content.map(part => {
150
+ const partType = part.type;
151
+ if (partType === 'input_image') {
152
+ if (!part.image_url) {
153
+ throw new Error('input_image requires image_url');
154
+ }
155
+ return { type: 'image_url', image_url: { url: part.image_url } };
156
+ }
157
+ if (partType === 'input_file') {
158
+ if (!part.file_data) {
159
+ throw new Error('input_file requires file_data');
160
+ }
161
+ return {
162
+ type: 'file',
163
+ file: {
164
+ file_data: part.file_data,
165
+ ...(part.filename !== undefined ? { filename: part.filename } : {})
166
+ }
167
+ };
168
+ }
169
+ if (partType !== 'input_text' && partType !== 'output_text') {
170
+ throw new Error(`Unsupported responses content part type: ${partType}`);
171
+ }
172
+ if (part.text === undefined) {
173
+ throw new Error(`${partType} requires text`);
174
+ }
175
+ return { type: 'text', text: part.text };
176
+ });
177
+ }
178
+ function convertResponseToolChoice(toolChoice) {
179
+ if (!toolChoice || typeof toolChoice === 'string')
180
+ return toolChoice;
181
+ if (toolChoice.type === 'none' || toolChoice.type === 'auto')
182
+ return toolChoice.type;
183
+ if (toolChoice.type === 'function' && toolChoice.name) {
184
+ return { type: 'function', function: { name: toolChoice.name } };
185
+ }
186
+ if (toolChoice.function?.name)
187
+ return { type: 'function', function: { name: toolChoice.function.name } };
188
+ throw new Error('Unsupported responses tool_choice');
189
+ }
190
+ function openAIChatToResponsesResponse(response, previousResponseId) {
191
+ const output = response.choices.flatMap(choice => {
192
+ if (choice.message.tool_calls?.length) {
193
+ return choice.message.tool_calls.map(toolCall => ({
194
+ type: 'function_call',
195
+ id: `fc_${(0, uuid_1.v4)()}`,
196
+ call_id: toolCall.id,
197
+ name: toolCall.function.name,
198
+ arguments: toolCall.function.arguments
199
+ }));
200
+ }
201
+ return [{
202
+ type: 'message',
203
+ id: `msg_${(0, uuid_1.v4)()}`,
204
+ role: 'assistant',
205
+ content: [{ type: 'output_text', text: choice.message.content || '' }]
206
+ }];
207
+ });
208
+ const usage = {
209
+ input_tokens: response.usage.prompt_tokens,
210
+ output_tokens: response.usage.completion_tokens,
211
+ total_tokens: response.usage.total_tokens
212
+ };
213
+ const cachedTokens = response.usage.prompt_tokens_details?.cached_tokens;
214
+ if (cachedTokens !== undefined) {
215
+ usage.input_tokens_details = { cached_tokens: cachedTokens };
216
+ }
217
+ const reasoningTokens = response.usage.completion_tokens_details?.reasoning_tokens;
218
+ if (reasoningTokens !== undefined) {
219
+ usage.output_tokens_details = { reasoning_tokens: reasoningTokens };
220
+ }
221
+ const responsesResponse = {
222
+ id: `resp_${(0, uuid_1.v4)()}`,
223
+ object: 'response',
224
+ created_at: response.created,
225
+ model: response.model,
226
+ output,
227
+ usage
228
+ };
229
+ if (previousResponseId !== undefined) {
230
+ responsesResponse.previous_response_id = previousResponseId;
231
+ }
232
+ return responsesResponse;
233
+ }
234
+ // ============ OpenAI -> Kiro 转换 ============
235
+ function openaiToKiro(request, profileArn, toolNameRegistry = new toolNameRegistry_1.ToolNameRegistry()) {
236
+ const modelId = (0, kiroApi_1.mapModelId)(request.model);
237
+ const origin = 'AI_EDITOR';
238
+ // 提取系统提示
239
+ let systemPrompt = '';
240
+ let systemCachePoint;
241
+ const nonSystemMessages = [];
242
+ for (const msg of request.messages) {
243
+ if (msg.role === 'system') {
244
+ systemCachePoint = mergeCachePoint(systemCachePoint, toKiroCachePoint(msg.cache_control));
245
+ if (typeof msg.content === 'string') {
246
+ systemPrompt += (systemPrompt ? '\n' : '') + msg.content;
247
+ }
248
+ else if (Array.isArray(msg.content)) {
249
+ for (const part of msg.content) {
250
+ systemCachePoint = mergeCachePoint(systemCachePoint, toKiroCachePoint(part.cache_control));
251
+ if (part.type === 'text' && part.text) {
252
+ systemPrompt += (systemPrompt ? '\n' : '') + part.text;
253
+ }
254
+ }
255
+ }
256
+ }
257
+ else {
258
+ nonSystemMessages.push(msg);
259
+ }
260
+ }
261
+ // 注入时间戳
262
+ const timestamp = new Date().toISOString();
263
+ systemPrompt = `[Context: Current time is ${timestamp}]\n\n${systemPrompt}`;
264
+ // 注入执行导向指令(防止 AI 在探索过程中丢失目标)
265
+ const executionDirective = `
266
+ <execution_discipline>
267
+ 当用户要求执行特定任务时,你必须遵循以下纪律:
268
+ 1. **目标锁定**:在整个会话中始终牢记用户的原始目标,不要在代码探索过程中迷失方向
269
+ 2. **行动优先**:优先执行任务而非仅分析或总结,除非用户明确只要求分析
270
+ 3. **计划执行**:为任务创建明确的步骤计划,逐步执行并标记完成状态
271
+ 4. **禁止确认性收尾**:在任务未完成前,禁止输出"需要我继续吗?"、"需要深入分析吗?"等确认性问题
272
+ 5. **持续推进**:如果发现部分任务已完成,立即继续执行剩余未完成的任务
273
+ 6. **完整交付**:直到所有任务步骤都执行完毕才算完成
274
+ </execution_discipline>
275
+ `;
276
+ systemPrompt = systemPrompt + '\n\n' + executionDirective;
277
+ // 构建历史消息(参考 Proxycast 实现)
278
+ const history = [];
279
+ const toolResults = [];
280
+ let currentContent = '';
281
+ let currentCachePoint;
282
+ const images = [];
283
+ const documents = [];
284
+ for (let i = 0; i < nonSystemMessages.length; i++) {
285
+ const msg = nonSystemMessages[i];
286
+ const isLast = i === nonSystemMessages.length - 1;
287
+ if (msg.role === 'user') {
288
+ const { content: userContent, images: userImages, documents: userDocuments, cachePoint } = extractOpenAIContent(msg);
289
+ const mergedContent = userContent || 'Continue';
290
+ const messageCachePoint = cachePoint;
291
+ if (isLast) {
292
+ currentContent = mergedContent;
293
+ currentCachePoint = messageCachePoint;
294
+ images.push(...userImages);
295
+ documents.push(...userDocuments);
296
+ }
297
+ else {
298
+ history.push({
299
+ userInputMessage: {
300
+ content: mergedContent,
301
+ modelId,
302
+ origin,
303
+ images: userImages.length > 0 ? userImages : undefined,
304
+ documents: userDocuments.length > 0 ? userDocuments : undefined,
305
+ ...(messageCachePoint ? { cachePoint: messageCachePoint } : {})
306
+ }
307
+ });
308
+ }
309
+ }
310
+ else if (msg.role === 'assistant') {
311
+ // Kiro API 要求 content 非空
312
+ // 注意: 故意不读取 msg.reasoning_content (history 中不传给 Kiro)
313
+ // Kiro 后端 schema 仅在响应输出中支持 assistantResponseMessage.reasoningContent,
314
+ // 在请求 history 中传入此字段会触发 400 "Improperly formed request"
315
+ let assistantContent = typeof msg.content === 'string' ? msg.content : '';
316
+ if (!assistantContent.trim() && msg.tool_calls && msg.tool_calls.length > 0) {
317
+ assistantContent = ' ';
318
+ }
319
+ else if (!assistantContent.trim()) {
320
+ assistantContent = 'I understand.';
321
+ }
322
+ const toolUses = [];
323
+ if (msg.tool_calls) {
324
+ for (const tc of msg.tool_calls) {
325
+ if (tc.type === 'function') {
326
+ let input = {};
327
+ try {
328
+ input = JSON.parse(tc.function.arguments);
329
+ }
330
+ catch { /* ignore */ }
331
+ toolUses.push({
332
+ toolUseId: tc.id,
333
+ name: toolNameRegistry.toKiroName(tc.function.name),
334
+ input
335
+ });
336
+ }
337
+ }
338
+ }
339
+ history.push({
340
+ assistantResponseMessage: {
341
+ content: assistantContent,
342
+ toolUses: toolUses.length > 0 ? toolUses : undefined
343
+ }
344
+ });
345
+ }
346
+ else if (msg.role === 'tool') {
347
+ // Tool result - 收集到待处理列表
348
+ if (msg.tool_call_id) {
349
+ let rawText = '';
350
+ let extractedImageCount = 0;
351
+ // content 是数组时(部分客户端把图像/多模态结果挂在这里):
352
+ // 提取所有 text 块拼接为文本;image_url 块提取到外层 images,避免被 JSON.stringify 序列化丢失
353
+ if (Array.isArray(msg.content)) {
354
+ const textParts = [];
355
+ for (const part of msg.content) {
356
+ if (part.type === 'text' && typeof part.text === 'string') {
357
+ textParts.push(part.text);
358
+ }
359
+ else if (part.type === 'image_url' && part.image_url?.url) {
360
+ const img = parseImageUrl(part.image_url.url);
361
+ if (img) {
362
+ images.push(img);
363
+ extractedImageCount++;
364
+ }
365
+ }
366
+ }
367
+ rawText = textParts.join('');
368
+ if (!rawText && extractedImageCount === 0) {
369
+ // 退化:把不识别的结构 stringify 让模型至少看到原始结构
370
+ rawText = JSON.stringify(msg.content);
371
+ }
372
+ if (extractedImageCount > 0) {
373
+ rawText = (rawText ? rawText + '\n\n' : '') +
374
+ `[Tool returned ${extractedImageCount} image${extractedImageCount > 1 ? 's' : ''}, attached to this message]`;
375
+ }
376
+ }
377
+ else {
378
+ rawText = typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content);
379
+ }
380
+ toolResults.push({
381
+ toolUseId: msg.tool_call_id,
382
+ content: [{ text: rawText || '(no output)' }],
383
+ status: 'success'
384
+ });
385
+ }
386
+ // 检查下一条消息:如果不是 tool 消息或已到末尾,将收集的 toolResults 添加为 user 消息
387
+ const nextMsg = nonSystemMessages[i + 1];
388
+ const shouldFlush = !nextMsg || nextMsg.role !== 'tool';
389
+ if (shouldFlush && toolResults.length > 0 && !isLast) {
390
+ // 将 toolResults 作为 user 消息添加到 history
391
+ history.push({
392
+ userInputMessage: {
393
+ content: 'Tool results provided.',
394
+ modelId,
395
+ origin,
396
+ userInputMessageContext: {
397
+ toolResults: [...toolResults]
398
+ }
399
+ }
400
+ });
401
+ // 清空已处理的 toolResults
402
+ toolResults.length = 0;
403
+ }
404
+ }
405
+ }
406
+ // 如果最后一条是 assistant 消息,自动发送 Continue(参考 Proxycast)
407
+ if (history.length > 0 && history[history.length - 1].assistantResponseMessage && !currentContent) {
408
+ currentContent = 'Continue.';
409
+ }
410
+ // 如果没有当前内容但有工具结果(最后一轮的),保留它们传给 currentMessage
411
+ if (!currentContent && toolResults.length > 0) {
412
+ currentContent = 'Tool results provided.';
413
+ }
414
+ // System prompt 以 Kiro 官方方式注入:作为 Human/AI pair 插入到 history 头部
415
+ if (systemPrompt) {
416
+ const systemMessages = [
417
+ {
418
+ userInputMessage: {
419
+ content: systemPrompt,
420
+ userInputMessageContext: {},
421
+ origin,
422
+ ...(systemCachePoint ? { cachePoint: systemCachePoint } : {})
423
+ }
424
+ },
425
+ {
426
+ assistantResponseMessage: {
427
+ content: 'I will follow these instructions.'
428
+ }
429
+ }
430
+ ];
431
+ history.unshift(...systemMessages);
432
+ }
433
+ const finalContent = currentContent || 'Continue.';
434
+ // 转换工具定义
435
+ const kiroTools = convertOpenAITools(request.tools, toolNameRegistry);
436
+ // OpenAI 兼容请求的 thinking 映射到 Kiro additionalModelRequestFields
437
+ // 仅对支持 thinking 的模型传递(Claude 4+ 系列)
438
+ let additionalModelRequestFields;
439
+ if (request.thinking && request.thinking.type !== 'disabled' && modelSupportsThinkingParam(modelId)) {
440
+ additionalModelRequestFields = { thinking: { type: 'adaptive' } };
441
+ }
442
+ return (0, kiroApi_1.buildKiroPayload)(finalContent, modelId, origin, history, kiroTools, toolResults, images, profileArn, {
443
+ maxTokens: request.max_tokens,
444
+ temperature: request.temperature,
445
+ topP: request.top_p
446
+ }, {
447
+ cachePoint: currentCachePoint,
448
+ documents,
449
+ conversationId: request.conversation_id,
450
+ context: request.kiro_context
451
+ }, additionalModelRequestFields);
452
+ }
453
+ function extractOpenAIContent(msg) {
454
+ const images = [];
455
+ const documents = [];
456
+ let content = '';
457
+ let cachePoint = toKiroCachePoint(msg.cache_control);
458
+ if (typeof msg.content === 'string') {
459
+ content = msg.content;
460
+ }
461
+ else if (Array.isArray(msg.content)) {
462
+ for (const part of msg.content) {
463
+ cachePoint = mergeCachePoint(cachePoint, toKiroCachePoint(part.cache_control));
464
+ if (part.type === 'text' && part.text) {
465
+ content += part.text;
466
+ }
467
+ else if (part.type === 'image_url' && part.image_url?.url) {
468
+ const image = parseImageUrl(part.image_url.url);
469
+ if (image) {
470
+ images.push(image);
471
+ }
472
+ }
473
+ else if (part.type === 'file' || part.type === 'document') {
474
+ if (part.file?.file_data) {
475
+ const name = part.file.filename || part.name;
476
+ if (!name) {
477
+ throw new Error(`${part.type} requires filename or name`);
478
+ }
479
+ documents.push(parseOpenAIFileData(part.file.file_data, name));
480
+ }
481
+ else if (part.source) {
482
+ if (!part.name) {
483
+ throw new Error(`${part.type} requires name`);
484
+ }
485
+ documents.push(parseClaudeDocumentSource(part.source, part.name));
486
+ }
487
+ else {
488
+ throw new Error(`${part.type} requires file_data or source`);
489
+ }
490
+ }
491
+ }
492
+ }
493
+ return { content, images, documents, cachePoint };
494
+ }
495
+ // 解析图像 URL(支持 data URL 和 HTTP URL)
496
+ function parseImageUrl(url) {
497
+ if (url.startsWith('data:')) {
498
+ // 解析 data URL: data:image/png;base64,xxxxx
499
+ const match = url.match(/^data:image\/(\w+);base64,(.+)$/);
500
+ if (match) {
501
+ return {
502
+ format: normalizeImageFormat(match[1]),
503
+ source: { bytes: match[2] }
504
+ };
505
+ }
506
+ }
507
+ return null;
508
+ }
509
+ function parseOpenAIFileData(fileData, name) {
510
+ const dataUrlMatch = fileData.match(/^data:([^;]+);base64,(.+)$/);
511
+ if (dataUrlMatch) {
512
+ return {
513
+ format: normalizeDocumentFormat(dataUrlMatch[1], name),
514
+ name,
515
+ source: { bytes: dataUrlMatch[2] }
516
+ };
517
+ }
518
+ return {
519
+ format: normalizeDocumentFormat(undefined, name),
520
+ name,
521
+ source: { bytes: fileData }
522
+ };
523
+ }
524
+ function parseClaudeDocumentSource(source, name) {
525
+ if (source.type === 'base64') {
526
+ return {
527
+ format: normalizeDocumentFormat(source.media_type, name),
528
+ name,
529
+ source: { bytes: source.data }
530
+ };
531
+ }
532
+ if (source.type === 'text') {
533
+ return {
534
+ format: normalizeDocumentFormat(source.media_type, name),
535
+ name,
536
+ source: { bytes: Buffer.from(source.data, 'utf8').toString('base64') }
537
+ };
538
+ }
539
+ throw new Error(`Unsupported document source type: ${source.type}`);
540
+ }
541
+ // 标准化图像格式
542
+ function normalizeImageFormat(format) {
543
+ const lower = format.toLowerCase();
544
+ const formatMap = {
545
+ 'jpg': 'jpeg',
546
+ 'jpeg': 'jpeg',
547
+ 'png': 'png',
548
+ 'gif': 'gif',
549
+ 'webp': 'webp'
550
+ };
551
+ const normalized = formatMap[lower];
552
+ if (!normalized) {
553
+ throw new Error(`Unsupported image format: ${format}`);
554
+ }
555
+ return normalized;
556
+ }
557
+ function normalizeDocumentFormat(mediaType, name) {
558
+ const lowerMediaType = mediaType?.toLowerCase();
559
+ if (lowerMediaType === 'application/pdf')
560
+ return 'pdf';
561
+ if (lowerMediaType === 'text/markdown')
562
+ return 'md';
563
+ if (lowerMediaType === 'text/csv')
564
+ return 'csv';
565
+ if (lowerMediaType === 'text/html')
566
+ return 'html';
567
+ if (lowerMediaType?.startsWith('text/'))
568
+ return 'txt';
569
+ const extension = name.split('.').pop()?.toLowerCase();
570
+ if (extension === 'pdf')
571
+ return 'pdf';
572
+ if (extension === 'md' || extension === 'markdown')
573
+ return 'md';
574
+ if (extension === 'csv')
575
+ return 'csv';
576
+ if (extension === 'html' || extension === 'htm')
577
+ return 'html';
578
+ return 'txt';
579
+ }
580
+ // Kiro API 工具描述最大长度
581
+ const KIRO_MAX_TOOL_DESC_LEN = 10237; // 留出 "..." 的空间
582
+ function convertOpenAITools(tools, toolNameRegistry) {
583
+ if (!tools)
584
+ return [];
585
+ return tools.flatMap(tool => {
586
+ let description = tool.function.description || `Tool: ${tool.function.name}`;
587
+ // 截断过长的描述
588
+ if (description.length > KIRO_MAX_TOOL_DESC_LEN) {
589
+ description = description.substring(0, KIRO_MAX_TOOL_DESC_LEN) + '...';
590
+ }
591
+ const kiroTool = {
592
+ toolSpecification: {
593
+ name: shortenToolName(tool.function.name, toolNameRegistry),
594
+ description,
595
+ inputSchema: { json: tool.function.parameters }
596
+ }
597
+ };
598
+ const cachePoint = toKiroCachePoint(tool.cache_control);
599
+ return cachePoint ? [kiroTool, { cachePoint }] : [kiroTool];
600
+ });
601
+ }
602
+ function shortenToolName(name, toolNameRegistry) {
603
+ return toolNameRegistry.toKiroName(name);
604
+ }
605
+ // ============ Kiro -> OpenAI 转换 ============
606
+ function kiroToOpenaiResponse(content, toolUses, usage, model, toolNameRegistry = new toolNameRegistry_1.ToolNameRegistry(), reasoningContent) {
607
+ const restoredToolUses = toolNameRegistry.restoreToolUses(toolUses);
608
+ const openaiUsage = {
609
+ prompt_tokens: usage.inputTokens,
610
+ completion_tokens: usage.outputTokens,
611
+ total_tokens: usage.inputTokens + usage.outputTokens
612
+ };
613
+ if (usage.cacheReadTokens) {
614
+ openaiUsage.prompt_tokens_details = {
615
+ cached_tokens: usage.cacheReadTokens
616
+ };
617
+ }
618
+ if (usage.reasoningTokens) {
619
+ openaiUsage.completion_tokens_details = {
620
+ reasoning_tokens: usage.reasoningTokens
621
+ };
622
+ }
623
+ const response = {
624
+ id: `chatcmpl-${(0, uuid_1.v4)()}`,
625
+ object: 'chat.completion',
626
+ created: Math.floor(Date.now() / 1000),
627
+ model,
628
+ choices: [{
629
+ index: 0,
630
+ message: {
631
+ role: 'assistant',
632
+ content: (restoredToolUses.length > 0 || !content?.trim()) ? null : content,
633
+ ...(reasoningContent?.text ? { reasoning_content: reasoningContent.text } : {}),
634
+ tool_calls: restoredToolUses.length > 0 ? restoredToolUses.map(tu => ({
635
+ id: tu.toolUseId,
636
+ type: 'function',
637
+ function: {
638
+ name: tu.name,
639
+ arguments: JSON.stringify(tu.input)
640
+ }
641
+ })) : undefined
642
+ },
643
+ finish_reason: restoredToolUses.length > 0 ? 'tool_calls' : 'stop'
644
+ }],
645
+ usage: openaiUsage
646
+ };
647
+ return response;
648
+ }
649
+ function createOpenaiStreamChunk(id, model, delta, finishReason = null, usage) {
650
+ const chunk = {
651
+ id,
652
+ object: 'chat.completion.chunk',
653
+ created: Math.floor(Date.now() / 1000),
654
+ model,
655
+ choices: [{
656
+ index: 0,
657
+ delta: delta,
658
+ finish_reason: finishReason
659
+ }]
660
+ };
661
+ if (usage) {
662
+ chunk.usage = usage;
663
+ }
664
+ return chunk;
665
+ }
666
+ // ============ Claude -> Kiro 转换 ============
667
+ function claudeToKiro(request, profileArn, toolNameRegistry = new toolNameRegistry_1.ToolNameRegistry()) {
668
+ const modelId = (0, kiroApi_1.mapModelId)(request.model);
669
+ const origin = 'AI_EDITOR';
670
+ // 提取系统提示
671
+ let systemPrompt = '';
672
+ let systemCachePoint;
673
+ if (typeof request.system === 'string') {
674
+ systemPrompt = request.system;
675
+ }
676
+ else if (Array.isArray(request.system)) {
677
+ systemPrompt = request.system.map(b => {
678
+ systemCachePoint = mergeCachePoint(systemCachePoint, toKiroCachePoint(b.cache_control));
679
+ return b.text;
680
+ }).join('\n');
681
+ }
682
+ // 注入时间戳
683
+ const timestamp = new Date().toISOString();
684
+ systemPrompt = `[Context: Current time is ${timestamp}]\n\n${systemPrompt}`;
685
+ // 注入执行导向指令(防止 AI 在探索过程中丢失目标)
686
+ const executionDirective = `
687
+ <execution_discipline>
688
+ 当用户要求执行特定任务时,你必须遵循以下纪律:
689
+ 1. **目标锁定**:在整个会话中始终牢记用户的原始目标,不要在代码探索过程中迷失方向
690
+ 2. **行动优先**:优先执行任务而非仅分析或总结,除非用户明确只要求分析
691
+ 3. **计划执行**:为任务创建明确的步骤计划,逐步执行并标记完成状态
692
+ 4. **禁止确认性收尾**:在任务未完成前,禁止输出"需要我继续吗?"、"需要深入分析吗?"等确认性问题
693
+ 5. **持续推进**:如果发现部分任务已完成,立即继续执行剩余未完成的任务
694
+ 6. **完整交付**:直到所有任务步骤都执行完毕才算完成
695
+ </execution_discipline>
696
+ `;
697
+ systemPrompt = systemPrompt + '\n\n' + executionDirective;
698
+ // 构建历史消息 - Kiro API 要求严格的 user -> assistant 交替
699
+ const history = [];
700
+ let currentToolResults = []; // 只保存最后一条消息的 toolResults
701
+ let currentContent = '';
702
+ let currentCachePoint;
703
+ const images = [];
704
+ const documents = [];
705
+ // 临时存储,用于合并连续的同类型消息
706
+ let pendingUserContent = '';
707
+ let pendingUserImages = [];
708
+ let pendingUserDocuments = [];
709
+ let pendingToolResults = [];
710
+ let pendingUserCachePoint;
711
+ for (let i = 0; i < request.messages.length; i++) {
712
+ const msg = request.messages[i];
713
+ const isLast = i === request.messages.length - 1;
714
+ if (msg.role === 'user') {
715
+ const { content: userContent, images: userImages, documents: userDocuments, toolResults: userToolResults, cachePoint: userCachePoint } = extractClaudeContent(msg);
716
+ if (isLast) {
717
+ // 最后一条消息:合并之前的 pending 内容,toolResults 放入 currentMessage
718
+ currentContent = pendingUserContent ? pendingUserContent + '\n' + userContent : userContent;
719
+ images.push(...pendingUserImages, ...userImages);
720
+ documents.push(...pendingUserDocuments, ...userDocuments);
721
+ currentToolResults = [...pendingToolResults, ...userToolResults];
722
+ currentCachePoint = mergeCachePoint(pendingUserCachePoint, userCachePoint);
723
+ pendingUserContent = '';
724
+ pendingUserImages = [];
725
+ pendingUserDocuments = [];
726
+ pendingToolResults = [];
727
+ pendingUserCachePoint = undefined;
728
+ }
729
+ else {
730
+ // 非最后一条:检查下一条是否是 assistant
731
+ const nextMsg = request.messages[i + 1];
732
+ if (nextMsg && nextMsg.role === 'assistant') {
733
+ // 下一条是 assistant,可以安全添加到 history
734
+ const finalUserContent = pendingUserContent ? pendingUserContent + '\n' + userContent : userContent;
735
+ const finalUserImages = [...pendingUserImages, ...userImages];
736
+ const finalUserDocuments = [...pendingUserDocuments, ...userDocuments];
737
+ const finalToolResults = [...pendingToolResults, ...userToolResults];
738
+ const finalCachePoint = mergeCachePoint(pendingUserCachePoint, userCachePoint);
739
+ if (finalUserContent.trim() || finalUserImages.length > 0 || finalUserDocuments.length > 0 || finalToolResults.length > 0) {
740
+ const userInputMessage = {
741
+ content: finalUserContent || (finalToolResults.length > 0 ? 'Tool results provided.' : 'Continue'),
742
+ modelId,
743
+ origin,
744
+ images: finalUserImages.length > 0 ? finalUserImages : undefined,
745
+ documents: finalUserDocuments.length > 0 ? finalUserDocuments : undefined,
746
+ ...(finalCachePoint ? { cachePoint: finalCachePoint } : {})
747
+ };
748
+ // 如果有 toolResults,放入 userInputMessageContext
749
+ if (finalToolResults.length > 0) {
750
+ userInputMessage.userInputMessageContext = {
751
+ toolResults: finalToolResults
752
+ };
753
+ }
754
+ history.push({ userInputMessage });
755
+ }
756
+ pendingUserContent = '';
757
+ pendingUserImages = [];
758
+ pendingUserDocuments = [];
759
+ pendingToolResults = [];
760
+ pendingUserCachePoint = undefined;
761
+ }
762
+ else {
763
+ // 下一条不是 assistant(可能是连续 user 或结束),累积内容
764
+ pendingUserContent = pendingUserContent ? pendingUserContent + '\n' + userContent : userContent;
765
+ pendingUserImages.push(...userImages);
766
+ pendingUserDocuments.push(...userDocuments);
767
+ pendingToolResults.push(...userToolResults);
768
+ pendingUserCachePoint = mergeCachePoint(pendingUserCachePoint, userCachePoint);
769
+ }
770
+ }
771
+ }
772
+ else if (msg.role === 'assistant') {
773
+ // 注意: 故意丢弃 reasoningContent (history 中不传给 Kiro)
774
+ // Kiro 后端 schema 仅在响应输出中支持 assistantResponseMessage.reasoningContent,
775
+ // 在请求 history 中传入此字段会触发 400 "Improperly formed request"
776
+ // 当前消息的 thinking 开关由 additionalModelRequestFields.thinking 控制
777
+ const { content: assistantContent, toolUses } = extractClaudeAssistantContent(msg, toolNameRegistry);
778
+ // 如果有 pending 的 user 内容但还没添加到 history,先添加
779
+ if (pendingUserContent.trim() || pendingUserImages.length > 0 || pendingUserDocuments.length > 0 || pendingToolResults.length > 0) {
780
+ const userInputMessage = {
781
+ content: pendingUserContent || (pendingToolResults.length > 0 ? 'Tool results provided.' : 'Continue'),
782
+ modelId,
783
+ origin,
784
+ images: pendingUserImages.length > 0 ? pendingUserImages : undefined,
785
+ documents: pendingUserDocuments.length > 0 ? pendingUserDocuments : undefined,
786
+ ...(pendingUserCachePoint ? { cachePoint: pendingUserCachePoint } : {})
787
+ };
788
+ if (pendingToolResults.length > 0) {
789
+ userInputMessage.userInputMessageContext = {
790
+ toolResults: pendingToolResults
791
+ };
792
+ }
793
+ history.push({ userInputMessage });
794
+ pendingUserContent = '';
795
+ pendingUserImages = [];
796
+ pendingUserDocuments = [];
797
+ pendingToolResults = [];
798
+ pendingUserCachePoint = undefined;
799
+ }
800
+ const assistantResponseMessage = {
801
+ content: assistantContent,
802
+ ...(toolUses.length > 0 ? { toolUses } : {})
803
+ };
804
+ history.push({ assistantResponseMessage });
805
+ }
806
+ }
807
+ // 处理剩余的 pending 内容(如果最后几条都是 user 且不是 isLast)
808
+ if (pendingUserContent.trim() || pendingUserImages.length > 0 || pendingUserDocuments.length > 0 || pendingToolResults.length > 0) {
809
+ currentContent = pendingUserContent + (currentContent ? '\n' + currentContent : '');
810
+ images.unshift(...pendingUserImages);
811
+ documents.unshift(...pendingUserDocuments);
812
+ currentToolResults = [...pendingToolResults, ...currentToolResults];
813
+ currentCachePoint = mergeCachePoint(pendingUserCachePoint, currentCachePoint);
814
+ }
815
+ // 确保 history 以 user 开始(Kiro API 要求)
816
+ // 如果 history 以 assistant 开始,在前面插入一个空的 user 消息
817
+ if (history.length > 0 && history[0].assistantResponseMessage) {
818
+ history.unshift({
819
+ userInputMessage: {
820
+ content: 'Begin conversation',
821
+ modelId,
822
+ origin
823
+ }
824
+ });
825
+ }
826
+ // 构建最终内容
827
+ // System prompt 以 Kiro 官方方式注入:作为 Human/AI pair 插入到 history 头部
828
+ // 官方 Kiro IDE: [Human(systemPrompt, forcedRole), AI("I will follow these instructions.", forcedRole)]
829
+ if (systemPrompt) {
830
+ const systemMessages = [
831
+ {
832
+ userInputMessage: {
833
+ content: systemPrompt,
834
+ userInputMessageContext: {},
835
+ origin,
836
+ ...(systemCachePoint ? { cachePoint: systemCachePoint } : {})
837
+ }
838
+ },
839
+ {
840
+ assistantResponseMessage: {
841
+ content: 'I will follow these instructions.'
842
+ }
843
+ }
844
+ ];
845
+ history.unshift(...systemMessages);
846
+ }
847
+ const finalContent = currentContent || (currentToolResults.length > 0 ? 'Tool results provided.' : 'Continue');
848
+ // 转换工具定义
849
+ const kiroTools = convertClaudeTools(request.tools, toolNameRegistry);
850
+ // 将 Claude thinking 参数映射为 Kiro additionalModelRequestFields
851
+ // 仅对支持 thinking 的模型传递(Claude 4+ 系列)
852
+ // 非 Claude 模型(deepseek/minimax/glm 等)的 schema 没有 thinking 属性,传了会 400
853
+ let additionalModelRequestFields;
854
+ if (request.thinking && request.thinking.type !== 'disabled' && modelSupportsThinkingParam(modelId)) {
855
+ additionalModelRequestFields = { thinking: { type: 'adaptive' } };
856
+ }
857
+ return (0, kiroApi_1.buildKiroPayload)(finalContent, modelId, origin, history, kiroTools, currentToolResults, images, profileArn, {
858
+ maxTokens: request.max_tokens,
859
+ temperature: request.temperature,
860
+ topP: request.top_p
861
+ }, {
862
+ cachePoint: currentCachePoint,
863
+ documents,
864
+ conversationId: request.conversation_id,
865
+ context: request.kiro_context
866
+ }, additionalModelRequestFields);
867
+ }
868
+ function extractClaudeContent(msg) {
869
+ const images = [];
870
+ const documents = [];
871
+ const toolResults = [];
872
+ let content = '';
873
+ let cachePoint = toKiroCachePoint(msg.cache_control);
874
+ if (typeof msg.content === 'string') {
875
+ content = msg.content;
876
+ }
877
+ else if (Array.isArray(msg.content)) {
878
+ for (const block of msg.content) {
879
+ cachePoint = mergeCachePoint(cachePoint, toKiroCachePoint(block.cache_control));
880
+ if (block.type === 'text' && block.text) {
881
+ content += block.text;
882
+ }
883
+ else if (block.type === 'image' && block.source?.type === 'base64') {
884
+ const mediaTypeParts = block.source.media_type.split('/');
885
+ const imageFormat = mediaTypeParts[1];
886
+ if (mediaTypeParts[0] !== 'image' || !imageFormat) {
887
+ throw new Error(`Unsupported image media_type: ${block.source.media_type}`);
888
+ }
889
+ images.push({
890
+ format: normalizeImageFormat(imageFormat),
891
+ source: { bytes: block.source.data }
892
+ });
893
+ }
894
+ else if (block.type === 'document' && block.source) {
895
+ if (!block.name) {
896
+ throw new Error('document requires name');
897
+ }
898
+ documents.push(parseClaudeDocumentSource(block.source, block.name));
899
+ }
900
+ else if (block.type === 'tool_result' && block.tool_use_id) {
901
+ let resultContent = '';
902
+ // Kiro tool_result.content 只支持 text,但用户层 images 可以承载图片。
903
+ // 把内嵌 image block 提取到外层 images,避免「读取本地图片」这类场景图像内容被静默丢弃。
904
+ let extractedImageCount = 0;
905
+ if (typeof block.content === 'string') {
906
+ resultContent = block.content || '(empty)';
907
+ }
908
+ else if (Array.isArray(block.content)) {
909
+ const textParts = [];
910
+ for (const b of block.content) {
911
+ if (b.type === 'text') {
912
+ textParts.push(b.text || '');
913
+ }
914
+ else if (b.type === 'image' && b.source?.type === 'base64' && b.source.data) {
915
+ const mediaTypeParts = (b.source.media_type || '').split('/');
916
+ const imageFormat = mediaTypeParts[1];
917
+ if (mediaTypeParts[0] === 'image' && imageFormat) {
918
+ try {
919
+ images.push({
920
+ format: normalizeImageFormat(imageFormat),
921
+ source: { bytes: b.source.data }
922
+ });
923
+ extractedImageCount++;
924
+ }
925
+ catch {
926
+ // 不支持的格式:跳过但不抛错(保留旧行为,避免整轮失败)
927
+ }
928
+ }
929
+ }
930
+ }
931
+ resultContent = textParts.join('');
932
+ if (!resultContent) {
933
+ resultContent = extractedImageCount > 0
934
+ ? `(tool returned ${extractedImageCount} image${extractedImageCount > 1 ? 's' : ''}, attached to this message)`
935
+ : '(no text output)';
936
+ }
937
+ else if (extractedImageCount > 0) {
938
+ // 既有文本又有图片:在文本末尾提示模型有附图
939
+ resultContent += `\n\n[Tool also returned ${extractedImageCount} image${extractedImageCount > 1 ? 's' : ''}, attached to this message]`;
940
+ }
941
+ }
942
+ else if (block.content === undefined || block.content === null) {
943
+ resultContent = '(no output)';
944
+ }
945
+ else {
946
+ resultContent = String(block.content) || '(empty)';
947
+ }
948
+ toolResults.push({
949
+ toolUseId: block.tool_use_id,
950
+ content: [{ text: resultContent }],
951
+ status: 'success'
952
+ });
953
+ }
954
+ }
955
+ }
956
+ return { content, images, documents, toolResults, cachePoint };
957
+ }
958
+ function extractClaudeAssistantContent(msg, toolNameRegistry) {
959
+ const toolUses = [];
960
+ let content = '';
961
+ let thinking = '';
962
+ let signature;
963
+ let redactedContent;
964
+ if (typeof msg.content === 'string') {
965
+ content = msg.content;
966
+ }
967
+ else if (Array.isArray(msg.content)) {
968
+ for (const block of msg.content) {
969
+ if (block.type === 'text' && block.text) {
970
+ content += block.text;
971
+ }
972
+ else if (block.type === 'thinking' && block.thinking) {
973
+ thinking += block.thinking;
974
+ signature = block.signature || signature;
975
+ }
976
+ else if (block.type === 'redacted_thinking' && block.data) {
977
+ // redacted_thinking 是加密的思考内容,原样保留
978
+ redactedContent = (redactedContent || '') + block.data;
979
+ }
980
+ else if (block.type === 'tool_use' && block.id && block.name) {
981
+ if (!block.input || typeof block.input !== 'object' || Array.isArray(block.input)) {
982
+ throw new Error(`tool_use requires object input: ${block.name}`);
983
+ }
984
+ toolUses.push({
985
+ toolUseId: block.id,
986
+ name: toolNameRegistry.toKiroName(block.name),
987
+ input: block.input
988
+ });
989
+ }
990
+ }
991
+ }
992
+ // Kiro API 要求 content 非空
993
+ if (!content.trim() && toolUses.length > 0) {
994
+ content = ' ';
995
+ }
996
+ if (thinking || redactedContent) {
997
+ const reasoningContent = {};
998
+ if (thinking) {
999
+ reasoningContent.reasoningText = signature ? { text: thinking, signature } : { text: thinking };
1000
+ }
1001
+ if (redactedContent) {
1002
+ reasoningContent.redactedContent = redactedContent;
1003
+ }
1004
+ return { content, toolUses, reasoningContent };
1005
+ }
1006
+ return { content, toolUses };
1007
+ }
1008
+ function convertClaudeTools(tools, toolNameRegistry) {
1009
+ if (!tools)
1010
+ return [];
1011
+ return tools.flatMap(tool => {
1012
+ let description = tool.description || `Tool: ${tool.name}`;
1013
+ // 截断过长的描述
1014
+ if (description.length > KIRO_MAX_TOOL_DESC_LEN) {
1015
+ description = description.substring(0, KIRO_MAX_TOOL_DESC_LEN) + '...';
1016
+ }
1017
+ const kiroTool = {
1018
+ toolSpecification: {
1019
+ name: shortenToolName(tool.name, toolNameRegistry),
1020
+ description,
1021
+ inputSchema: { json: tool.input_schema }
1022
+ }
1023
+ };
1024
+ const cachePoint = toKiroCachePoint(tool.cache_control);
1025
+ return cachePoint ? [kiroTool, { cachePoint }] : [kiroTool];
1026
+ });
1027
+ }
1028
+ // ============ Kiro -> Claude 转换 ============
1029
+ function kiroToClaudeResponse(content, toolUses, usage, model, toolNameRegistry = new toolNameRegistry_1.ToolNameRegistry(), reasoningContent) {
1030
+ const contentBlocks = [];
1031
+ const restoredToolUses = toolNameRegistry.restoreToolUses(toolUses);
1032
+ if (reasoningContent?.text) {
1033
+ contentBlocks.push(reasoningContent.signature ? {
1034
+ type: 'thinking',
1035
+ thinking: reasoningContent.text,
1036
+ signature: reasoningContent.signature
1037
+ } : {
1038
+ type: 'thinking',
1039
+ thinking: reasoningContent.text
1040
+ });
1041
+ }
1042
+ if (reasoningContent?.redactedContent) {
1043
+ contentBlocks.push({
1044
+ type: 'redacted_thinking',
1045
+ data: reasoningContent.redactedContent
1046
+ });
1047
+ }
1048
+ // 仅在有实际文本内容时添加 text block
1049
+ if (content && content.trim()) {
1050
+ contentBlocks.push({ type: 'text', text: content });
1051
+ }
1052
+ for (const tu of restoredToolUses) {
1053
+ contentBlocks.push({
1054
+ type: 'tool_use',
1055
+ id: tu.toolUseId,
1056
+ name: tu.name,
1057
+ input: tu.input
1058
+ });
1059
+ }
1060
+ const claudeUsage = {
1061
+ input_tokens: usage.inputTokens,
1062
+ output_tokens: usage.outputTokens
1063
+ };
1064
+ if (usage.cacheWriteTokens) {
1065
+ claudeUsage.cache_creation_input_tokens = usage.cacheWriteTokens;
1066
+ }
1067
+ if (usage.cacheReadTokens) {
1068
+ claudeUsage.cache_read_input_tokens = usage.cacheReadTokens;
1069
+ }
1070
+ const response = {
1071
+ id: `msg_${(0, uuid_1.v4)()}`,
1072
+ type: 'message',
1073
+ role: 'assistant',
1074
+ content: contentBlocks,
1075
+ model,
1076
+ stop_reason: restoredToolUses.length > 0 ? 'tool_use' : 'end_turn',
1077
+ stop_sequence: null,
1078
+ usage: claudeUsage
1079
+ };
1080
+ return response;
1081
+ }
1082
+ function createClaudeStreamEvent(type, data) {
1083
+ return { type, ...data };
1084
+ }