Gemini CLI commited on
Commit
c60e994
·
1 Parent(s): 5bab9b6

Fix indentation error in claude_compat.py

Browse files
Files changed (1) hide show
  1. app/core/claude_compat.py +339 -1
app/core/claude_compat.py CHANGED
@@ -11,4 +11,342 @@ from typing import Any, Optional
11
 
12
 
13
  def extract_text(content: Any) -> str:
14
- ...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
 
13
  def extract_text(content: Any) -> str:
14
+ """Extract plain text from Claude/OpenAI mixed content blocks."""
15
+ if isinstance(content, str):
16
+ return content
17
+
18
+ if isinstance(content, list):
19
+ return " ".join(
20
+ str(block.get("text", ""))
21
+ for block in content
22
+ if isinstance(block, dict) and block.get("type") == "text"
23
+ ).strip()
24
+
25
+ return str(content) if content else ""
26
+
27
+
28
+ def claude_messages_to_openai(system: Any, messages: list[dict]) -> list[dict]:
29
+ """Convert Claude messages payload into OpenAI-style messages."""
30
+ converted: list[dict] = []
31
+
32
+ if system:
33
+ if isinstance(system, str):
34
+ converted.append({"role": "system", "content": system})
35
+ elif isinstance(system, list):
36
+ system_text = [
37
+ block.get("text", "")
38
+ for block in system
39
+ if isinstance(block, dict) and block.get("type") == "text"
40
+ ]
41
+ if system_text:
42
+ converted.append({
43
+ "role": "system",
44
+ "content": "\n".join(system_text),
45
+ })
46
+
47
+ for message in messages:
48
+ role = message.get("role", "user")
49
+ content = message.get("content", "")
50
+
51
+ if role == "assistant" and isinstance(content, list):
52
+ text_parts: list[str] = []
53
+ tool_calls: list[dict] = []
54
+
55
+ for block in content:
56
+ if not isinstance(block, dict):
57
+ continue
58
+
59
+ block_type = block.get("type")
60
+ if block_type == "text":
61
+ text_parts.append(block.get("text", ""))
62
+ elif block_type == "tool_use":
63
+ tool_calls.append(
64
+ {
65
+ "id": block.get(
66
+ "id",
67
+ f"call_{uuid.uuid4().hex[:24]}",
68
+ ),
69
+ "type": "function",
70
+ "function": {
71
+ "name": block.get("name", ""),
72
+ "arguments": json.dumps(
73
+ block.get("input", {}),
74
+ ensure_ascii=False,
75
+ ),
76
+ },
77
+ }
78
+ )
79
+
80
+ openai_message: dict = {
81
+ "role": "assistant",
82
+ "content": " ".join(text_parts).strip() or None,
83
+ }
84
+ if tool_calls:
85
+ openai_message["tool_calls"] = tool_calls
86
+ converted.append(openai_message)
87
+ continue
88
+
89
+ if role == "user" and isinstance(content, list):
90
+ has_tool_result = any(
91
+ isinstance(block, dict) and block.get("type") == "tool_result"
92
+ for block in content
93
+ )
94
+ if has_tool_result:
95
+ for block in content:
96
+ if not isinstance(block, dict):
97
+ continue
98
+
99
+ block_type = block.get("type")
100
+ if block_type == "tool_result":
101
+ result_content = block.get("content", "")
102
+ if isinstance(result_content, str):
103
+ rendered = result_content
104
+ elif isinstance(result_content, list):
105
+ rendered = " ".join(
106
+ item.get("text", "")
107
+ for item in result_content
108
+ if isinstance(item, dict)
109
+ and item.get("type") == "text"
110
+ )
111
+ else:
112
+ rendered = str(result_content)
113
+
114
+ converted.append(
115
+ {
116
+ "role": "tool",
117
+ "tool_call_id": block.get("tool_use_id", ""),
118
+ "content": rendered,
119
+ }
120
+ )
121
+ elif block_type == "text":
122
+ converted.append(
123
+ {"role": "user", "content": block.get("text", "")}
124
+ )
125
+ continue
126
+
127
+ converted.append({"role": role, "content": extract_text(content)})
128
+
129
+ return converted
130
+
131
+
132
+ def claude_tools_to_openai(tools: Optional[list[dict]]) -> Optional[list[dict]]:
133
+ """Convert Claude tool schemas into OpenAI function tools."""
134
+ if not tools:
135
+ return None
136
+
137
+ converted = [
138
+ {
139
+ "type": "function",
140
+ "function": {
141
+ "name": tool.get("name", ""),
142
+ "description": tool.get("description", ""),
143
+ "parameters": tool.get("input_schema", {}),
144
+ },
145
+ }
146
+ for tool in tools
147
+ if isinstance(tool, dict)
148
+ ]
149
+ return converted or None
150
+
151
+
152
+ def claude_tool_choice_to_openai(tool_choice: Any) -> Any:
153
+ """Convert Claude tool_choice payload into OpenAI-compatible form."""
154
+ if not isinstance(tool_choice, dict):
155
+ return tool_choice
156
+
157
+ tool_choice_type = tool_choice.get("type", "auto")
158
+ if tool_choice_type == "auto":
159
+ return "auto"
160
+ if tool_choice_type == "any":
161
+ return "required"
162
+ if tool_choice_type == "none":
163
+ return "none"
164
+ if tool_choice_type == "tool":
165
+ name = tool_choice.get("name", "")
166
+ if name:
167
+ return {"type": "function", "function": {"name": name}}
168
+ return tool_choice
169
+
170
+
171
+ def make_claude_id() -> str:
172
+ """Generate a Claude-style message id."""
173
+ return f"msg_{uuid.uuid4().hex[:24]}"
174
+
175
+
176
+ def build_tool_call_blocks(tool_calls: list[dict]) -> list[dict]:
177
+ """Convert OpenAI tool calls to Claude tool_use blocks."""
178
+ blocks = []
179
+ for tool_call in tool_calls:
180
+ function_data = (
181
+ tool_call.get("function")
182
+ if isinstance(tool_call.get("function"), dict)
183
+ else {}
184
+ )
185
+ arguments = function_data.get("arguments", "{}")
186
+ try:
187
+ input_data = json.loads(arguments) if isinstance(arguments, str) else arguments
188
+ except Exception:
189
+ input_data = {}
190
+
191
+ blocks.append(
192
+ {
193
+ "type": "tool_use",
194
+ "id": tool_call.get(
195
+ "id",
196
+ f"toolu_{uuid.uuid4().hex[:20]}",
197
+ ).replace("call_", "toolu_"),
198
+ "name": function_data.get("name", ""),
199
+ "input": input_data,
200
+ }
201
+ )
202
+ return blocks
203
+
204
+
205
+ def build_non_stream_response(
206
+ msg_id: str,
207
+ model: str,
208
+ reasoning_parts: list[str],
209
+ answer_text: str,
210
+ tool_calls: Optional[list[dict]],
211
+ input_tokens: int,
212
+ output_tokens: int,
213
+ cache_creation_tokens: int = 0,
214
+ cache_read_tokens: int = 0,
215
+ ) -> dict:
216
+ """Build a Claude non-streaming message response."""
217
+ content: list[dict] = []
218
+ if reasoning_parts:
219
+ content.append(
220
+ {"type": "thinking", "thinking": "".join(reasoning_parts)}
221
+ )
222
+ if answer_text:
223
+ content.append({"type": "text", "text": answer_text})
224
+ elif not tool_calls:
225
+ content.append({"type": "text", "text": ""})
226
+ if tool_calls:
227
+ content.extend(build_tool_call_blocks(tool_calls))
228
+
229
+ return {
230
+ "id": msg_id,
231
+ "type": "message",
232
+ "role": "assistant",
233
+ "content": content,
234
+ "model": model,
235
+ "stop_reason": "tool_use" if tool_calls else "end_turn",
236
+ "stop_sequence": None,
237
+ "usage": {
238
+ "input_tokens": input_tokens,
239
+ "output_tokens": output_tokens,
240
+ "cache_creation_input_tokens": cache_creation_tokens,
241
+ "cache_read_input_tokens": cache_read_tokens,
242
+ },
243
+ }
244
+
245
+
246
+ def sse(event: str, data: dict) -> str:
247
+ """Format a Claude SSE event."""
248
+ return f"event: {event}\ndata: {json.dumps(data, ensure_ascii=False)}\n\n"
249
+
250
+
251
+ def sse_message_start(
252
+ msg_id: str,
253
+ model: str,
254
+ input_tokens: int,
255
+ cache_creation_tokens: int = 0,
256
+ cache_read_tokens: int = 0,
257
+ ) -> str:
258
+ """Create Claude message_start SSE event."""
259
+ return sse(
260
+ "message_start",
261
+ {
262
+ "type": "message_start",
263
+ "message": {
264
+ "id": msg_id,
265
+ "type": "message",
266
+ "role": "assistant",
267
+ "content": [],
268
+ "model": model,
269
+ "stop_reason": None,
270
+ "stop_sequence": None,
271
+ "usage": {
272
+ "input_tokens": input_tokens,
273
+ "cache_creation_input_tokens": cache_creation_tokens,
274
+ "cache_read_input_tokens": cache_read_tokens,
275
+ "output_tokens": 0,
276
+ },
277
+ },
278
+ },
279
+ )
280
+
281
+
282
+ def sse_ping() -> str:
283
+ """Create Claude ping SSE event."""
284
+ return sse("ping", {"type": "ping"})
285
+
286
+
287
+ def sse_content_block_start(index: int, block: dict) -> str:
288
+ """Create Claude content_block_start SSE event."""
289
+ return sse(
290
+ "content_block_start",
291
+ {
292
+ "type": "content_block_start",
293
+ "index": index,
294
+ "content_block": block,
295
+ },
296
+ )
297
+
298
+
299
+ def sse_content_block_delta(index: int, delta: dict) -> str:
300
+ """Create Claude content_block_delta SSE event."""
301
+ return sse(
302
+ "content_block_delta",
303
+ {"type": "content_block_delta", "index": index, "delta": delta},
304
+ )
305
+
306
+
307
+ def sse_content_block_stop(index: int) -> str:
308
+ """Create Claude content_block_stop SSE event."""
309
+ return sse(
310
+ "content_block_stop",
311
+ {"type": "content_block_stop", "index": index},
312
+ )
313
+
314
+
315
+ def sse_message_delta(
316
+ stop_reason: str,
317
+ output_tokens: int,
318
+ *,
319
+ input_tokens: int = 0,
320
+ cache_creation_tokens: int = 0,
321
+ cache_read_tokens: int = 0,
322
+ ) -> str:
323
+ """Create Claude message_delta SSE event."""
324
+ return sse(
325
+ "message_delta",
326
+ {
327
+ "type": "message_delta",
328
+ "delta": {"stop_reason": stop_reason, "stop_sequence": None},
329
+ "usage": {
330
+ "input_tokens": input_tokens,
331
+ "output_tokens": output_tokens,
332
+ "cache_creation_input_tokens": cache_creation_tokens,
333
+ "cache_read_input_tokens": cache_read_tokens,
334
+ },
335
+ },
336
+ )
337
+
338
+
339
+ def sse_message_stop() -> str:
340
+ """Create Claude message_stop SSE event."""
341
+ return sse("message_stop", {"type": "message_stop"})
342
+
343
+
344
+ def sse_error(error_type: str, message: str) -> str:
345
+ """Create Claude error SSE event."""
346
+ return sse(
347
+ "error",
348
+ {
349
+ "type": "error",
350
+ "error": {"type": error_type, "message": message},
351
+ },
352
+ )