aicodeswitch 4.0.3 → 5.0.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.
- package/README.md +7 -6
- package/UPGRADE.md +5 -6
- package/dist/server/coding-plan.js +94 -0
- package/dist/server/config-managed-fields.js +1 -0
- package/dist/server/conversions/compact.js +613 -0
- package/dist/server/conversions/detector.js +70 -0
- package/dist/server/conversions/index.js +285 -0
- package/dist/server/conversions/pairs/claude-completions/request.js +167 -0
- package/dist/server/conversions/pairs/claude-completions/response.js +56 -0
- package/dist/server/conversions/pairs/claude-completions/streaming.js +259 -0
- package/dist/server/conversions/pairs/claude-gemini/request.js +130 -0
- package/dist/server/conversions/pairs/claude-gemini/response.js +65 -0
- package/dist/server/conversions/pairs/claude-gemini/streaming.js +199 -0
- package/dist/server/conversions/pairs/claude-responses/request.js +190 -0
- package/dist/server/conversions/pairs/claude-responses/response.js +89 -0
- package/dist/server/conversions/pairs/claude-responses/streaming.js +266 -0
- package/dist/server/conversions/pairs/completions-claude/request.js +111 -0
- package/dist/server/conversions/pairs/completions-claude/response.js +67 -0
- package/dist/server/conversions/pairs/completions-claude/streaming.js +165 -0
- package/dist/server/conversions/pairs/completions-gemini/request.js +169 -0
- package/dist/server/conversions/pairs/completions-gemini/response.js +70 -0
- package/dist/server/conversions/pairs/completions-gemini/streaming.js +132 -0
- package/dist/server/conversions/pairs/completions-responses/request.js +149 -0
- package/dist/server/conversions/pairs/completions-responses/response.js +74 -0
- package/dist/server/conversions/pairs/completions-responses/streaming.js +189 -0
- package/dist/server/conversions/pairs/gemini-claude/request.js +118 -0
- package/dist/server/conversions/pairs/gemini-claude/response.js +45 -0
- package/dist/server/conversions/pairs/gemini-claude/streaming.js +146 -0
- package/dist/server/conversions/pairs/gemini-completions/request.js +151 -0
- package/dist/server/conversions/pairs/gemini-completions/response.js +54 -0
- package/dist/server/conversions/pairs/gemini-completions/streaming.js +108 -0
- package/dist/server/conversions/pairs/gemini-responses/request.js +18 -0
- package/dist/server/conversions/pairs/gemini-responses/response.js +18 -0
- package/dist/server/conversions/pairs/gemini-responses/streaming.js +43 -0
- package/dist/server/conversions/pairs/responses-claude/request.js +155 -0
- package/dist/server/conversions/pairs/responses-claude/response.js +70 -0
- package/dist/server/conversions/pairs/responses-claude/streaming.js +345 -0
- package/dist/server/conversions/pairs/responses-completions/request.js +207 -0
- package/dist/server/conversions/pairs/responses-completions/response.js +96 -0
- package/dist/server/conversions/pairs/responses-completions/streaming.js +344 -0
- package/dist/server/conversions/pairs/responses-gemini/request.js +18 -0
- package/dist/server/conversions/pairs/responses-gemini/response.js +18 -0
- package/dist/server/conversions/pairs/responses-gemini/streaming.js +43 -0
- package/dist/server/conversions/pairs/responses-responses/request.js +115 -0
- package/dist/server/conversions/pipeline.js +296 -0
- package/dist/server/conversions/stream-converter-adapter.js +49 -0
- package/dist/server/conversions/thinking/effort.js +61 -0
- package/dist/server/conversions/thinking/mapper.js +59 -0
- package/dist/server/conversions/thinking/providers.js +76 -0
- package/dist/server/conversions/types.js +5 -0
- package/dist/server/conversions/url-normalizer.js +58 -0
- package/dist/server/conversions/utils/format-mappers.js +57 -0
- package/dist/server/conversions/utils/id.js +33 -0
- package/dist/server/conversions/utils/stop-reasons.js +95 -0
- package/dist/server/conversions/utils/streaming-helpers.js +59 -0
- package/dist/server/conversions/utils/tool-schema.js +169 -0
- package/dist/server/conversions/utils/usage.js +82 -0
- package/dist/server/fs-database.js +465 -135
- package/dist/server/main.js +93 -33
- package/dist/server/original-config-reader.js +1 -1
- package/dist/server/proxy-server.js +1102 -804
- package/dist/server/transformers/chunk-collector.js +5 -1
- package/dist/server/transformers/streaming.js +6 -3235
- package/dist/server/type-migration.js +2 -3
- package/dist/server/utils.js +5 -0
- package/dist/ui/assets/{index-C7G0whng.css → index-BHR12ImE.css} +1 -1
- package/dist/ui/assets/index-DjdBW1yu.js +517 -0
- package/dist/ui/index.html +2 -2
- package/package.json +1 -1
- package/dist/server/transformers/transformers.js +0 -1767
- package/dist/ui/assets/index-Nl6yJxrc.js +0 -514
- package/schema/claude.schema.md +0 -946
- package/schema/deepseek-chat.schema.md +0 -799
- package/schema/gemini.schema.md +0 -1408
- package/schema/openai-chat-completions.schema.md +0 -1088
- package/schema/openai-responses.schema.md +0 -226196
- package/schema/stream.md +0 -2592
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Claude Messages → OpenAI Responses API request conversion.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.claudeToResponses = claudeToResponses;
|
|
7
|
+
const effort_js_1 = require("../../thinking/effort.js");
|
|
8
|
+
const tool_schema_js_1 = require("../../utils/tool-schema.js");
|
|
9
|
+
/**
|
|
10
|
+
* Convert a Claude Messages request body to an OpenAI Responses API request body.
|
|
11
|
+
*/
|
|
12
|
+
function claudeToResponses(body) {
|
|
13
|
+
var _a;
|
|
14
|
+
const input = [];
|
|
15
|
+
// --- System prompt -> instructions ---
|
|
16
|
+
let instructions;
|
|
17
|
+
if (body.system) {
|
|
18
|
+
instructions = typeof body.system === 'string'
|
|
19
|
+
? body.system
|
|
20
|
+
: Array.isArray(body.system)
|
|
21
|
+
? body.system
|
|
22
|
+
.filter((s) => s.type === 'text' || typeof s.text === 'string')
|
|
23
|
+
.map((s) => s.text || '')
|
|
24
|
+
.join('\n')
|
|
25
|
+
: undefined;
|
|
26
|
+
// Strip any x-anthropic-billing-header prefixes
|
|
27
|
+
if (instructions) {
|
|
28
|
+
instructions = instructions.replace(/^x-anthropic-billing-header[^\n]*\n?/gi, '');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// --- Messages -> input array ---
|
|
32
|
+
if (body.messages) {
|
|
33
|
+
const buffer = new MessageBuffer();
|
|
34
|
+
for (const msg of body.messages) {
|
|
35
|
+
buffer.setCurrentRole(msg.role);
|
|
36
|
+
const content = msg.content;
|
|
37
|
+
if (typeof content === 'string') {
|
|
38
|
+
buffer.addText(content);
|
|
39
|
+
}
|
|
40
|
+
else if (Array.isArray(content)) {
|
|
41
|
+
for (const block of content) {
|
|
42
|
+
if (block.type === 'text') {
|
|
43
|
+
buffer.addText(block.text);
|
|
44
|
+
}
|
|
45
|
+
else if (block.type === 'image') {
|
|
46
|
+
buffer.addImage(block);
|
|
47
|
+
}
|
|
48
|
+
else if (block.type === 'tool_use') {
|
|
49
|
+
// Flush buffered content first, then emit standalone function_call
|
|
50
|
+
buffer.flush(input);
|
|
51
|
+
input.push({
|
|
52
|
+
type: 'function_call',
|
|
53
|
+
call_id: block.id,
|
|
54
|
+
name: block.name,
|
|
55
|
+
arguments: typeof block.input === 'string' ? block.input : JSON.stringify(block.input),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
else if (block.type === 'tool_result') {
|
|
59
|
+
// Flush buffered content first, then emit standalone function_call_output
|
|
60
|
+
buffer.flush(input);
|
|
61
|
+
const outputContent = typeof block.content === 'string'
|
|
62
|
+
? block.content
|
|
63
|
+
: JSON.stringify(block.content);
|
|
64
|
+
input.push({
|
|
65
|
+
type: 'function_call_output',
|
|
66
|
+
call_id: block.tool_use_id,
|
|
67
|
+
output: outputContent,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
else if (block.type === 'thinking' || block.type === 'redacted_thinking') {
|
|
71
|
+
// Skip thinking blocks
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Flush any remaining buffered content
|
|
78
|
+
buffer.flush(input);
|
|
79
|
+
}
|
|
80
|
+
// --- Build result ---
|
|
81
|
+
const result = {
|
|
82
|
+
model: body.model,
|
|
83
|
+
input,
|
|
84
|
+
stream: (_a = body.stream) !== null && _a !== void 0 ? _a : false,
|
|
85
|
+
};
|
|
86
|
+
if (instructions)
|
|
87
|
+
result.instructions = instructions;
|
|
88
|
+
if (body.max_tokens)
|
|
89
|
+
result.max_output_tokens = body.max_tokens;
|
|
90
|
+
if (body.temperature !== undefined)
|
|
91
|
+
result.temperature = body.temperature;
|
|
92
|
+
if (body.top_p !== undefined)
|
|
93
|
+
result.top_p = body.top_p;
|
|
94
|
+
// --- Thinking -> reasoning ---
|
|
95
|
+
if (body.thinking) {
|
|
96
|
+
const reasoning = (0, effort_js_1.claudeThinkingToResponsesReasoning)(body.thinking);
|
|
97
|
+
if (reasoning) {
|
|
98
|
+
result.reasoning = reasoning;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// --- Tools ---
|
|
102
|
+
if (body.tools && body.tools.length > 0) {
|
|
103
|
+
result.tools = (0, tool_schema_js_1.claudeToResponsesTools)(body.tools);
|
|
104
|
+
}
|
|
105
|
+
// --- Tool choice mapping ---
|
|
106
|
+
if (body.tool_choice) {
|
|
107
|
+
result.tool_choice = mapClaudeToolChoice(body.tool_choice);
|
|
108
|
+
}
|
|
109
|
+
// Note: stop_sequences are dropped (not supported in Responses API)
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// Helpers
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
/**
|
|
116
|
+
* Buffer for accumulating text/image content into a single message before
|
|
117
|
+
* flushing when encountering standalone items like function_call / function_call_output.
|
|
118
|
+
*/
|
|
119
|
+
class MessageBuffer {
|
|
120
|
+
constructor() {
|
|
121
|
+
Object.defineProperty(this, "currentRole", {
|
|
122
|
+
enumerable: true,
|
|
123
|
+
configurable: true,
|
|
124
|
+
writable: true,
|
|
125
|
+
value: null
|
|
126
|
+
});
|
|
127
|
+
Object.defineProperty(this, "content", {
|
|
128
|
+
enumerable: true,
|
|
129
|
+
configurable: true,
|
|
130
|
+
writable: true,
|
|
131
|
+
value: []
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
setCurrentRole(role) {
|
|
135
|
+
// If role changed, flush what we have
|
|
136
|
+
if (this.currentRole !== null && this.currentRole !== role && this.content.length > 0) {
|
|
137
|
+
// Role changed — caller should flush first, but for safety we just track
|
|
138
|
+
}
|
|
139
|
+
this.currentRole = role;
|
|
140
|
+
}
|
|
141
|
+
addText(text) {
|
|
142
|
+
if (!this.currentRole)
|
|
143
|
+
return;
|
|
144
|
+
const textType = this.currentRole === 'assistant' ? 'output_text' : 'input_text';
|
|
145
|
+
this.content.push({ type: textType, text });
|
|
146
|
+
}
|
|
147
|
+
addImage(block) {
|
|
148
|
+
var _a, _b;
|
|
149
|
+
if (!this.currentRole)
|
|
150
|
+
return;
|
|
151
|
+
this.content.push({
|
|
152
|
+
type: 'input_image',
|
|
153
|
+
image_url: `data:${((_a = block.source) === null || _a === void 0 ? void 0 : _a.media_type) || block.media_type || 'image/png'};base64,${((_b = block.source) === null || _b === void 0 ? void 0 : _b.data) || block.data}`,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
flush(target) {
|
|
157
|
+
if (this.content.length === 0 || !this.currentRole) {
|
|
158
|
+
this.content = [];
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const role = this.currentRole;
|
|
162
|
+
target.push({ role, content: this.content });
|
|
163
|
+
this.content = [];
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Map Claude tool_choice to Responses API tool_choice.
|
|
168
|
+
*/
|
|
169
|
+
function mapClaudeToolChoice(toolChoice) {
|
|
170
|
+
if (!toolChoice)
|
|
171
|
+
return undefined;
|
|
172
|
+
if (typeof toolChoice === 'string') {
|
|
173
|
+
switch (toolChoice) {
|
|
174
|
+
case 'any': return 'required';
|
|
175
|
+
case 'auto': return 'auto';
|
|
176
|
+
case 'none': return 'none';
|
|
177
|
+
default: return 'auto';
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (toolChoice.type === 'any')
|
|
181
|
+
return 'required';
|
|
182
|
+
if (toolChoice.type === 'auto')
|
|
183
|
+
return 'auto';
|
|
184
|
+
if (toolChoice.type === 'none')
|
|
185
|
+
return 'none';
|
|
186
|
+
if (toolChoice.type === 'tool') {
|
|
187
|
+
return { type: 'function', name: toolChoice.name };
|
|
188
|
+
}
|
|
189
|
+
return 'auto';
|
|
190
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* OpenAI Responses API → Claude Messages response conversion.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.responsesToClaudeResponse = responsesToClaudeResponse;
|
|
7
|
+
exports.claudeToResponsesUsage = claudeToResponsesUsage;
|
|
8
|
+
const id_js_1 = require("../../utils/id.js");
|
|
9
|
+
const stop_reasons_js_1 = require("../../utils/stop-reasons.js");
|
|
10
|
+
const usage_js_1 = require("../../utils/usage.js");
|
|
11
|
+
const mapper_js_1 = require("../../thinking/mapper.js");
|
|
12
|
+
/**
|
|
13
|
+
* Convert an OpenAI Responses API response to a Claude Messages response.
|
|
14
|
+
*/
|
|
15
|
+
function responsesToClaudeResponse(response) {
|
|
16
|
+
var _a;
|
|
17
|
+
const output = response.output || [];
|
|
18
|
+
const content = [];
|
|
19
|
+
for (const item of output) {
|
|
20
|
+
if (item.type === 'message') {
|
|
21
|
+
// Message with text / refusal content
|
|
22
|
+
if (Array.isArray(item.content)) {
|
|
23
|
+
for (const part of item.content) {
|
|
24
|
+
if (part.type === 'output_text') {
|
|
25
|
+
content.push({ type: 'text', text: part.text || '' });
|
|
26
|
+
}
|
|
27
|
+
else if (part.type === 'refusal') {
|
|
28
|
+
content.push({ type: 'text', text: part.refusal || '' });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
else if (item.type === 'function_call') {
|
|
34
|
+
let parsedInput;
|
|
35
|
+
try {
|
|
36
|
+
parsedInput = typeof item.arguments === 'string' ? JSON.parse(item.arguments) : item.arguments;
|
|
37
|
+
}
|
|
38
|
+
catch (_b) {
|
|
39
|
+
parsedInput = {};
|
|
40
|
+
}
|
|
41
|
+
content.push({
|
|
42
|
+
type: 'tool_use',
|
|
43
|
+
id: item.call_id || (0, id_js_1.generateCallId)(),
|
|
44
|
+
name: item.name,
|
|
45
|
+
input: parsedInput,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
else if (item.type === 'reasoning') {
|
|
49
|
+
// Reasoning with summary -> thinking block
|
|
50
|
+
if (Array.isArray(item.summary) && item.summary.length > 0) {
|
|
51
|
+
const thinkingBlock = (0, mapper_js_1.reasoningToThinking)(item.summary);
|
|
52
|
+
if (thinkingBlock.thinking) {
|
|
53
|
+
content.push(thinkingBlock);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const hasToolUse = content.some((b) => b.type === 'tool_use');
|
|
59
|
+
const stopReason = (0, stop_reasons_js_1.responsesToClaudeStopReason)(response.status, (_a = response.incomplete_details) === null || _a === void 0 ? void 0 : _a.reason, hasToolUse);
|
|
60
|
+
const usage = (0, usage_js_1.responsesToClaudeUsage)(response.usage);
|
|
61
|
+
return {
|
|
62
|
+
id: response.id || (0, id_js_1.generateMessageId)(),
|
|
63
|
+
type: 'message',
|
|
64
|
+
role: 'assistant',
|
|
65
|
+
content,
|
|
66
|
+
model: response.model || '',
|
|
67
|
+
stop_reason: stopReason,
|
|
68
|
+
stop_sequence: null,
|
|
69
|
+
usage,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// Helpers
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
/**
|
|
76
|
+
* Map Claude usage to Responses API usage.
|
|
77
|
+
*/
|
|
78
|
+
function claudeToResponsesUsage(usage) {
|
|
79
|
+
var _a, _b;
|
|
80
|
+
if (!usage)
|
|
81
|
+
return { input_tokens: 0, output_tokens: 0, total_tokens: 0 };
|
|
82
|
+
const input = (_a = usage.input_tokens) !== null && _a !== void 0 ? _a : 0;
|
|
83
|
+
const output = (_b = usage.output_tokens) !== null && _b !== void 0 ? _b : 0;
|
|
84
|
+
return {
|
|
85
|
+
input_tokens: input,
|
|
86
|
+
output_tokens: output,
|
|
87
|
+
total_tokens: input + output,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* OpenAI Responses API SSE → Claude Messages SSE streaming conversion.
|
|
4
|
+
*
|
|
5
|
+
* Stateful converter that processes SSE events from an OpenAI Responses API
|
|
6
|
+
* upstream and emits Claude Messages SSE events.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.ResponsesToClaudeConverter = void 0;
|
|
10
|
+
const id_js_1 = require("../../utils/id.js");
|
|
11
|
+
const stop_reasons_js_1 = require("../../utils/stop-reasons.js");
|
|
12
|
+
const usage_js_1 = require("../../utils/usage.js");
|
|
13
|
+
const streaming_helpers_js_1 = require("../../utils/streaming-helpers.js");
|
|
14
|
+
/**
|
|
15
|
+
* Stateful converter: Responses API SSE → Claude Messages SSE.
|
|
16
|
+
*/
|
|
17
|
+
class ResponsesToClaudeConverter {
|
|
18
|
+
constructor() {
|
|
19
|
+
Object.defineProperty(this, "started", {
|
|
20
|
+
enumerable: true,
|
|
21
|
+
configurable: true,
|
|
22
|
+
writable: true,
|
|
23
|
+
value: false
|
|
24
|
+
});
|
|
25
|
+
Object.defineProperty(this, "messageStopped", {
|
|
26
|
+
enumerable: true,
|
|
27
|
+
configurable: true,
|
|
28
|
+
writable: true,
|
|
29
|
+
value: false
|
|
30
|
+
});
|
|
31
|
+
Object.defineProperty(this, "currentBlockType", {
|
|
32
|
+
enumerable: true,
|
|
33
|
+
configurable: true,
|
|
34
|
+
writable: true,
|
|
35
|
+
value: null
|
|
36
|
+
});
|
|
37
|
+
Object.defineProperty(this, "currentBlockIndex", {
|
|
38
|
+
enumerable: true,
|
|
39
|
+
configurable: true,
|
|
40
|
+
writable: true,
|
|
41
|
+
value: -1
|
|
42
|
+
});
|
|
43
|
+
Object.defineProperty(this, "nextBlockIndex", {
|
|
44
|
+
enumerable: true,
|
|
45
|
+
configurable: true,
|
|
46
|
+
writable: true,
|
|
47
|
+
value: 0
|
|
48
|
+
});
|
|
49
|
+
Object.defineProperty(this, "messageId", {
|
|
50
|
+
enumerable: true,
|
|
51
|
+
configurable: true,
|
|
52
|
+
writable: true,
|
|
53
|
+
value: (0, id_js_1.generateMessageId)()
|
|
54
|
+
});
|
|
55
|
+
Object.defineProperty(this, "model", {
|
|
56
|
+
enumerable: true,
|
|
57
|
+
configurable: true,
|
|
58
|
+
writable: true,
|
|
59
|
+
value: ''
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
convertEvent(event) {
|
|
63
|
+
var _a;
|
|
64
|
+
if (!event.data)
|
|
65
|
+
return [];
|
|
66
|
+
const eventName = event.event;
|
|
67
|
+
const events = [];
|
|
68
|
+
try {
|
|
69
|
+
const data = (0, streaming_helpers_js_1.parseEventData)(event.data);
|
|
70
|
+
switch (eventName) {
|
|
71
|
+
case 'response.created': {
|
|
72
|
+
this.model = data.model || '';
|
|
73
|
+
this.started = false;
|
|
74
|
+
this.messageStopped = false;
|
|
75
|
+
// Will emit message_start when we get actual content
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
case 'response.output_item.added': {
|
|
79
|
+
const item = data.item || data;
|
|
80
|
+
if (item.type === 'function_call') {
|
|
81
|
+
this.ensureStarted(events, data.model);
|
|
82
|
+
this.closeCurrentBlock(events);
|
|
83
|
+
// Open tool_use block
|
|
84
|
+
const idx = this.nextBlockIndex++;
|
|
85
|
+
events.push(this.makeSSE('content_block_start', {
|
|
86
|
+
type: 'content_block_start',
|
|
87
|
+
index: idx,
|
|
88
|
+
content_block: {
|
|
89
|
+
type: 'tool_use',
|
|
90
|
+
id: item.call_id || (0, id_js_1.generateCallId)(),
|
|
91
|
+
name: item.name || '',
|
|
92
|
+
input: {},
|
|
93
|
+
},
|
|
94
|
+
}));
|
|
95
|
+
this.currentBlockIndex = idx;
|
|
96
|
+
this.currentBlockType = null; // tool_use is not tracked as a "block type" for closing
|
|
97
|
+
}
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
case 'response.content_part.added': {
|
|
101
|
+
const part = data.part || data;
|
|
102
|
+
if (part.type === 'output_text') {
|
|
103
|
+
this.ensureStarted(events, data.model);
|
|
104
|
+
if (this.currentBlockType !== 'text') {
|
|
105
|
+
this.closeCurrentBlock(events);
|
|
106
|
+
this.openTextBlock(events);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
case 'response.output_text.delta': {
|
|
112
|
+
this.ensureStarted(events, data.model);
|
|
113
|
+
if (this.currentBlockType !== 'text') {
|
|
114
|
+
this.closeCurrentBlock(events);
|
|
115
|
+
this.openTextBlock(events);
|
|
116
|
+
}
|
|
117
|
+
const delta = data.delta || '';
|
|
118
|
+
if (delta) {
|
|
119
|
+
events.push(this.makeSSE('content_block_delta', {
|
|
120
|
+
type: 'content_block_delta',
|
|
121
|
+
index: this.currentBlockIndex,
|
|
122
|
+
delta: { type: 'text_delta', text: delta },
|
|
123
|
+
}));
|
|
124
|
+
}
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
case 'response.output_text.done': {
|
|
128
|
+
this.closeCurrentBlock(events);
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
case 'response.reasoning.delta':
|
|
132
|
+
case 'response.reasoning_summary_text.delta': {
|
|
133
|
+
this.ensureStarted(events, data.model);
|
|
134
|
+
const text = data.delta || data.text || '';
|
|
135
|
+
if (!text)
|
|
136
|
+
break;
|
|
137
|
+
if (this.currentBlockType !== 'thinking') {
|
|
138
|
+
this.closeCurrentBlock(events);
|
|
139
|
+
this.openThinkingBlock(events);
|
|
140
|
+
}
|
|
141
|
+
events.push(this.makeSSE('content_block_delta', {
|
|
142
|
+
type: 'content_block_delta',
|
|
143
|
+
index: this.currentBlockIndex,
|
|
144
|
+
delta: { type: 'thinking_delta', thinking: text },
|
|
145
|
+
}));
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
case 'response.reasoning.done': {
|
|
149
|
+
this.closeCurrentBlock(events);
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
case 'response.function_call_arguments.delta': {
|
|
153
|
+
const argDelta = data.delta || '';
|
|
154
|
+
if (argDelta) {
|
|
155
|
+
events.push(this.makeSSE('content_block_delta', {
|
|
156
|
+
type: 'content_block_delta',
|
|
157
|
+
index: this.currentBlockIndex,
|
|
158
|
+
delta: { type: 'input_json_delta', partial_json: argDelta },
|
|
159
|
+
}));
|
|
160
|
+
}
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
case 'response.function_call_arguments.done': {
|
|
164
|
+
// Close the tool_use block
|
|
165
|
+
if (this.currentBlockIndex >= 0 && this.currentBlockType === null) {
|
|
166
|
+
events.push(this.makeSSE('content_block_stop', {
|
|
167
|
+
type: 'content_block_stop',
|
|
168
|
+
index: this.currentBlockIndex,
|
|
169
|
+
}));
|
|
170
|
+
this.currentBlockIndex = -1;
|
|
171
|
+
this.currentBlockType = null;
|
|
172
|
+
}
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
case 'response.completed': {
|
|
176
|
+
this.closeCurrentBlock(events);
|
|
177
|
+
if (this.started && !this.messageStopped) {
|
|
178
|
+
const responseData = data.response || data;
|
|
179
|
+
const stopReason = (0, stop_reasons_js_1.responsesToClaudeStopReason)(responseData.status, (_a = responseData.incomplete_details) === null || _a === void 0 ? void 0 : _a.reason, true);
|
|
180
|
+
const usage = (0, usage_js_1.responsesToClaudeUsage)(responseData.usage);
|
|
181
|
+
events.push(this.makeSSE('message_delta', {
|
|
182
|
+
type: 'message_delta',
|
|
183
|
+
delta: { stop_reason: stopReason, stop_sequence: null },
|
|
184
|
+
usage,
|
|
185
|
+
}));
|
|
186
|
+
events.push(this.makeSSE('message_stop', {
|
|
187
|
+
type: 'message_stop',
|
|
188
|
+
}));
|
|
189
|
+
this.messageStopped = true;
|
|
190
|
+
}
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
// Skip these events
|
|
194
|
+
case 'response.in_progress':
|
|
195
|
+
case 'response.output_item.done':
|
|
196
|
+
case 'response.content_part.done':
|
|
197
|
+
case 'response.reasoning_summary_part.added':
|
|
198
|
+
case 'response.reasoning_summary_part.done':
|
|
199
|
+
break;
|
|
200
|
+
default:
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
catch (_b) {
|
|
205
|
+
// Ignore parse errors
|
|
206
|
+
}
|
|
207
|
+
return events;
|
|
208
|
+
}
|
|
209
|
+
ensureStarted(events, model) {
|
|
210
|
+
if (this.started)
|
|
211
|
+
return;
|
|
212
|
+
if (model)
|
|
213
|
+
this.model = model;
|
|
214
|
+
events.push(this.makeSSE('message_start', {
|
|
215
|
+
type: 'message_start',
|
|
216
|
+
message: {
|
|
217
|
+
id: this.messageId,
|
|
218
|
+
type: 'message',
|
|
219
|
+
role: 'assistant',
|
|
220
|
+
content: [],
|
|
221
|
+
model: this.model,
|
|
222
|
+
stop_reason: null,
|
|
223
|
+
stop_sequence: null,
|
|
224
|
+
usage: { input_tokens: 0, output_tokens: 0 },
|
|
225
|
+
},
|
|
226
|
+
}));
|
|
227
|
+
this.started = true;
|
|
228
|
+
}
|
|
229
|
+
openTextBlock(events) {
|
|
230
|
+
const idx = this.nextBlockIndex++;
|
|
231
|
+
events.push(this.makeSSE('content_block_start', {
|
|
232
|
+
type: 'content_block_start',
|
|
233
|
+
index: idx,
|
|
234
|
+
content_block: { type: 'text', text: '' },
|
|
235
|
+
}));
|
|
236
|
+
this.currentBlockIndex = idx;
|
|
237
|
+
this.currentBlockType = 'text';
|
|
238
|
+
}
|
|
239
|
+
openThinkingBlock(events) {
|
|
240
|
+
const idx = this.nextBlockIndex++;
|
|
241
|
+
events.push(this.makeSSE('content_block_start', {
|
|
242
|
+
type: 'content_block_start',
|
|
243
|
+
index: idx,
|
|
244
|
+
content_block: { type: 'thinking', thinking: '' },
|
|
245
|
+
}));
|
|
246
|
+
this.currentBlockIndex = idx;
|
|
247
|
+
this.currentBlockType = 'thinking';
|
|
248
|
+
}
|
|
249
|
+
closeCurrentBlock(events) {
|
|
250
|
+
if (this.currentBlockType !== null && this.currentBlockIndex >= 0) {
|
|
251
|
+
events.push(this.makeSSE('content_block_stop', {
|
|
252
|
+
type: 'content_block_stop',
|
|
253
|
+
index: this.currentBlockIndex,
|
|
254
|
+
}));
|
|
255
|
+
this.currentBlockType = null;
|
|
256
|
+
this.currentBlockIndex = -1;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
makeSSE(claudeEventType, data) {
|
|
260
|
+
return {
|
|
261
|
+
event: claudeEventType,
|
|
262
|
+
data,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
exports.ResponsesToClaudeConverter = ResponsesToClaudeConverter;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* OpenAI Chat Completions → Claude Messages request conversion.
|
|
4
|
+
*
|
|
5
|
+
* Converts a Chat Completions request body into a Claude Messages request body.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.completionsToClaude = completionsToClaude;
|
|
9
|
+
const tool_schema_js_1 = require("../../utils/tool-schema.js");
|
|
10
|
+
/**
|
|
11
|
+
* Convert an OpenAI Chat Completions request body to a Claude Messages request body.
|
|
12
|
+
*/
|
|
13
|
+
function completionsToClaude(body) {
|
|
14
|
+
const messages = [];
|
|
15
|
+
let systemPrompt;
|
|
16
|
+
for (const msg of body.messages) {
|
|
17
|
+
if (msg.role === 'system') {
|
|
18
|
+
systemPrompt =
|
|
19
|
+
typeof msg.content === 'string' ? msg.content : msg.content;
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const content = convertCompletionsMessageToClaude(msg);
|
|
23
|
+
if (content) {
|
|
24
|
+
const role = msg.role === 'tool'
|
|
25
|
+
? 'user'
|
|
26
|
+
: msg.role === 'function'
|
|
27
|
+
? 'assistant'
|
|
28
|
+
: msg.role;
|
|
29
|
+
messages.push({ role, content });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const result = {
|
|
33
|
+
model: body.model,
|
|
34
|
+
messages,
|
|
35
|
+
max_tokens: body.max_tokens || 8192,
|
|
36
|
+
stream: body.stream || false,
|
|
37
|
+
};
|
|
38
|
+
if (systemPrompt)
|
|
39
|
+
result.system = systemPrompt;
|
|
40
|
+
if (body.temperature !== undefined)
|
|
41
|
+
result.temperature = body.temperature;
|
|
42
|
+
if (body.top_p !== undefined)
|
|
43
|
+
result.top_p = body.top_p;
|
|
44
|
+
if (body.stop) {
|
|
45
|
+
result.stop_sequences = Array.isArray(body.stop) ? body.stop : [body.stop];
|
|
46
|
+
}
|
|
47
|
+
// Tool mapping (OpenAI parameters → Claude input_schema)
|
|
48
|
+
if (body.tools && body.tools.length > 0) {
|
|
49
|
+
result.tools = (0, tool_schema_js_1.completionsToClaudeTools)(body.tools);
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Convert a single OpenAI message into Claude content (string or content block array).
|
|
55
|
+
*/
|
|
56
|
+
function convertCompletionsMessageToClaude(msg) {
|
|
57
|
+
// Assistant with tool_calls
|
|
58
|
+
if (msg.role === 'assistant' && msg.tool_calls) {
|
|
59
|
+
const content = [];
|
|
60
|
+
// reasoning_content → thinking block (prepend)
|
|
61
|
+
if (msg.reasoning_content) {
|
|
62
|
+
content.push({
|
|
63
|
+
type: 'thinking',
|
|
64
|
+
thinking: msg.reasoning_content,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
if (msg.content) {
|
|
68
|
+
content.push({ type: 'text', text: msg.content });
|
|
69
|
+
}
|
|
70
|
+
for (const tc of msg.tool_calls) {
|
|
71
|
+
content.push({
|
|
72
|
+
type: 'tool_use',
|
|
73
|
+
id: tc.id,
|
|
74
|
+
name: tc.function.name,
|
|
75
|
+
input: typeof tc.function.arguments === 'string'
|
|
76
|
+
? JSON.parse(tc.function.arguments)
|
|
77
|
+
: tc.function.arguments,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return content;
|
|
81
|
+
}
|
|
82
|
+
// Tool role → tool_result block
|
|
83
|
+
if (msg.role === 'tool') {
|
|
84
|
+
return {
|
|
85
|
+
type: 'tool_result',
|
|
86
|
+
tool_use_id: msg.tool_call_id,
|
|
87
|
+
content: msg.content,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
// Function role → tool_result block
|
|
91
|
+
if (msg.role === 'function') {
|
|
92
|
+
return {
|
|
93
|
+
type: 'tool_result',
|
|
94
|
+
tool_use_id: msg.name,
|
|
95
|
+
content: msg.content,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
// Plain assistant with reasoning_content (no tool_calls)
|
|
99
|
+
if (msg.role === 'assistant' && msg.reasoning_content) {
|
|
100
|
+
const content = [];
|
|
101
|
+
content.push({
|
|
102
|
+
type: 'thinking',
|
|
103
|
+
thinking: msg.reasoning_content,
|
|
104
|
+
});
|
|
105
|
+
if (msg.content) {
|
|
106
|
+
content.push({ type: 'text', text: msg.content });
|
|
107
|
+
}
|
|
108
|
+
return content;
|
|
109
|
+
}
|
|
110
|
+
return msg.content;
|
|
111
|
+
}
|