fix: session handling and SDK async iteration
- Add sdk_conversation_id column to track SDK conversation separately from user session_id - Queue processor looks up SDK conversation ID for resume instead of passing user session_id - session_id "new" stores NULL initially, gets set from SDK on completion - Custom session_ids are preserved (not overwritten by SDK) - Worker catches SDK process exit error after messages are collected Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+17
-10
@@ -19,6 +19,7 @@ CREATE TABLE IF NOT EXISTS topics (
|
|||||||
CREATE TABLE IF NOT EXISTS requests (
|
CREATE TABLE IF NOT EXISTS requests (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
session_id TEXT,
|
session_id TEXT,
|
||||||
|
sdk_conversation_id TEXT,
|
||||||
topic_id TEXT NOT NULL REFERENCES topics(id),
|
topic_id TEXT NOT NULL REFERENCES topics(id),
|
||||||
prompt TEXT NOT NULL,
|
prompt TEXT NOT NULL,
|
||||||
model TEXT,
|
model TEXT,
|
||||||
@@ -156,7 +157,7 @@ async def create_request(
|
|||||||
now = _now()
|
now = _now()
|
||||||
|
|
||||||
if session_id == "new":
|
if session_id == "new":
|
||||||
session_id = _new_id()
|
session_id = None
|
||||||
|
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"INSERT INTO requests (id, session_id, topic_id, prompt, model, summarize, status, created_at) "
|
"INSERT INTO requests (id, session_id, topic_id, prompt, model, summarize, status, created_at) "
|
||||||
@@ -246,18 +247,13 @@ async def complete_request(
|
|||||||
db: aiosqlite.Connection,
|
db: aiosqlite.Connection,
|
||||||
request_id: str,
|
request_id: str,
|
||||||
result: str,
|
result: str,
|
||||||
session_id: str | None = None,
|
sdk_conversation_id: str | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
now = _now()
|
now = _now()
|
||||||
if session_id:
|
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"UPDATE requests SET status = 'completed', result = ?, completed_at = ?, session_id = ? WHERE id = ?",
|
"UPDATE requests SET status = 'completed', result = ?, completed_at = ?, "
|
||||||
(result, now, session_id, request_id),
|
"sdk_conversation_id = ?, session_id = COALESCE(session_id, ?) WHERE id = ?",
|
||||||
)
|
(result, now, sdk_conversation_id, sdk_conversation_id, request_id),
|
||||||
else:
|
|
||||||
await db.execute(
|
|
||||||
"UPDATE requests SET status = 'completed', result = ?, completed_at = ? WHERE id = ?",
|
|
||||||
(result, now, request_id),
|
|
||||||
)
|
)
|
||||||
await db.commit()
|
await db.commit()
|
||||||
|
|
||||||
@@ -297,6 +293,17 @@ async def list_sessions(db: aiosqlite.Connection) -> list[dict]:
|
|||||||
return [dict(row) for row in rows]
|
return [dict(row) for row in rows]
|
||||||
|
|
||||||
|
|
||||||
|
async def get_session_sdk_id(db: aiosqlite.Connection, session_id: str) -> str | None:
|
||||||
|
cursor = await db.execute(
|
||||||
|
"SELECT sdk_conversation_id FROM requests "
|
||||||
|
"WHERE session_id = ? AND status = 'completed' AND sdk_conversation_id IS NOT NULL "
|
||||||
|
"ORDER BY completed_at DESC LIMIT 1",
|
||||||
|
(session_id,),
|
||||||
|
)
|
||||||
|
row = await cursor.fetchone()
|
||||||
|
return row[0] if row else None
|
||||||
|
|
||||||
|
|
||||||
async def session_exists(db: aiosqlite.Connection, session_id: str) -> bool:
|
async def session_exists(db: aiosqlite.Connection, session_id: str) -> bool:
|
||||||
cursor = await db.execute(
|
cursor = await db.execute(
|
||||||
"SELECT 1 FROM requests WHERE session_id = ? LIMIT 1", (session_id,)
|
"SELECT 1 FROM requests WHERE session_id = ? LIMIT 1", (session_id,)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from src.backend.db.database import (
|
|||||||
complete_request,
|
complete_request,
|
||||||
fail_request,
|
fail_request,
|
||||||
append_topic_summary,
|
append_topic_summary,
|
||||||
|
get_session_sdk_id,
|
||||||
)
|
)
|
||||||
from src.backend.services.worker_client import WorkerClient
|
from src.backend.services.worker_client import WorkerClient
|
||||||
|
|
||||||
@@ -38,6 +39,11 @@ async def process_one(worker: WorkerClient) -> bool:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
model = resolve_model(req["model"], topic["model"])
|
model = resolve_model(req["model"], topic["model"])
|
||||||
|
|
||||||
|
# Only resume if there's a previous completed request with an SDK conversation ID
|
||||||
|
resume_id = None
|
||||||
|
if req["session_id"]:
|
||||||
|
resume_id = await get_session_sdk_id(db, req["session_id"])
|
||||||
finally:
|
finally:
|
||||||
await db.close()
|
await db.close()
|
||||||
|
|
||||||
@@ -46,7 +52,7 @@ async def process_one(worker: WorkerClient) -> bool:
|
|||||||
prompt=req["prompt"],
|
prompt=req["prompt"],
|
||||||
system_prompt=topic["system_prompt"],
|
system_prompt=topic["system_prompt"],
|
||||||
model=model,
|
model=model,
|
||||||
session_id=req["session_id"],
|
session_id=resume_id,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
db = await get_db()
|
db = await get_db()
|
||||||
@@ -63,7 +69,7 @@ async def process_one(worker: WorkerClient) -> bool:
|
|||||||
db,
|
db,
|
||||||
req["id"],
|
req["id"],
|
||||||
result["result"],
|
result["result"],
|
||||||
session_id=result.get("session_id"),
|
sdk_conversation_id=result.get("session_id"),
|
||||||
)
|
)
|
||||||
|
|
||||||
if req["summarize"]:
|
if req["summarize"]:
|
||||||
|
|||||||
+14
-3
@@ -33,19 +33,26 @@ app.post("/process", async (req, res) => {
|
|||||||
|
|
||||||
const conv = query({ prompt, options });
|
const conv = query({ prompt, options });
|
||||||
const messages = [];
|
const messages = [];
|
||||||
|
try {
|
||||||
for await (const msg of conv) {
|
for await (const msg of conv) {
|
||||||
messages.push(msg);
|
messages.push(msg);
|
||||||
}
|
}
|
||||||
|
} catch (_) {
|
||||||
|
// SDK throws after process exits; messages already collected
|
||||||
|
}
|
||||||
|
|
||||||
const resultMsg = messages.find((m) => m.type === "result");
|
const resultMsg = messages.find((m) => m.type === "result");
|
||||||
if (resultMsg && resultMsg.is_error) {
|
if (!resultMsg) {
|
||||||
|
return res.json({ success: false, error: "No result received from Claude" });
|
||||||
|
}
|
||||||
|
if (resultMsg.is_error) {
|
||||||
return res.json({ success: false, error: resultMsg.result });
|
return res.json({ success: false, error: resultMsg.result });
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
result: resultMsg?.result || "",
|
result: resultMsg.result || "",
|
||||||
session_id: resultMsg?.session_id || session_id,
|
session_id: resultMsg.session_id || session_id,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Process error:", error.message);
|
console.error("Process error:", error.message);
|
||||||
@@ -85,9 +92,13 @@ app.post("/summarize", async (req, res) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
const msgs = [];
|
const msgs = [];
|
||||||
|
try {
|
||||||
for await (const msg of conv) {
|
for await (const msg of conv) {
|
||||||
msgs.push(msg);
|
msgs.push(msg);
|
||||||
}
|
}
|
||||||
|
} catch (_) {
|
||||||
|
// SDK throws after process exits; messages already collected
|
||||||
|
}
|
||||||
|
|
||||||
const resultMsg = msgs.find((m) => m.type === "result");
|
const resultMsg = msgs.find((m) => m.type === "result");
|
||||||
res.json({
|
res.json({
|
||||||
|
|||||||
Reference in New Issue
Block a user