Skip to content

Commit 65a54c1

Browse files
committed
camelCase normalization
1 parent 5b50555 commit 65a54c1

2 files changed

Lines changed: 46 additions & 8 deletions

File tree

README.md

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -290,19 +290,31 @@ You can also pin specific versions with full model names (`claude-opus-4-6`, `cl
290290

291291
**`text`** (default) — plain text response.
292292

293-
**`json`** — single JSON object:
293+
**`json`** — single JSON object (all keys normalized to camelCase):
294294

295295
```json
296296
{
297297
"type": "result",
298298
"subtype": "success",
299-
"is_error": false,
299+
"isError": false,
300300
"result": "the response text",
301-
"num_turns": 1,
302-
"duration_ms": 3100,
303-
"total_cost_usd": 0.156,
304-
"session_id": "...",
305-
"usage": { "input_tokens": 3, "output_tokens": 4 }
301+
"numTurns": 1,
302+
"durationMs": 3100,
303+
"totalCostUsd": 0.156,
304+
"sessionId": "...",
305+
"usage": { "inputTokens": 3, "outputTokens": 4, "cacheReadInputTokens": 512 },
306+
"modelUsage": {
307+
"glm-5.1": {
308+
"inputTokens": 15702,
309+
"outputTokens": 28,
310+
"cacheReadInputTokens": 6836,
311+
"costUsd": 0.0826,
312+
"contextWindow": 200000,
313+
"maxOutputTokens": 32000
314+
}
315+
},
316+
"permissionDenials": [],
317+
"iterations": []
306318
}
307319
```
308320

api_server.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#!/usr/bin/env python3
22

33
import asyncio
4+
import json
45
import os
56
import signal
67
from typing import Optional
@@ -13,6 +14,31 @@
1314
app = FastAPI()
1415

1516

17+
def _to_camel(name: str) -> str:
18+
"""Convert a snake_case string to camelCase."""
19+
parts = name.split("_")
20+
return parts[0] + "".join(p.capitalize() for p in parts[1:])
21+
22+
23+
def _normalize_keys(obj):
24+
"""Recursively convert all dict keys from snake_case to camelCase."""
25+
if isinstance(obj, dict):
26+
return {_to_camel(k): _normalize_keys(v) for k, v in obj.items()}
27+
if isinstance(obj, list):
28+
return [_normalize_keys(item) for item in obj]
29+
return obj
30+
31+
32+
def _normalize_response(raw: bytes) -> bytes:
33+
"""Parse raw JSON bytes, normalize keys to camelCase, return bytes."""
34+
try:
35+
parsed = json.loads(raw)
36+
normalized = _normalize_keys(parsed)
37+
return json.dumps(normalized).encode()
38+
except (json.JSONDecodeError, ValueError):
39+
return raw
40+
41+
1642
def _shutdown(sig, _frame):
1743
for ws, proc in list(busy_workspaces.items()):
1844
try:
@@ -216,7 +242,7 @@ async def _disconnect_watcher():
216242
if not req.fire_and_forget and await request.is_disconnected():
217243
return Response(status_code=499)
218244

219-
return Response(content=output, media_type="application/json")
245+
return Response(content=_normalize_response(output), media_type="application/json")
220246

221247

222248
@app.get("/files/{path:path}")

0 commit comments

Comments
 (0)