agentengine-sdk-python 0.6.9__py3-none-any.whl → 0.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.7.0.dist-info}/METADATA +6 -4
  2. {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.7.0.dist-info}/RECORD +123 -128
  3. ksadk/api/client.py +19 -0
  4. ksadk/cli/cmd_create.py +34 -7
  5. ksadk/cli/cmd_hermes.py +6 -2
  6. ksadk/cli/cmd_invoke.py +208 -41
  7. ksadk/cli/cmd_mcp.py +6 -1
  8. ksadk/cli/cmd_openclaw.py +6 -2
  9. ksadk/cli/cmd_run.py +13 -6
  10. ksadk/cli/network_options.py +5 -3
  11. ksadk/configs/env_registry.py +1 -0
  12. ksadk/conversations/message_projection.py +269 -0
  13. ksadk/conversations/model_context.py +11 -1
  14. ksadk/conversations/runtime.py +81 -13
  15. ksadk/deployment/base.py +1 -1
  16. ksadk/deployment/providers/serverless.py +18 -8
  17. ksadk/memory/adk/resilient_session_service.py +268 -0
  18. ksadk/memory/adk/short_term_memory.py +21 -6
  19. ksadk/runners/adk_runner.py +974 -115
  20. ksadk/runners/langchain_runner.py +219 -7
  21. ksadk/runners/langgraph_runner.py +145 -12
  22. ksadk/runners/remote_runner.py +143 -18
  23. ksadk/server/app.py +148 -6
  24. ksadk/server/static/assets/{ArtifactsPanel-C0meuOdi.js → ArtifactsPanel-D-SSn2ee.js} +1 -1
  25. ksadk/server/static/assets/{CodeBlock-BbI4HPtJ.js → CodeBlock-P16VOLYi.js} +1 -1
  26. ksadk/server/static/assets/{MathMessageMarkdown-DMLQYoLp.js → MathMessageMarkdown-CdGiQWHR.js} +1 -1
  27. ksadk/server/static/assets/{MermaidBlock-DCR8WZ17.js → MermaidBlock-IOcal8cm.js} +6 -6
  28. ksadk/server/static/assets/{NativeTerminalPanel-CKUvWhUz.js → NativeTerminalPanel-DGSDWsei.js} +1 -1
  29. ksadk/server/static/assets/{arc-bj2rZnP5.js → arc-BA13sHc9.js} +1 -1
  30. ksadk/server/static/assets/architecture-7EHR7CIX-BvUlsA6l.js +1 -0
  31. ksadk/server/static/assets/{architectureDiagram-3BPJPVTR-2u1ltDIj.js → architectureDiagram-3BPJPVTR-DzJzgVoL.js} +1 -1
  32. ksadk/server/static/assets/{blockDiagram-GPEHLZMM-D6fvdK20.js → blockDiagram-GPEHLZMM-UTdVcZhn.js} +1 -1
  33. ksadk/server/static/assets/{c4Diagram-AAUBKEIU-BK82L5n5.js → c4Diagram-AAUBKEIU-Dix0GD4m.js} +1 -1
  34. ksadk/server/static/assets/channel-CTuydARX.js +1 -0
  35. ksadk/server/static/assets/{chunk-2J33WTMH-DLVdkeyQ.js → chunk-2J33WTMH-C2JxuZ42.js} +1 -1
  36. ksadk/server/static/assets/{chunk-4BX2VUAB-BbwsYcbW.js → chunk-4BX2VUAB-Be57QPgD.js} +1 -1
  37. ksadk/server/static/assets/{chunk-55IACEB6-DOairxzw.js → chunk-55IACEB6-WjSuy4nv.js} +1 -1
  38. ksadk/server/static/assets/{chunk-727SXJPM-hCO487gs.js → chunk-727SXJPM-BOovP-b5.js} +1 -1
  39. ksadk/server/static/assets/{chunk-AQP2D5EJ-XU8vDOuG.js → chunk-AQP2D5EJ-DOmZNmWj.js} +1 -1
  40. ksadk/server/static/assets/{chunk-FMBD7UC4-PrapyfMu.js → chunk-FMBD7UC4-DzwlodkU.js} +1 -1
  41. ksadk/server/static/assets/{chunk-ND2GUHAM-CboKhTZ3.js → chunk-ND2GUHAM-DMqAikEU.js} +1 -1
  42. ksadk/server/static/assets/chunk-QZHKN3VN-BnCydivS.js +1 -0
  43. ksadk/server/static/assets/classDiagram-4FO5ZUOK-BNHg45Be.js +1 -0
  44. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-BNHg45Be.js +1 -0
  45. ksadk/server/static/assets/{cose-bilkent-S5V4N54A-_N8bJ-EU.js → cose-bilkent-S5V4N54A-Zhy2GBnP.js} +1 -1
  46. ksadk/server/static/assets/{dagre-BM42HDAG-FoDlo5J2.js → dagre-BM42HDAG-CbKrtIUz.js} +1 -1
  47. ksadk/server/static/assets/{diagram-2AECGRRQ-BBp6ebBw.js → diagram-2AECGRRQ-BRApgQRK.js} +1 -1
  48. ksadk/server/static/assets/{diagram-5GNKFQAL-CqOvG1PR.js → diagram-5GNKFQAL-D0xbnDiK.js} +1 -1
  49. ksadk/server/static/assets/{diagram-KO2AKTUF-O9T3t6ba.js → diagram-KO2AKTUF-CVugVI9L.js} +1 -1
  50. ksadk/server/static/assets/{diagram-LMA3HP47-CGG8ekBO.js → diagram-LMA3HP47-DozUGgee.js} +1 -1
  51. ksadk/server/static/assets/{diagram-OG6HWLK6-4BmH1EEg.js → diagram-OG6HWLK6-ZOYsKlru.js} +1 -1
  52. ksadk/server/static/assets/{dist-BmuTWcm7.js → dist-2hInLS_J.js} +1 -1
  53. ksadk/server/static/assets/{dist-BMKM66B4.js → dist-8EEyGgDx.js} +1 -1
  54. ksadk/server/static/assets/{dist-Cp1_jKwf.js → dist-8_SfK1IP.js} +1 -1
  55. ksadk/server/static/assets/{dist-CZ6C3x8B.js → dist-B0NP_Z0z.js} +1 -1
  56. ksadk/server/static/assets/{dist-DLeM1OCK.js → dist-BIJVDMuB.js} +1 -1
  57. ksadk/server/static/assets/{dist-Cwhk4lwI.js → dist-BmlMPsCc.js} +1 -1
  58. ksadk/server/static/assets/{dist-D99wf6_X.js → dist-BrCcQ47T.js} +1 -1
  59. ksadk/server/static/assets/{dist-CWiTDN_V.js → dist-BssVmjIo.js} +1 -1
  60. ksadk/server/static/assets/{dist-BVzcWa6D.js → dist-C3HIIaN5.js} +1 -1
  61. ksadk/server/static/assets/{dist-DM0rEQPk.js → dist-CPBtqRTW.js} +1 -1
  62. ksadk/server/static/assets/{dist-BkJjNih3.js → dist-CkEKJFtk.js} +1 -1
  63. ksadk/server/static/assets/{dist-B67uF2uK.js → dist-D-JMdWPQ.js} +1 -1
  64. ksadk/server/static/assets/{dist-DI-oVAXw.js → dist-D-xOD048.js} +1 -1
  65. ksadk/server/static/assets/{dist-6kSZvCZh.js → dist-D57wnGWd.js} +1 -1
  66. ksadk/server/static/assets/{dist-Bk1l05Ni.js → dist-DL_NKc_z.js} +1 -1
  67. ksadk/server/static/assets/{dist-DbaH3p5h.js → dist-DMezvBGJ.js} +1 -1
  68. ksadk/server/static/assets/{dist-CiZHB6ZW.js → dist-DmFaq_Iy.js} +1 -1
  69. ksadk/server/static/assets/{dist-DLMjL01k.js → dist-Dp0k7bWI.js} +1 -1
  70. ksadk/server/static/assets/{dist-DCpZrjaK.js → dist-DpKtt4Bo.js} +1 -1
  71. ksadk/server/static/assets/{dist-D5JGcxBx.js → dist-DyOkZmze.js} +1 -1
  72. ksadk/server/static/assets/{dist-C2DKRFVK.js → dist-DyWsb78Q.js} +1 -1
  73. ksadk/server/static/assets/{dist-S4klkK5W.js → dist-gX6GOLKq.js} +1 -1
  74. ksadk/server/static/assets/{erDiagram-TEJ5UH35-oJhaci7S.js → erDiagram-TEJ5UH35-Bk-9bgE5.js} +1 -1
  75. ksadk/server/static/assets/eventmodeling-FCH6USID-DsAgWnlf.js +1 -0
  76. ksadk/server/static/assets/{flowDiagram-I6XJVG4X-DtTmUan-.js → flowDiagram-I6XJVG4X-Dn9kZVuZ.js} +1 -1
  77. ksadk/server/static/assets/{ganttDiagram-6RSMTGT7-BSsb_eij.js → ganttDiagram-6RSMTGT7-BGxXlJqm.js} +1 -1
  78. ksadk/server/static/assets/gitGraph-WXDBUCRP-BIao9Yjg.js +1 -0
  79. ksadk/server/static/assets/{gitGraphDiagram-PVQCEYII-BR5s6Pws.js → gitGraphDiagram-PVQCEYII-C7Q0X-wi.js} +1 -1
  80. ksadk/server/static/assets/{index-BH7-pC0B.js → index-C00Lwg3L.js} +6 -6
  81. ksadk/server/static/assets/info-J43DQDTF-CyipqGxU.js +1 -0
  82. ksadk/server/static/assets/{infoDiagram-5YYISTIA-DYPc5cvh.js → infoDiagram-5YYISTIA-BO_CSqq5.js} +1 -1
  83. ksadk/server/static/assets/{ishikawaDiagram-YF4QCWOH-MoBdALDg.js → ishikawaDiagram-YF4QCWOH-eYLPLd46.js} +1 -1
  84. ksadk/server/static/assets/{journeyDiagram-JHISSGLW-DgLiQT8l.js → journeyDiagram-JHISSGLW-De9geSb0.js} +1 -1
  85. ksadk/server/static/assets/{kanban-definition-UN3LZRKU-CsFx112F.js → kanban-definition-UN3LZRKU-Cs80kQto.js} +1 -1
  86. ksadk/server/static/assets/{katex-DRsiwpM2.js → katex-DSrCFD8n.js} +1 -1
  87. ksadk/server/static/assets/{linear-TNRQ5Orv.js → linear-CN9sbKTF.js} +1 -1
  88. ksadk/server/static/assets/{mermaid-parser.core-D2eMVOgo.js → mermaid-parser.core-DlgSBU8l.js} +2 -2
  89. ksadk/server/static/assets/{mindmap-definition-RKZ34NQL-BtByYFhL.js → mindmap-definition-RKZ34NQL-9afz7Oug.js} +1 -1
  90. ksadk/server/static/assets/packet-YPE3B663-CGkyRxlg.js +1 -0
  91. ksadk/server/static/assets/pie-LRSECV5Y-M0uWAf4n.js +1 -0
  92. ksadk/server/static/assets/{pieDiagram-4H26LBE5-DPnuCB8c.js → pieDiagram-4H26LBE5-DPv_VstN.js} +1 -1
  93. ksadk/server/static/assets/{quadrantDiagram-W4KKPZXB-miFpCPv4.js → quadrantDiagram-W4KKPZXB-o77wZ1XK.js} +1 -1
  94. ksadk/server/static/assets/radar-GUYGQ44K-Bdbm24-x.js +1 -0
  95. ksadk/server/static/assets/{requirementDiagram-4Y6WPE33-mjB_yYkW.js → requirementDiagram-4Y6WPE33-BYVw81VC.js} +1 -1
  96. ksadk/server/static/assets/{sankeyDiagram-5OEKKPKP-D7hSlZZZ.js → sankeyDiagram-5OEKKPKP-B7nZFMKT.js} +1 -1
  97. ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-D9Cx6hC3.js → sequenceDiagram-3UESZ5HK-BGRRrFw5.js} +1 -1
  98. ksadk/server/static/assets/{stateDiagram-AJRCARHV-D_zrg47o.js → stateDiagram-AJRCARHV-BUfHCoPs.js} +1 -1
  99. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-8xBN_gLM.js +1 -0
  100. ksadk/server/static/assets/{timeline-definition-PNZ67QCA-CwSec0Wa.js → timeline-definition-PNZ67QCA-FbCTGrf9.js} +1 -1
  101. ksadk/server/static/assets/treeView-BLDUP644-npMo0ak1.js +1 -0
  102. ksadk/server/static/assets/treemap-LRROVOQU-CHdzrrdo.js +1 -0
  103. ksadk/server/static/assets/{vennDiagram-CIIHVFJN-agCVobiF.js → vennDiagram-CIIHVFJN-DWgwtPsJ.js} +1 -1
  104. ksadk/server/static/assets/wardley-L42UT6IY-BEQz7wyy.js +1 -0
  105. ksadk/server/static/assets/{wardleyDiagram-YWT4CUSO-TmrlhWNt.js → wardleyDiagram-YWT4CUSO-2x8G-tTM.js} +1 -1
  106. ksadk/server/static/assets/{xychartDiagram-2RQKCTM6-DV71V-ah.js → xychartDiagram-2RQKCTM6-Drma3AYl.js} +1 -1
  107. ksadk/server/static/index.html +1 -1
  108. ksadk/sessions/__init__.py +17 -21
  109. ksadk/sessions/continuity.py +28 -4
  110. ksadk/sessions/postgres_service.py +77 -14
  111. ksadk/sessions/resilience.py +52 -0
  112. ksadk/sessions/resilient.py +360 -0
  113. ksadk/toolsets/__init__.py +2 -0
  114. ksadk/toolsets/workspace.py +49 -10
  115. ksadk/tracing/setup.py +63 -0
  116. ksadk/tui/__init__.py +22 -7
  117. ksadk/tui/loop.py +1877 -0
  118. ksadk/tui/stream_render.py +68 -0
  119. ksadk/version.py +1 -1
  120. ksadk/server/static/assets/architecture-7EHR7CIX-Cr6Xz1_c.js +0 -1
  121. ksadk/server/static/assets/channel-BSSOwFIb.js +0 -1
  122. ksadk/server/static/assets/chunk-QZHKN3VN-XXpdiwUL.js +0 -1
  123. ksadk/server/static/assets/classDiagram-4FO5ZUOK-C5HvGiyK.js +0 -1
  124. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-C5HvGiyK.js +0 -1
  125. ksadk/server/static/assets/eventmodeling-FCH6USID-CIgwHJLL.js +0 -1
  126. ksadk/server/static/assets/gitGraph-WXDBUCRP-BVAJ7uo0.js +0 -1
  127. ksadk/server/static/assets/info-J43DQDTF-MT80V4ge.js +0 -1
  128. ksadk/server/static/assets/packet-YPE3B663-BLKhbXZB.js +0 -1
  129. ksadk/server/static/assets/pie-LRSECV5Y-DFOikjSY.js +0 -1
  130. ksadk/server/static/assets/radar-GUYGQ44K-095b1Ikw.js +0 -1
  131. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-J9kCb0nC.js +0 -1
  132. ksadk/server/static/assets/treeView-BLDUP644-yinfJEY6.js +0 -1
  133. ksadk/server/static/assets/treemap-LRROVOQU-DosUqN1Q.js +0 -1
  134. ksadk/server/static/assets/wardley-L42UT6IY-Bvp7-5uH.js +0 -1
  135. ksadk/tui/app.py +0 -400
  136. ksadk/tui/clipboard.py +0 -100
  137. ksadk/tui/widgets/__init__.py +0 -22
  138. ksadk/tui/widgets/assistant.py +0 -89
  139. ksadk/tui/widgets/base.py +0 -54
  140. ksadk/tui/widgets/chat_input.py +0 -246
  141. ksadk/tui/widgets/history.py +0 -156
  142. ksadk/tui/widgets/system.py +0 -87
  143. ksadk/tui/widgets/thinking.py +0 -110
  144. ksadk/tui/widgets/tool_call.py +0 -161
  145. ksadk/tui/widgets/user.py +0 -37
  146. {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.7.0.dist-info}/WHEEL +0 -0
  147. {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.7.0.dist-info}/entry_points.txt +0 -0
  148. {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.7.0.dist-info}/licenses/LICENSE +0 -0
  149. {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.7.0.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,7 @@
1
1
  """
2
2
  RemoteRunner - 远程 Agent 运行时
3
3
 
4
- 与 AgentTUI 配合使用,提供和本地 Runner 一致的接口
4
+ 与 InteractionLoop 配合使用,提供和本地 Runner 一致的接口
5
5
  """
6
6
 
7
7
  import json
@@ -43,6 +43,11 @@ class RemoteRunner(BaseRunner):
43
43
  self._agent = None # 兼容 BaseRunner
44
44
  self._responses_tool_names: dict[str, str] = {}
45
45
  self._responses_tool_args: dict[str, str] = {}
46
+ self._responses_streamed_item_keys: set[str] = set()
47
+ self._responses_text_streamed = False
48
+ self._responses_reasoning_streamed = False
49
+ self._observed_model: str | None = None # 流式回包里观察到的真实模型名
50
+ self.available_models: list[dict[str, Any]] | None = None # ListAgentModels 拿到的可选模型列表
46
51
 
47
52
  @staticmethod
48
53
  def _normalize_api_format(api_format: Optional[str]) -> str:
@@ -362,6 +367,14 @@ class RemoteRunner(BaseRunner):
362
367
  """流式调用远程 Agent"""
363
368
  import httpx
364
369
 
370
+ # 工具 item/call_id 只在单次 response 内有效。RemoteRunner 会被 TUI 多轮复用,
371
+ # 若保留上一轮的追踪状态,runtime 复用 id 时 response.completed 会被误判为重放。
372
+ self._responses_tool_names.clear()
373
+ self._responses_tool_args.clear()
374
+ self._responses_streamed_item_keys.clear()
375
+ self._responses_text_streamed = False
376
+ self._responses_reasoning_streamed = False
377
+
365
378
  user_input = input_data.get("input", "")
366
379
  session_id = input_data.get("session_id") or self.session_id
367
380
 
@@ -406,6 +419,17 @@ class RemoteRunner(BaseRunner):
406
419
  try:
407
420
  data = json.loads(data_str)
408
421
 
422
+ # 记录回包里的真实模型名:
423
+ # chat completions 顶层 data.model;responses 格式在
424
+ # data.response.model(response.completed 事件的 response 对象)。
425
+ observed = data.get("model") if isinstance(data, Mapping) else None
426
+ if not observed and isinstance(data, Mapping):
427
+ resp = data.get("response")
428
+ if isinstance(resp, Mapping):
429
+ observed = resp.get("model")
430
+ if observed and not isinstance(observed, (list, dict)):
431
+ self._observed_model = str(observed)
432
+
409
433
  if self.api_format == "responses":
410
434
  async for item in self._iter_responses_stream_events(
411
435
  data, event_name=event_name
@@ -413,11 +437,26 @@ class RemoteRunner(BaseRunner):
413
437
  yield item
414
438
  continue
415
439
 
440
+ # 兜底:api_format 标成 chat 但 runtime 实发 responses 格式
441
+ # 事件(event_name 或 data.type 以 response. 开头)→ 按 responses
442
+ # 解析,否则 response.reasoning.delta 会被 chat 路径当顶层 text 显示。
443
+ _ev = event_name or (str(data.get("type")) if isinstance(data, Mapping) else "")
444
+ if _ev.startswith("response."):
445
+ async for item in self._iter_responses_stream_events(
446
+ data, event_name=event_name
447
+ ):
448
+ yield item
449
+ continue
450
+
416
451
  # 解析 OpenAI Chat Completions 流式格式
417
452
  choices = data.get("choices", [])
418
453
  usage = data.get("usage")
419
454
  if isinstance(usage, Mapping):
420
- yield {"type": "final", "usage": dict(usage)}
455
+ final_chunk = {"type": "final", "usage": dict(usage)}
456
+ metadata = data.get("metadata")
457
+ if isinstance(metadata, Mapping):
458
+ final_chunk["metadata"] = dict(metadata)
459
+ yield final_chunk
421
460
  final_sent = True
422
461
  if choices:
423
462
  delta = choices[0].get("delta", {})
@@ -429,6 +468,13 @@ class RemoteRunner(BaseRunner):
429
468
  if content:
430
469
  accumulated_text += content
431
470
  yield {"delta": content, "type": "text"}
471
+ else:
472
+ # 非标准简化流(runtime 直发顶层 delta,无 choices 包装):
473
+ # 兜底提取顶层 delta 作为正文,与 _extract_content 口径一致。
474
+ top_delta = data.get("delta")
475
+ if isinstance(top_delta, str) and top_delta:
476
+ accumulated_text += top_delta
477
+ yield {"delta": top_delta, "type": "text"}
432
478
 
433
479
  except json.JSONDecodeError:
434
480
  pass
@@ -484,11 +530,14 @@ class RemoteRunner(BaseRunner):
484
530
  def _remember_responses_tool(
485
531
  self, key: str, item: Dict[str, Any], name: str, args: str
486
532
  ) -> None:
533
+ if key:
534
+ self._responses_streamed_item_keys.add(key)
487
535
  if key:
488
536
  self._responses_tool_names[key] = name
489
537
  self._responses_tool_args[key] = args
490
538
  call_id = str(item.get("call_id") or "")
491
539
  if call_id:
540
+ self._responses_streamed_item_keys.add(call_id)
492
541
  self._responses_tool_names[call_id] = name
493
542
  self._responses_tool_args[call_id] = args
494
543
 
@@ -526,6 +575,8 @@ class RemoteRunner(BaseRunner):
526
575
  data: Dict[str, Any],
527
576
  *,
528
577
  status: str,
578
+ replay_completed_text: bool = False,
579
+ replay_completed_reasoning: bool = False,
529
580
  ) -> AsyncIterator[Dict[str, Any]]:
530
581
  item = data.get("item") or data.get("output_item") or data
531
582
  if not isinstance(item, dict):
@@ -541,15 +592,20 @@ class RemoteRunner(BaseRunner):
541
592
  else item.get("args", item.get("input"))
542
593
  )
543
594
  self._remember_responses_tool(key, item, name, args)
544
- yield {"type": "tool_call", "tool_name": name, "tool_args": args, "status": status}
595
+ yield {"type": "tool_call", "tool_name": name, "tool_args": args, "status": status, "call_id": key}
545
596
  return
546
597
 
547
598
  if item_type == "function_call_output":
548
599
  name = self._responses_tool_name(key, item)
600
+ if key:
601
+ self._responses_streamed_item_keys.add(key)
602
+ call_id = str(item.get("call_id") or "")
603
+ if call_id:
604
+ self._responses_streamed_item_keys.add(call_id)
549
605
  output = self._stringify_responses_payload(
550
606
  item.get("output") if "output" in item else item.get("result", item.get("content"))
551
607
  )
552
- yield {"type": "tool_result", "tool_name": name, "tool_output": output}
608
+ yield {"type": "tool_result", "tool_name": name, "tool_output": output, "call_id": call_id or key}
553
609
  return
554
610
 
555
611
  if item_type == "mcp_approval_request":
@@ -576,13 +632,15 @@ class RemoteRunner(BaseRunner):
576
632
 
577
633
  if item_type in {"reasoning", "reasoning_summary", "reasoning_summary_text"}:
578
634
  text = self._responses_item_text(item)
579
- if text:
635
+ if text and (status != "completed" or replay_completed_reasoning):
636
+ self._responses_reasoning_streamed = True
580
637
  yield {"delta": text, "type": "thinking"}
581
638
  return
582
639
 
583
640
  if item_type == "message":
584
641
  text = self._responses_item_text(item)
585
- if text and status != "completed":
642
+ if text and (status != "completed" or replay_completed_text):
643
+ self._responses_text_streamed = True
586
644
  yield {"delta": text, "type": "text"}
587
645
  return
588
646
 
@@ -596,6 +654,7 @@ class RemoteRunner(BaseRunner):
596
654
  if event_name == "response.reasoning.delta":
597
655
  delta = data.get("delta")
598
656
  if delta:
657
+ self._responses_reasoning_streamed = True
599
658
  yield {"delta": str(delta), "type": "thinking"}
600
659
  return
601
660
  if event_type in {
@@ -606,12 +665,16 @@ class RemoteRunner(BaseRunner):
606
665
  }:
607
666
  delta = data.get("delta") or data.get("text")
608
667
  if delta:
668
+ self._responses_reasoning_streamed = True
609
669
  yield {"delta": str(delta), "type": "thinking"}
610
670
  return
611
- if event_type == "response.output_text.delta":
612
- delta = data.get("delta")
613
- if delta:
614
- yield {"delta": str(delta), "type": "text"}
671
+ if event_type in {"response.output_text.delta", "response.output_text.done"}:
672
+ text = data.get("delta") or data.get("text")
673
+ if text and (
674
+ event_type == "response.output_text.delta" or not self._responses_text_streamed
675
+ ):
676
+ self._responses_text_streamed = True
677
+ yield {"delta": str(text), "type": "text"}
615
678
  return
616
679
  if event_type == "response.output_item.added":
617
680
  async for item in self._iter_responses_output_item(data, status="running"):
@@ -621,13 +684,35 @@ class RemoteRunner(BaseRunner):
621
684
  async for item in self._iter_responses_output_item(data, status="completed"):
622
685
  yield item
623
686
  return
687
+ # ksadk/runtime 特有 tool 事件(对标 hosted UI responses-stream.js:176-186)
688
+ if event_type in {"response.tool_call", "response.ksadk.stage_tool_call"}:
689
+ name = str(data.get("name") or data.get("tool_name") or "tool")
690
+ args = self._stringify_responses_payload(data.get("args") if "args" in data else data.get("arguments"))
691
+ cid = str(data.get("call_id") or data.get("item_id") or data.get("run_id") or "")
692
+ yield {"type": "tool_call", "tool_name": name, "tool_args": args, "status": "running", "call_id": cid}
693
+ return
694
+ if event_type in {
695
+ "response.tool_result",
696
+ "response.ksadk.tool_result",
697
+ "response.ksadk.stage_tool_result",
698
+ }:
699
+ name = str(data.get("name") or data.get("tool_name") or "tool")
700
+ output = self._stringify_responses_payload(data.get("output") if "output" in data else data.get("result"))
701
+ cid = str(data.get("call_id") or data.get("item_id") or data.get("run_id") or "")
702
+ yield {
703
+ "type": "tool_result",
704
+ "tool_name": name,
705
+ "tool_output": output,
706
+ "call_id": cid,
707
+ }
708
+ return
624
709
  if event_type == "response.function_call_arguments.delta":
625
710
  key = str(data.get("item_id") or data.get("call_id") or "")
626
711
  name = self._responses_tool_name(key, data)
627
712
  args = f"{self._responses_tool_args.get(key, '')}{str(data.get('delta') or '')}"
628
713
  if key:
629
714
  self._responses_tool_args[key] = args
630
- yield {"type": "tool_call", "tool_name": name, "tool_args": args, "status": "running"}
715
+ yield {"type": "tool_call", "tool_name": name, "tool_args": args, "status": "running", "call_id": key}
631
716
  return
632
717
  if event_type == "response.function_call_arguments.done":
633
718
  key = str(data.get("item_id") or data.get("call_id") or "")
@@ -637,12 +722,30 @@ class RemoteRunner(BaseRunner):
637
722
  )
638
723
  if key:
639
724
  self._responses_tool_args[key] = args
640
- yield {"type": "tool_call", "tool_name": name, "tool_args": args, "status": "running"}
725
+ yield {"type": "tool_call", "tool_name": name, "tool_args": args, "status": "running", "call_id": key}
641
726
  return
642
727
  if event_type == "response.completed":
643
728
  response = data.get("response") if isinstance(data.get("response"), dict) else data
644
729
  output = response.get("output") if isinstance(response, dict) else None
645
730
  if isinstance(output, list):
731
+ replayed_item_keys = set(self._responses_streamed_item_keys)
732
+ replay_completed_text = not self._responses_text_streamed
733
+ replay_completed_reasoning = not self._responses_reasoning_streamed
734
+ for item in output:
735
+ if isinstance(item, dict):
736
+ key = self._responses_item_key(item, {"item": item})
737
+ call_id = str(item.get("call_id") or "")
738
+ if (key and key in replayed_item_keys) or (
739
+ call_id and call_id in replayed_item_keys
740
+ ):
741
+ continue
742
+ async for projected in self._iter_responses_output_item(
743
+ {"item": item},
744
+ status="completed",
745
+ replay_completed_text=replay_completed_text,
746
+ replay_completed_reasoning=replay_completed_reasoning,
747
+ ):
748
+ yield projected
646
749
  chunk = {
647
750
  "type": "responses_output",
648
751
  "output": output,
@@ -651,17 +754,39 @@ class RemoteRunner(BaseRunner):
651
754
  usage = response.get("usage")
652
755
  if isinstance(usage, Mapping):
653
756
  chunk["usage"] = dict(usage)
757
+ metadata = response.get("metadata")
758
+ if isinstance(metadata, Mapping):
759
+ chunk["metadata"] = dict(metadata)
654
760
  yield chunk
655
761
  return
762
+ # response.content_part.delta:glm 等模型把 reasoning 走这个事件,
763
+ # partType 含 "reasoning" → thinking(不显示),否则 text(对标 hosted UI)。
764
+ if event_type == "response.content_part.delta":
765
+ part = data.get("part") if isinstance(data.get("part"), dict) else {}
766
+ delta = data.get("delta")
767
+ part_type = str(part.get("type") or (delta.get("type") if isinstance(delta, dict) else "") or data.get("content_type") or "")
768
+ text = ""
769
+ if isinstance(delta, dict):
770
+ text = str(delta.get("text") or delta.get("content") or "")
771
+ elif isinstance(delta, str):
772
+ text = delta
773
+ if not text:
774
+ text = str(data.get("text") or "")
775
+ if not text:
776
+ return
777
+ if "reasoning" in part_type:
778
+ self._responses_reasoning_streamed = True
779
+ yield {"delta": text, "type": "thinking"}
780
+ else:
781
+ self._responses_text_streamed = True
782
+ yield {"delta": text, "type": "text"}
783
+ return
656
784
  if event_type == "response.failed":
657
785
  yield {"type": "error", "message": self._responses_error_message(data)}
658
786
  return
659
787
  if event_type == "response.incomplete":
660
788
  yield {"type": "error", "message": "Agent 响应未完成"}
661
789
  return
662
- if isinstance(data.get("delta"), str):
663
- yield {"delta": str(data["delta"]), "type": "text"}
664
- return
665
- output_text = RemoteRunner._extract_responses_output_text(data)
666
- if output_text and event_type != "response.completed":
667
- yield {"delta": output_text, "type": "text"}
790
+ # 未知事件默认丢弃(对标 hosted UI responses-stream.js: 未知事件 return [])。
791
+ # 之前默认把 data.delta 当 text 显示,会导致 reasoning 走未识别事件时漏进正文。
792
+ # 正文已由 output_text.delta / content_part.delta 显式处理,未知事件不该当正文。
ksadk/server/app.py CHANGED
@@ -1064,6 +1064,16 @@ class ListSessionEventsActionRequest(BaseModel):
1064
1064
  BeforeSeqId: Optional[int] = Field(None, ge=1)
1065
1065
 
1066
1066
 
1067
+ class ListSessionMessagesActionRequest(BaseModel):
1068
+ SessionId: str
1069
+ AfterSeqId: Optional[int] = Field(None, ge=0)
1070
+ BeforeSeqId: Optional[int] = Field(None, ge=1)
1071
+ Limit: int = Field(50, ge=1, le=200)
1072
+ IncludeReasoning: bool = False
1073
+ IncludeToolEvents: bool = False
1074
+ IncludeAttachments: bool = True
1075
+
1076
+
1067
1077
  class ListSessionCheckpointsActionRequest(BaseModel):
1068
1078
  AgentId: str
1069
1079
  SessionId: str
@@ -1596,6 +1606,82 @@ def _apply_checkpoint_resume_audit(
1596
1606
  return checkpoint
1597
1607
 
1598
1608
 
1609
+ def _apply_adk_only_latest_resumable(
1610
+ checkpoints: list[dict[str, Any]],
1611
+ ) -> list[dict[str, Any]]:
1612
+ """P1.4: For ADK invocation_id resume mode, only the latest checkpoint per
1613
+ RunId is independently resumable. Older checkpoints get IsResumable=False."""
1614
+ latest_by_run: dict[str, int] = {}
1615
+ for cp in checkpoints:
1616
+ metadata = cp.get("Metadata") or {}
1617
+ if not metadata.get("only_latest_resumable"):
1618
+ continue
1619
+ run_id = str(cp.get("RunId") or "")
1620
+ seq_id = int(cp.get("SeqId") or 0)
1621
+ if run_id not in latest_by_run or seq_id > latest_by_run[run_id]:
1622
+ latest_by_run[run_id] = seq_id
1623
+
1624
+ for cp in checkpoints:
1625
+ metadata = cp.get("Metadata") or {}
1626
+ if not metadata.get("only_latest_resumable"):
1627
+ continue
1628
+ run_id = str(cp.get("RunId") or "")
1629
+ seq_id = int(cp.get("SeqId") or 0)
1630
+ if seq_id < latest_by_run.get(run_id, 0):
1631
+ if cp.get("IsResumable") is True:
1632
+ cp["IsResumable"] = False
1633
+ cp["ResumeStatus"] = "disabled"
1634
+ cp["ResumeDisabledReason"] = (
1635
+ "新的恢复点已生成,此恢复点暂停恢复能力"
1636
+ )
1637
+ metadata["resume_disabled_reason"] = (
1638
+ "新的恢复点已生成,此恢复点暂停恢复能力"
1639
+ )
1640
+ metadata["resume_status"] = "disabled"
1641
+ cp["Metadata"] = metadata
1642
+
1643
+ return checkpoints
1644
+
1645
+
1646
+ def _check_adk_latest_resumable(
1647
+ checkpoint: dict[str, Any],
1648
+ events: list,
1649
+ ) -> dict[str, Any]:
1650
+ """P1.4: For a single ADK only_latest_resumable checkpoint, verify it is
1651
+ the latest for its RunId. If not, mark IsResumable=False."""
1652
+ metadata = checkpoint.get("Metadata") or {}
1653
+ if not metadata.get("only_latest_resumable"):
1654
+ return checkpoint
1655
+
1656
+ run_id = str(checkpoint.get("RunId") or "")
1657
+ my_seq_id = int(checkpoint.get("SeqId") or 0)
1658
+
1659
+ max_seq_id = my_seq_id
1660
+ for event in events:
1661
+ if event.event_type != "run_checkpoint":
1662
+ continue
1663
+ ev_meta = event.metadata or {}
1664
+ if str(ev_meta.get("run_id") or "") != run_id:
1665
+ continue
1666
+ seq_id = int(event.seq_id or 0)
1667
+ if seq_id > max_seq_id:
1668
+ max_seq_id = seq_id
1669
+
1670
+ if my_seq_id < max_seq_id and checkpoint.get("IsResumable") is True:
1671
+ checkpoint["IsResumable"] = False
1672
+ checkpoint["ResumeStatus"] = "disabled"
1673
+ checkpoint["ResumeDisabledReason"] = (
1674
+ "新的恢复点已生成,此恢复点暂停恢复能力"
1675
+ )
1676
+ metadata["resume_disabled_reason"] = (
1677
+ "新的恢复点已生成,此恢复点暂停恢复能力"
1678
+ )
1679
+ metadata["resume_status"] = "disabled"
1680
+ checkpoint["Metadata"] = metadata
1681
+
1682
+ return checkpoint
1683
+
1684
+
1599
1685
  _SIDE_EFFECT_TOOL_NAMES = {
1600
1686
  "write_workspace_file",
1601
1687
  "write_workspace_files",
@@ -1729,6 +1815,7 @@ async def _find_session_checkpoint(
1729
1815
  continue
1730
1816
  if checkpoint["CheckpointId"] != checkpoint_id:
1731
1817
  continue
1818
+ checkpoint = _check_adk_latest_resumable(checkpoint, events)
1732
1819
  return checkpoint
1733
1820
  return None
1734
1821
 
@@ -1951,6 +2038,12 @@ async def get_agent_ui_bootstrap(request: UiBootstrapRequest):
1951
2038
  if isinstance(runtime_capabilities, Mapping)
1952
2039
  else {},
1953
2040
  }
2041
+ checkpoint_resume_supported = bool(checkpoint_resume_capability["Supported"])
2042
+ cancel_run_supported = bool(
2043
+ (runtime_capabilities.get("CancelRun") or {}).get("Supported")
2044
+ if isinstance(runtime_capabilities, Mapping)
2045
+ else False
2046
+ )
1954
2047
  return _action_response(
1955
2048
  "GetAgentUiBootstrap",
1956
2049
  {
@@ -1966,17 +2059,17 @@ async def get_agent_ui_bootstrap(request: UiBootstrapRequest):
1966
2059
  "WorkspaceFiles": workspace_enabled,
1967
2060
  "Approval": True,
1968
2061
  "Thinking": True,
1969
- "StopRun": True,
1970
- "ResumeRun": True,
2062
+ "StopRun": cancel_run_supported,
2063
+ "ResumeRun": checkpoint_resume_supported,
1971
2064
  "RuntimeCapabilities": runtime_capabilities,
1972
2065
  "CheckpointResumeCapability": checkpoint_resume_capability,
1973
2066
  "RunLifecycle": {
1974
2067
  "Enabled": True,
1975
2068
  "Resume": True,
1976
2069
  "Abort": True,
1977
- "Checkpoints": True,
1978
- "CheckpointResume": True,
1979
- "CheckpointResumePreview": True,
2070
+ "Checkpoints": checkpoint_resume_supported,
2071
+ "CheckpointResume": checkpoint_resume_supported,
2072
+ "CheckpointResumePreview": checkpoint_resume_supported,
1980
2073
  },
1981
2074
  "MCP": False,
1982
2075
  "HostedRuntime": False,
@@ -2083,6 +2176,51 @@ async def list_session_events_action(request: ListSessionEventsActionRequest):
2083
2176
  )
2084
2177
 
2085
2178
 
2179
+ @app.post("/agentengine/api/v1/ListSessionMessages")
2180
+ async def list_session_messages_action(request: ListSessionMessagesActionRequest):
2181
+ from ksadk.conversations.message_projection import project_session_messages
2182
+
2183
+ service = resolve_session_service()
2184
+ events = await service.get_events(
2185
+ request.SessionId,
2186
+ offset=0,
2187
+ limit=2000,
2188
+ after_seq_id=request.AfterSeqId,
2189
+ before_seq_id=request.BeforeSeqId,
2190
+ )
2191
+ serialized_events = [_event_to_action_payload(event) for event in events]
2192
+ messages = project_session_messages(
2193
+ serialized_events,
2194
+ include_reasoning=request.IncludeReasoning,
2195
+ include_tool_events=request.IncludeToolEvents,
2196
+ include_attachments=request.IncludeAttachments,
2197
+ )
2198
+ if request.AfterSeqId is not None:
2199
+ page = messages
2200
+ has_more = False
2201
+ next_cursor = None
2202
+ else:
2203
+ page = messages[-request.Limit :]
2204
+ minimum_seq_id = int(page[0].get("SeqId") or 0) if page else 0
2205
+ has_more = minimum_seq_id > 1 or len(serialized_events) >= 2000
2206
+ next_cursor = minimum_seq_id - 1 if has_more else None
2207
+ latest_seq_id = (
2208
+ int(page[-1].get("SeqId") or 0)
2209
+ if page
2210
+ else max((int(event.get("SeqId") or 0) for event in serialized_events), default=0)
2211
+ )
2212
+ return _action_response(
2213
+ "ListSessionMessages",
2214
+ {
2215
+ "SessionId": request.SessionId,
2216
+ "Messages": page,
2217
+ "LatestSeqId": latest_seq_id,
2218
+ "HasMore": has_more,
2219
+ "NextCursor": next_cursor,
2220
+ },
2221
+ )
2222
+
2223
+
2086
2224
  def _count_resumable_checkpoints(checkpoints: list[dict[str, Any]]) -> int:
2087
2225
  """统计可恢复 checkpoint 数量。
2088
2226
 
@@ -2127,6 +2265,7 @@ async def _list_checkpoints_payload(request: ListSessionCheckpointsActionRequest
2127
2265
  continue
2128
2266
  # ResumableTotal 在 OnlyResumable 过滤前统计全量可恢复数(RunId/Framework 范围内)
2129
2267
  checkpoints.append(checkpoint)
2268
+ checkpoints = _apply_adk_only_latest_resumable(checkpoints)
2130
2269
  resumable_total = _count_resumable_checkpoints(checkpoints)
2131
2270
  if request.OnlyResumable:
2132
2271
  checkpoints = [cp for cp in checkpoints if cp.get("IsResumable") is True]
@@ -2202,6 +2341,8 @@ async def get_checkpoint_resume_preview_action(request: GetCheckpointResumePrevi
2202
2341
  if checkpoint is None:
2203
2342
  raise HTTPException(status_code=404, detail="Checkpoint not found")
2204
2343
 
2344
+ checkpoint = _check_adk_latest_resumable(checkpoint, events)
2345
+
2205
2346
  return _action_response(
2206
2347
  "GetCheckpointResumePreview",
2207
2348
  {"Preview": _build_checkpoint_resume_preview(checkpoint=checkpoint, events=events)},
@@ -2367,7 +2508,8 @@ async def subscribe_run_events_action(
2367
2508
  yield "data: [DONE]\n\n"
2368
2509
  return
2369
2510
 
2370
- # 重连兜底:本轮无新事件时,查全量确认 run 是否已有 terminal(客户端断连期间 run 已结束)。
2511
+ # 重连兜底:本轮无新事件时,查全量确认 run 是否已有 terminal
2512
+ # (客户端断连期间 run 已结束)。
2371
2513
  # 正常流式期间不触发此查询,保持增量收益。
2372
2514
  if not matched_events:
2373
2515
  all_events = await service.get_events(session_id)
@@ -1 +1 @@
1
- import{bt as e,ct as t,gt as n,lt as r,n as i,t as a,ut as o,wt as s,xt as c}from"./index-BH7-pC0B.js";var l=s(c(),1),u=t();function d(){let t=e(e=>e.visible),s=e(e=>e.content),c=e(e=>e.type),d=e(e=>e.hide),f=(0,l.useRef)(null);if(i(f),!t||!s)return null;let p=a(s);return(0,u.jsxs)(`aside`,{className:r(`flex h-full flex-shrink-0 flex-col border-l border-slate-200/30 bg-white dark:border-slate-800/40 dark:bg-slate-950`,`w-[min(42rem,50vw)]`),children:[(0,u.jsxs)(`div`,{className:`flex h-14 flex-shrink-0 items-center justify-between border-b border-slate-200/30 px-4 dark:border-slate-800/40`,children:[(0,u.jsxs)(`div`,{className:`flex min-w-0 items-center gap-2`,children:[(0,u.jsx)(`span`,{className:`text-sm font-semibold text-slate-900 dark:text-slate-100`,children:`Artifact`}),(0,u.jsx)(`span`,{className:`rounded-md bg-slate-100 px-1.5 py-0.5 font-mono text-[11px] text-slate-500 dark:bg-slate-900 dark:text-slate-400`,children:c})]}),(0,u.jsxs)(`div`,{className:`flex items-center gap-1.5`,children:[(0,u.jsxs)(`button`,{type:`button`,onClick:()=>{let e=new Blob([s],{type:`text/html;charset=utf-8`}),t=URL.createObjectURL(e),n=document.createElement(`a`);n.href=t,n.download=`artifact.${c===`svg`?`svg`:`html`}`,document.body.appendChild(n),n.click(),document.body.removeChild(n),URL.revokeObjectURL(t)},className:`inline-flex items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-xs font-medium text-slate-600 transition hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-900`,title:`Download`,children:[(0,u.jsx)(n,{className:`h-3.5 w-3.5`}),`Download`]}),(0,u.jsx)(`button`,{type:`button`,onClick:d,className:`rounded-lg p-1.5 text-slate-500 transition hover:bg-slate-100 hover:text-slate-900 dark:text-slate-400 dark:hover:bg-slate-900 dark:hover:text-slate-100`,"aria-label":`Close Artifact`,title:`Close`,children:(0,u.jsx)(o,{className:`h-4 w-4`})})]})]}),(0,u.jsx)(`div`,{className:`min-h-0 flex-1`,children:(0,u.jsx)(`iframe`,{ref:f,srcDoc:p,sandbox:`allow-scripts allow-downloads`,title:`Artifact Preview`,className:`h-full w-full border-0 bg-white`})})]})}export{d as ArtifactsPanel};
1
+ import{bt as e,ct as t,gt as n,lt as r,n as i,t as a,ut as o,wt as s,xt as c}from"./index-C00Lwg3L.js";var l=s(c(),1),u=t();function d(){let t=e(e=>e.visible),s=e(e=>e.content),c=e(e=>e.type),d=e(e=>e.hide),f=(0,l.useRef)(null);if(i(f),!t||!s)return null;let p=a(s);return(0,u.jsxs)(`aside`,{className:r(`flex h-full flex-shrink-0 flex-col border-l border-slate-200/30 bg-white dark:border-slate-800/40 dark:bg-slate-950`,`w-[min(42rem,50vw)]`),children:[(0,u.jsxs)(`div`,{className:`flex h-14 flex-shrink-0 items-center justify-between border-b border-slate-200/30 px-4 dark:border-slate-800/40`,children:[(0,u.jsxs)(`div`,{className:`flex min-w-0 items-center gap-2`,children:[(0,u.jsx)(`span`,{className:`text-sm font-semibold text-slate-900 dark:text-slate-100`,children:`Artifact`}),(0,u.jsx)(`span`,{className:`rounded-md bg-slate-100 px-1.5 py-0.5 font-mono text-[11px] text-slate-500 dark:bg-slate-900 dark:text-slate-400`,children:c})]}),(0,u.jsxs)(`div`,{className:`flex items-center gap-1.5`,children:[(0,u.jsxs)(`button`,{type:`button`,onClick:()=>{let e=new Blob([s],{type:`text/html;charset=utf-8`}),t=URL.createObjectURL(e),n=document.createElement(`a`);n.href=t,n.download=`artifact.${c===`svg`?`svg`:`html`}`,document.body.appendChild(n),n.click(),document.body.removeChild(n),URL.revokeObjectURL(t)},className:`inline-flex items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-xs font-medium text-slate-600 transition hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-900`,title:`Download`,children:[(0,u.jsx)(n,{className:`h-3.5 w-3.5`}),`Download`]}),(0,u.jsx)(`button`,{type:`button`,onClick:d,className:`rounded-lg p-1.5 text-slate-500 transition hover:bg-slate-100 hover:text-slate-900 dark:text-slate-400 dark:hover:bg-slate-900 dark:hover:text-slate-100`,"aria-label":`Close Artifact`,title:`Close`,children:(0,u.jsx)(o,{className:`h-4 w-4`})})]})]}),(0,u.jsx)(`div`,{className:`min-h-0 flex-1`,children:(0,u.jsx)(`iframe`,{ref:f,srcDoc:p,sandbox:`allow-scripts allow-downloads`,title:`Artifact Preview`,className:`h-full w-full border-0 bg-white`})})]})}export{d as ArtifactsPanel};