Repository navigation
Expand file tree
/
Copy pathtranslator.js
More file actions
155 lines (137 loc) · 4.18 KB
/
Copy pathtranslator.js
File metadata and controls
155 lines (137 loc) · 4.18 KB
1
2
3
4
5
6
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
import { v4 as uuidv4 } from 'uuid';
/**
* Convert OpenAI chat messages to Claude Code stream-json input format
*/
export function openaiToClaudeInput(messages, sessionId) {
// Extract system prompt if present
const systemMessages = messages.filter(m => m.role === 'system');
const otherMessages = messages.filter(m => m.role !== 'system');
// Combine all messages into a single prompt
// Claude Code manages its own conversation history via session
let prompt = '';
if (systemMessages.length > 0) {
prompt += systemMessages.map(m => m.content).join('\n') + '\n\n';
}
// Get the latest user message (Claude Code maintains history internally)
const lastUserMessage = otherMessages.filter(m => m.role === 'user').pop();
if (lastUserMessage) {
prompt += lastUserMessage.content;
}
return {
type: 'user',
uuid: uuidv4(),
session_id: sessionId,
message: {
role: 'user',
content: [{ type: 'text', text: prompt }]
},
parent_tool_use_id: null
};
}
/**
* Convert Claude Code stream event to OpenAI SSE chunk format
*/
export function claudeToOpenaiChunk(claudeEvent, completionId, model) {
const chunk = {
id: completionId,
object: 'chat.completion.chunk',
created: Math.floor(Date.now() / 1000),
model: model,
choices: [{
index: 0,
delta: {},
finish_reason: null
}]
};
// Handle different Claude event types
if (claudeEvent.type === 'stream_event') {
const event = claudeEvent.event;
if (event?.type === 'content_block_delta') {
if (event.delta?.type === 'text_delta' && event.delta?.text) {
chunk.choices[0].delta.content = event.delta.text;
}
} else if (event?.type === 'message_start') {
chunk.choices[0].delta.role = 'assistant';
}
} else if (claudeEvent.type === 'assistant') {
// Full assistant message - extract text content
const content = claudeEvent.message?.content;
if (Array.isArray(content)) {
const textContent = content
.filter(c => c.type === 'text')
.map(c => c.text)
.join('');
if (textContent) {
chunk.choices[0].delta.content = textContent;
}
}
} else if (claudeEvent.type === 'message_stop' || claudeEvent.type === 'result') {
chunk.choices[0].delta = {};
chunk.choices[0].finish_reason = 'stop';
}
return chunk;
}
/**
* Convert Claude Code result to OpenAI non-streaming response
*/
export function claudeResultToOpenai(resultEvent, assistantMessages, completionId, model) {
// Collect all text from assistant messages
let fullContent = '';
for (const msg of assistantMessages) {
if (msg.type === 'assistant' && msg.message?.content) {
const textParts = msg.message.content
.filter(c => c.type === 'text')
.map(c => c.text);
fullContent += textParts.join('');
}
}
// If we have a result event with result text, use that
if (resultEvent?.result) {
fullContent = resultEvent.result;
}
const response = {
id: completionId,
object: 'chat.completion',
created: Math.floor(Date.now() / 1000),
model: model,
choices: [{
index: 0,
message: {
role: 'assistant',
content: fullContent
},
finish_reason: 'stop'
}],
usage: {
prompt_tokens: resultEvent?.usage?.input_tokens || 0,
completion_tokens: resultEvent?.usage?.output_tokens || 0,
total_tokens: (resultEvent?.usage?.input_tokens || 0) + (resultEvent?.usage?.output_tokens || 0)
}
};
return response;
}
/**
* Format an OpenAI SSE chunk for transmission
*/
export function formatSSE(chunk) {
return `data: ${JSON.stringify(chunk)}\n\n`;
}
/**
* Format the final SSE done message
*/
export function formatSSEDone() {
return 'data: [DONE]\n\n';
}
/**
* Parse a line of NDJSON from Claude Code output
*/
export function parseClaudeLine(line) {
const trimmed = line.trim();
if (!trimmed) return null;
try {
return JSON.parse(trimmed);
} catch (e) {
// Not valid JSON, might be partial or debug output
return null;
}
}