feat(chat): 생각 과정(reasoning·도구 스텝) 토글 + 작성 중 스피너·경과 초 — ABAP_OPENCODE 와 같은 모양. 백엔드가 step 이벤트로 흘림. 0.1.2
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
5ee599fb6b
commit
5277481513
@@ -134,8 +134,12 @@ class TurnState:
|
||||
if mid in self.user_message_ids and mid not in self.assistant_message_ids:
|
||||
return out
|
||||
pid = props.get("partID", "")
|
||||
if self.part_types.get(pid) != "text":
|
||||
return out # reasoning/tool 파트, 또는 아직 타입 모름(최종 스냅샷이 메워줌)
|
||||
ptype = self.part_types.get(pid)
|
||||
if ptype == "reasoning":
|
||||
out.append(("step", {"id": pid, "kind": "reasoning", "text": props["delta"]}))
|
||||
return out
|
||||
if ptype != "text":
|
||||
return out # tool 파트, 또는 아직 타입 모름(최종 스냅샷이 메워줌)
|
||||
delta = props["delta"]
|
||||
if pid not in self.text_by_part:
|
||||
self.part_order.append(pid)
|
||||
@@ -148,6 +152,11 @@ class TurnState:
|
||||
part = props.get("part") or {}
|
||||
if part.get("id"):
|
||||
self.part_types[part["id"]] = part.get("type", "")
|
||||
if part.get("type") == "tool":
|
||||
# 도구 호출 진행 — 화면 "생각 과정" 에 이름·상태만. 입력/출력은 안 보냄(크고 사용자 관심 밖)
|
||||
st = part.get("state") or {}
|
||||
out.append(("step", {"id": part.get("id", ""), "kind": "tool", "tool": part.get("tool", ""), "status": st.get("status", ""), "title": st.get("title") or ""}))
|
||||
return out
|
||||
if part.get("type") != "text" or part.get("synthetic") or part.get("ignored"):
|
||||
return out
|
||||
mid = part.get("messageID", "")
|
||||
|
||||
@@ -49,7 +49,7 @@ def test_turnstate_snapshot_path_without_delta():
|
||||
def test_turnstate_ignores_reasoning_and_emits_title():
|
||||
st = TurnState("s1", "q")
|
||||
st.handle(_msg_updated("ma", "assistant"))
|
||||
assert st.handle(_part_updated("ma", "pr", "생각중", ptype="reasoning")) == []
|
||||
assert st.handle(_part_updated("ma", "pr", "생각중", ptype="reasoning")) == [] # 스냅샷은 안 보냄(delta 만)
|
||||
ev = {"type": "session.updated", "properties": {"info": {"id": "s1", "title": "MARA 조회"}}}
|
||||
assert st.handle(ev) == [("title", {"title": "MARA 조회"})]
|
||||
assert st.handle(ev) == [] # 같은 제목 반복 안 보냄
|
||||
@@ -57,6 +57,14 @@ def test_turnstate_ignores_reasoning_and_emits_title():
|
||||
assert st.handle(placeholder) == [] # OpenCode 기본 제목은 무시
|
||||
|
||||
|
||||
def test_turnstate_tool_part_becomes_step():
|
||||
st = TurnState("s1", "q")
|
||||
st.handle(_msg_updated("ma", "assistant"))
|
||||
ev = {"type": "message.part.updated", "properties": {"part": {"id": "pt1", "messageID": "ma", "sessionID": "s1", "type": "tool", "tool": "sap-icf_get_program_source", "state": {"status": "running", "title": "ZRMEM2016", "input": {"x": 1}}}}}
|
||||
assert st.handle(ev) == [("step", {"id": "pt1", "kind": "tool", "tool": "sap-icf_get_program_source", "status": "running", "title": "ZRMEM2016"})]
|
||||
assert st.accumulated() == "" # 도구는 답변 본문에 안 섞임
|
||||
|
||||
|
||||
def _delta(mid, pid, delta, sid="s1", field="text"):
|
||||
return {"type": "message.part.delta", "properties": {"sessionID": sid, "messageID": mid, "partID": pid, "field": field, "delta": delta}}
|
||||
|
||||
@@ -68,7 +76,7 @@ def test_turnstate_real_server_shape_delta_events():
|
||||
st.handle(_part_updated("mu", "pu", "q"))
|
||||
st.handle(_msg_updated("ma", "assistant"))
|
||||
assert st.handle(_part_updated("ma", "pr", "", ptype="reasoning")) == []
|
||||
assert st.handle(_delta("ma", "pr", "생각")) == [] # reasoning delta 는 안 보냄
|
||||
assert st.handle(_delta("ma", "pr", "생각")) == [("step", {"id": "pr", "kind": "reasoning", "text": "생각"})]
|
||||
assert st.handle(_part_updated("ma", "pt", "")) == [] # 빈 스냅샷
|
||||
assert st.handle(_delta("ma", "pt", "SELECT")) == [("token", {"delta": "SELECT"})]
|
||||
assert st.handle(_delta("ma", "pt", " SINGLE")) == [("token", {"delta": " SINGLE"})]
|
||||
|
||||
Reference in New Issue
Block a user