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.
- {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.7.0.dist-info}/METADATA +6 -4
- {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.7.0.dist-info}/RECORD +123 -128
- ksadk/api/client.py +19 -0
- ksadk/cli/cmd_create.py +34 -7
- ksadk/cli/cmd_hermes.py +6 -2
- ksadk/cli/cmd_invoke.py +208 -41
- ksadk/cli/cmd_mcp.py +6 -1
- ksadk/cli/cmd_openclaw.py +6 -2
- ksadk/cli/cmd_run.py +13 -6
- ksadk/cli/network_options.py +5 -3
- ksadk/configs/env_registry.py +1 -0
- ksadk/conversations/message_projection.py +269 -0
- ksadk/conversations/model_context.py +11 -1
- ksadk/conversations/runtime.py +81 -13
- ksadk/deployment/base.py +1 -1
- ksadk/deployment/providers/serverless.py +18 -8
- ksadk/memory/adk/resilient_session_service.py +268 -0
- ksadk/memory/adk/short_term_memory.py +21 -6
- ksadk/runners/adk_runner.py +974 -115
- ksadk/runners/langchain_runner.py +219 -7
- ksadk/runners/langgraph_runner.py +145 -12
- ksadk/runners/remote_runner.py +143 -18
- ksadk/server/app.py +148 -6
- ksadk/server/static/assets/{ArtifactsPanel-C0meuOdi.js → ArtifactsPanel-D-SSn2ee.js} +1 -1
- ksadk/server/static/assets/{CodeBlock-BbI4HPtJ.js → CodeBlock-P16VOLYi.js} +1 -1
- ksadk/server/static/assets/{MathMessageMarkdown-DMLQYoLp.js → MathMessageMarkdown-CdGiQWHR.js} +1 -1
- ksadk/server/static/assets/{MermaidBlock-DCR8WZ17.js → MermaidBlock-IOcal8cm.js} +6 -6
- ksadk/server/static/assets/{NativeTerminalPanel-CKUvWhUz.js → NativeTerminalPanel-DGSDWsei.js} +1 -1
- ksadk/server/static/assets/{arc-bj2rZnP5.js → arc-BA13sHc9.js} +1 -1
- ksadk/server/static/assets/architecture-7EHR7CIX-BvUlsA6l.js +1 -0
- ksadk/server/static/assets/{architectureDiagram-3BPJPVTR-2u1ltDIj.js → architectureDiagram-3BPJPVTR-DzJzgVoL.js} +1 -1
- ksadk/server/static/assets/{blockDiagram-GPEHLZMM-D6fvdK20.js → blockDiagram-GPEHLZMM-UTdVcZhn.js} +1 -1
- ksadk/server/static/assets/{c4Diagram-AAUBKEIU-BK82L5n5.js → c4Diagram-AAUBKEIU-Dix0GD4m.js} +1 -1
- ksadk/server/static/assets/channel-CTuydARX.js +1 -0
- ksadk/server/static/assets/{chunk-2J33WTMH-DLVdkeyQ.js → chunk-2J33WTMH-C2JxuZ42.js} +1 -1
- ksadk/server/static/assets/{chunk-4BX2VUAB-BbwsYcbW.js → chunk-4BX2VUAB-Be57QPgD.js} +1 -1
- ksadk/server/static/assets/{chunk-55IACEB6-DOairxzw.js → chunk-55IACEB6-WjSuy4nv.js} +1 -1
- ksadk/server/static/assets/{chunk-727SXJPM-hCO487gs.js → chunk-727SXJPM-BOovP-b5.js} +1 -1
- ksadk/server/static/assets/{chunk-AQP2D5EJ-XU8vDOuG.js → chunk-AQP2D5EJ-DOmZNmWj.js} +1 -1
- ksadk/server/static/assets/{chunk-FMBD7UC4-PrapyfMu.js → chunk-FMBD7UC4-DzwlodkU.js} +1 -1
- ksadk/server/static/assets/{chunk-ND2GUHAM-CboKhTZ3.js → chunk-ND2GUHAM-DMqAikEU.js} +1 -1
- ksadk/server/static/assets/chunk-QZHKN3VN-BnCydivS.js +1 -0
- ksadk/server/static/assets/classDiagram-4FO5ZUOK-BNHg45Be.js +1 -0
- ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-BNHg45Be.js +1 -0
- ksadk/server/static/assets/{cose-bilkent-S5V4N54A-_N8bJ-EU.js → cose-bilkent-S5V4N54A-Zhy2GBnP.js} +1 -1
- ksadk/server/static/assets/{dagre-BM42HDAG-FoDlo5J2.js → dagre-BM42HDAG-CbKrtIUz.js} +1 -1
- ksadk/server/static/assets/{diagram-2AECGRRQ-BBp6ebBw.js → diagram-2AECGRRQ-BRApgQRK.js} +1 -1
- ksadk/server/static/assets/{diagram-5GNKFQAL-CqOvG1PR.js → diagram-5GNKFQAL-D0xbnDiK.js} +1 -1
- ksadk/server/static/assets/{diagram-KO2AKTUF-O9T3t6ba.js → diagram-KO2AKTUF-CVugVI9L.js} +1 -1
- ksadk/server/static/assets/{diagram-LMA3HP47-CGG8ekBO.js → diagram-LMA3HP47-DozUGgee.js} +1 -1
- ksadk/server/static/assets/{diagram-OG6HWLK6-4BmH1EEg.js → diagram-OG6HWLK6-ZOYsKlru.js} +1 -1
- ksadk/server/static/assets/{dist-BmuTWcm7.js → dist-2hInLS_J.js} +1 -1
- ksadk/server/static/assets/{dist-BMKM66B4.js → dist-8EEyGgDx.js} +1 -1
- ksadk/server/static/assets/{dist-Cp1_jKwf.js → dist-8_SfK1IP.js} +1 -1
- ksadk/server/static/assets/{dist-CZ6C3x8B.js → dist-B0NP_Z0z.js} +1 -1
- ksadk/server/static/assets/{dist-DLeM1OCK.js → dist-BIJVDMuB.js} +1 -1
- ksadk/server/static/assets/{dist-Cwhk4lwI.js → dist-BmlMPsCc.js} +1 -1
- ksadk/server/static/assets/{dist-D99wf6_X.js → dist-BrCcQ47T.js} +1 -1
- ksadk/server/static/assets/{dist-CWiTDN_V.js → dist-BssVmjIo.js} +1 -1
- ksadk/server/static/assets/{dist-BVzcWa6D.js → dist-C3HIIaN5.js} +1 -1
- ksadk/server/static/assets/{dist-DM0rEQPk.js → dist-CPBtqRTW.js} +1 -1
- ksadk/server/static/assets/{dist-BkJjNih3.js → dist-CkEKJFtk.js} +1 -1
- ksadk/server/static/assets/{dist-B67uF2uK.js → dist-D-JMdWPQ.js} +1 -1
- ksadk/server/static/assets/{dist-DI-oVAXw.js → dist-D-xOD048.js} +1 -1
- ksadk/server/static/assets/{dist-6kSZvCZh.js → dist-D57wnGWd.js} +1 -1
- ksadk/server/static/assets/{dist-Bk1l05Ni.js → dist-DL_NKc_z.js} +1 -1
- ksadk/server/static/assets/{dist-DbaH3p5h.js → dist-DMezvBGJ.js} +1 -1
- ksadk/server/static/assets/{dist-CiZHB6ZW.js → dist-DmFaq_Iy.js} +1 -1
- ksadk/server/static/assets/{dist-DLMjL01k.js → dist-Dp0k7bWI.js} +1 -1
- ksadk/server/static/assets/{dist-DCpZrjaK.js → dist-DpKtt4Bo.js} +1 -1
- ksadk/server/static/assets/{dist-D5JGcxBx.js → dist-DyOkZmze.js} +1 -1
- ksadk/server/static/assets/{dist-C2DKRFVK.js → dist-DyWsb78Q.js} +1 -1
- ksadk/server/static/assets/{dist-S4klkK5W.js → dist-gX6GOLKq.js} +1 -1
- ksadk/server/static/assets/{erDiagram-TEJ5UH35-oJhaci7S.js → erDiagram-TEJ5UH35-Bk-9bgE5.js} +1 -1
- ksadk/server/static/assets/eventmodeling-FCH6USID-DsAgWnlf.js +1 -0
- ksadk/server/static/assets/{flowDiagram-I6XJVG4X-DtTmUan-.js → flowDiagram-I6XJVG4X-Dn9kZVuZ.js} +1 -1
- ksadk/server/static/assets/{ganttDiagram-6RSMTGT7-BSsb_eij.js → ganttDiagram-6RSMTGT7-BGxXlJqm.js} +1 -1
- ksadk/server/static/assets/gitGraph-WXDBUCRP-BIao9Yjg.js +1 -0
- ksadk/server/static/assets/{gitGraphDiagram-PVQCEYII-BR5s6Pws.js → gitGraphDiagram-PVQCEYII-C7Q0X-wi.js} +1 -1
- ksadk/server/static/assets/{index-BH7-pC0B.js → index-C00Lwg3L.js} +6 -6
- ksadk/server/static/assets/info-J43DQDTF-CyipqGxU.js +1 -0
- ksadk/server/static/assets/{infoDiagram-5YYISTIA-DYPc5cvh.js → infoDiagram-5YYISTIA-BO_CSqq5.js} +1 -1
- ksadk/server/static/assets/{ishikawaDiagram-YF4QCWOH-MoBdALDg.js → ishikawaDiagram-YF4QCWOH-eYLPLd46.js} +1 -1
- ksadk/server/static/assets/{journeyDiagram-JHISSGLW-DgLiQT8l.js → journeyDiagram-JHISSGLW-De9geSb0.js} +1 -1
- ksadk/server/static/assets/{kanban-definition-UN3LZRKU-CsFx112F.js → kanban-definition-UN3LZRKU-Cs80kQto.js} +1 -1
- ksadk/server/static/assets/{katex-DRsiwpM2.js → katex-DSrCFD8n.js} +1 -1
- ksadk/server/static/assets/{linear-TNRQ5Orv.js → linear-CN9sbKTF.js} +1 -1
- ksadk/server/static/assets/{mermaid-parser.core-D2eMVOgo.js → mermaid-parser.core-DlgSBU8l.js} +2 -2
- ksadk/server/static/assets/{mindmap-definition-RKZ34NQL-BtByYFhL.js → mindmap-definition-RKZ34NQL-9afz7Oug.js} +1 -1
- ksadk/server/static/assets/packet-YPE3B663-CGkyRxlg.js +1 -0
- ksadk/server/static/assets/pie-LRSECV5Y-M0uWAf4n.js +1 -0
- ksadk/server/static/assets/{pieDiagram-4H26LBE5-DPnuCB8c.js → pieDiagram-4H26LBE5-DPv_VstN.js} +1 -1
- ksadk/server/static/assets/{quadrantDiagram-W4KKPZXB-miFpCPv4.js → quadrantDiagram-W4KKPZXB-o77wZ1XK.js} +1 -1
- ksadk/server/static/assets/radar-GUYGQ44K-Bdbm24-x.js +1 -0
- ksadk/server/static/assets/{requirementDiagram-4Y6WPE33-mjB_yYkW.js → requirementDiagram-4Y6WPE33-BYVw81VC.js} +1 -1
- ksadk/server/static/assets/{sankeyDiagram-5OEKKPKP-D7hSlZZZ.js → sankeyDiagram-5OEKKPKP-B7nZFMKT.js} +1 -1
- ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-D9Cx6hC3.js → sequenceDiagram-3UESZ5HK-BGRRrFw5.js} +1 -1
- ksadk/server/static/assets/{stateDiagram-AJRCARHV-D_zrg47o.js → stateDiagram-AJRCARHV-BUfHCoPs.js} +1 -1
- ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-8xBN_gLM.js +1 -0
- ksadk/server/static/assets/{timeline-definition-PNZ67QCA-CwSec0Wa.js → timeline-definition-PNZ67QCA-FbCTGrf9.js} +1 -1
- ksadk/server/static/assets/treeView-BLDUP644-npMo0ak1.js +1 -0
- ksadk/server/static/assets/treemap-LRROVOQU-CHdzrrdo.js +1 -0
- ksadk/server/static/assets/{vennDiagram-CIIHVFJN-agCVobiF.js → vennDiagram-CIIHVFJN-DWgwtPsJ.js} +1 -1
- ksadk/server/static/assets/wardley-L42UT6IY-BEQz7wyy.js +1 -0
- ksadk/server/static/assets/{wardleyDiagram-YWT4CUSO-TmrlhWNt.js → wardleyDiagram-YWT4CUSO-2x8G-tTM.js} +1 -1
- ksadk/server/static/assets/{xychartDiagram-2RQKCTM6-DV71V-ah.js → xychartDiagram-2RQKCTM6-Drma3AYl.js} +1 -1
- ksadk/server/static/index.html +1 -1
- ksadk/sessions/__init__.py +17 -21
- ksadk/sessions/continuity.py +28 -4
- ksadk/sessions/postgres_service.py +77 -14
- ksadk/sessions/resilience.py +52 -0
- ksadk/sessions/resilient.py +360 -0
- ksadk/toolsets/__init__.py +2 -0
- ksadk/toolsets/workspace.py +49 -10
- ksadk/tracing/setup.py +63 -0
- ksadk/tui/__init__.py +22 -7
- ksadk/tui/loop.py +1877 -0
- ksadk/tui/stream_render.py +68 -0
- ksadk/version.py +1 -1
- ksadk/server/static/assets/architecture-7EHR7CIX-Cr6Xz1_c.js +0 -1
- ksadk/server/static/assets/channel-BSSOwFIb.js +0 -1
- ksadk/server/static/assets/chunk-QZHKN3VN-XXpdiwUL.js +0 -1
- ksadk/server/static/assets/classDiagram-4FO5ZUOK-C5HvGiyK.js +0 -1
- ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-C5HvGiyK.js +0 -1
- ksadk/server/static/assets/eventmodeling-FCH6USID-CIgwHJLL.js +0 -1
- ksadk/server/static/assets/gitGraph-WXDBUCRP-BVAJ7uo0.js +0 -1
- ksadk/server/static/assets/info-J43DQDTF-MT80V4ge.js +0 -1
- ksadk/server/static/assets/packet-YPE3B663-BLKhbXZB.js +0 -1
- ksadk/server/static/assets/pie-LRSECV5Y-DFOikjSY.js +0 -1
- ksadk/server/static/assets/radar-GUYGQ44K-095b1Ikw.js +0 -1
- ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-J9kCb0nC.js +0 -1
- ksadk/server/static/assets/treeView-BLDUP644-yinfJEY6.js +0 -1
- ksadk/server/static/assets/treemap-LRROVOQU-DosUqN1Q.js +0 -1
- ksadk/server/static/assets/wardley-L42UT6IY-Bvp7-5uH.js +0 -1
- ksadk/tui/app.py +0 -400
- ksadk/tui/clipboard.py +0 -100
- ksadk/tui/widgets/__init__.py +0 -22
- ksadk/tui/widgets/assistant.py +0 -89
- ksadk/tui/widgets/base.py +0 -54
- ksadk/tui/widgets/chat_input.py +0 -246
- ksadk/tui/widgets/history.py +0 -156
- ksadk/tui/widgets/system.py +0 -87
- ksadk/tui/widgets/thinking.py +0 -110
- ksadk/tui/widgets/tool_call.py +0 -161
- ksadk/tui/widgets/user.py +0 -37
- {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.7.0.dist-info}/WHEEL +0 -0
- {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.7.0.dist-info}/entry_points.txt +0 -0
- {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.7.0.dist-info}/licenses/LICENSE +0 -0
- {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.7.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
from google.adk.events.event import Event
|
|
8
|
+
from google.adk.sessions import InMemorySessionService
|
|
9
|
+
from google.adk.sessions.base_session_service import (
|
|
10
|
+
BaseSessionService,
|
|
11
|
+
GetSessionConfig,
|
|
12
|
+
ListSessionsResponse,
|
|
13
|
+
)
|
|
14
|
+
from google.adk.sessions.session import Session
|
|
15
|
+
|
|
16
|
+
from ksadk.sessions.resilience import is_session_backend_failure
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ResilientADKSessionService(BaseSessionService):
|
|
22
|
+
"""Run ADK sessions locally while mirroring them to durable storage."""
|
|
23
|
+
|
|
24
|
+
_probe_interval_seconds: float = 30.0
|
|
25
|
+
|
|
26
|
+
def __init__(self, primary: BaseSessionService) -> None:
|
|
27
|
+
self.primary = primary
|
|
28
|
+
self.live = InMemorySessionService()
|
|
29
|
+
self._primary_sessions: dict[tuple[str, str, str], Session] = {}
|
|
30
|
+
self._primary_enabled = True
|
|
31
|
+
self._probe_task: asyncio.Task[None] | None = None
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def degraded(self) -> bool:
|
|
35
|
+
return not self._primary_enabled
|
|
36
|
+
|
|
37
|
+
def _degrade(self, exc: Exception) -> None:
|
|
38
|
+
if not self._primary_enabled:
|
|
39
|
+
return
|
|
40
|
+
self._primary_enabled = False
|
|
41
|
+
logger.error(
|
|
42
|
+
"ADK session persistence degraded; using in-memory live session: %s",
|
|
43
|
+
exc,
|
|
44
|
+
extra={
|
|
45
|
+
"session_backend_state": "degraded",
|
|
46
|
+
"session_backend": type(self.primary).__name__,
|
|
47
|
+
},
|
|
48
|
+
)
|
|
49
|
+
self._start_probe()
|
|
50
|
+
|
|
51
|
+
def _handle_primary_failure(self, exc: Exception) -> bool:
|
|
52
|
+
if not is_session_backend_failure(exc):
|
|
53
|
+
return False
|
|
54
|
+
self._degrade(exc)
|
|
55
|
+
return True
|
|
56
|
+
|
|
57
|
+
def _start_probe(self) -> None:
|
|
58
|
+
if self._probe_task is not None and not self._probe_task.done():
|
|
59
|
+
return
|
|
60
|
+
self._probe_task = asyncio.create_task(self._probe_loop())
|
|
61
|
+
|
|
62
|
+
async def _probe_loop(self) -> None:
|
|
63
|
+
while not self._primary_enabled:
|
|
64
|
+
await asyncio.sleep(self._probe_interval_seconds)
|
|
65
|
+
if self._primary_enabled:
|
|
66
|
+
break
|
|
67
|
+
try:
|
|
68
|
+
await self.primary.get_session(
|
|
69
|
+
app_name="__ksadk_probe__",
|
|
70
|
+
user_id="__ksadk_probe__",
|
|
71
|
+
session_id="__ksadk_probe__",
|
|
72
|
+
)
|
|
73
|
+
except Exception:
|
|
74
|
+
continue
|
|
75
|
+
self._primary_enabled = True
|
|
76
|
+
logger.info(
|
|
77
|
+
"ADK session persistence recovered; durable backend re-enabled",
|
|
78
|
+
extra={
|
|
79
|
+
"session_backend_state": "recovered",
|
|
80
|
+
"session_backend": type(self.primary).__name__,
|
|
81
|
+
},
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
@staticmethod
|
|
85
|
+
def _key(app_name: str, user_id: str, session_id: str) -> tuple[str, str, str]:
|
|
86
|
+
return app_name, user_id, session_id
|
|
87
|
+
|
|
88
|
+
async def _hydrate(self, durable: Session) -> Session:
|
|
89
|
+
existing = await self.live.get_session(
|
|
90
|
+
app_name=durable.app_name,
|
|
91
|
+
user_id=durable.user_id,
|
|
92
|
+
session_id=durable.id,
|
|
93
|
+
)
|
|
94
|
+
if existing is None:
|
|
95
|
+
existing = await self.live.create_session(
|
|
96
|
+
app_name=durable.app_name,
|
|
97
|
+
user_id=durable.user_id,
|
|
98
|
+
state=durable.state,
|
|
99
|
+
session_id=durable.id,
|
|
100
|
+
)
|
|
101
|
+
existing_ids = {event.id for event in existing.events}
|
|
102
|
+
for event in durable.events:
|
|
103
|
+
if event.id not in existing_ids:
|
|
104
|
+
await self.live.append_event(existing, event)
|
|
105
|
+
self._primary_sessions[self._key(durable.app_name, durable.user_id, durable.id)] = durable
|
|
106
|
+
hydrated = await self.live.get_session(
|
|
107
|
+
app_name=durable.app_name,
|
|
108
|
+
user_id=durable.user_id,
|
|
109
|
+
session_id=durable.id,
|
|
110
|
+
)
|
|
111
|
+
if hydrated is None:
|
|
112
|
+
raise RuntimeError(f"Failed to hydrate ADK live session {durable.id}")
|
|
113
|
+
return hydrated
|
|
114
|
+
|
|
115
|
+
async def create_session(
|
|
116
|
+
self,
|
|
117
|
+
*,
|
|
118
|
+
app_name: str,
|
|
119
|
+
user_id: str,
|
|
120
|
+
state: Optional[dict[str, Any]] = None,
|
|
121
|
+
session_id: Optional[str] = None,
|
|
122
|
+
) -> Session:
|
|
123
|
+
if session_id:
|
|
124
|
+
existing = await self.live.get_session(
|
|
125
|
+
app_name=app_name,
|
|
126
|
+
user_id=user_id,
|
|
127
|
+
session_id=session_id,
|
|
128
|
+
)
|
|
129
|
+
if self._primary_enabled:
|
|
130
|
+
try:
|
|
131
|
+
durable = await self.primary.get_session(
|
|
132
|
+
app_name=app_name,
|
|
133
|
+
user_id=user_id,
|
|
134
|
+
session_id=session_id,
|
|
135
|
+
)
|
|
136
|
+
if durable is not None:
|
|
137
|
+
return await self._hydrate(durable)
|
|
138
|
+
except Exception as exc:
|
|
139
|
+
if not self._handle_primary_failure(exc):
|
|
140
|
+
raise
|
|
141
|
+
if existing is not None:
|
|
142
|
+
return existing
|
|
143
|
+
|
|
144
|
+
live = await self.live.create_session(
|
|
145
|
+
app_name=app_name,
|
|
146
|
+
user_id=user_id,
|
|
147
|
+
state=state,
|
|
148
|
+
session_id=session_id,
|
|
149
|
+
)
|
|
150
|
+
if self._primary_enabled:
|
|
151
|
+
try:
|
|
152
|
+
durable = await self.primary.create_session(
|
|
153
|
+
app_name=app_name,
|
|
154
|
+
user_id=user_id,
|
|
155
|
+
state=state,
|
|
156
|
+
session_id=live.id,
|
|
157
|
+
)
|
|
158
|
+
self._primary_sessions[self._key(app_name, user_id, live.id)] = durable
|
|
159
|
+
except Exception as exc:
|
|
160
|
+
if not self._handle_primary_failure(exc):
|
|
161
|
+
raise
|
|
162
|
+
return live
|
|
163
|
+
|
|
164
|
+
async def get_session(
|
|
165
|
+
self,
|
|
166
|
+
*,
|
|
167
|
+
app_name: str,
|
|
168
|
+
user_id: str,
|
|
169
|
+
session_id: str,
|
|
170
|
+
config: Optional[GetSessionConfig] = None,
|
|
171
|
+
) -> Optional[Session]:
|
|
172
|
+
live = await self.live.get_session(
|
|
173
|
+
app_name=app_name,
|
|
174
|
+
user_id=user_id,
|
|
175
|
+
session_id=session_id,
|
|
176
|
+
config=config,
|
|
177
|
+
)
|
|
178
|
+
if not self._primary_enabled:
|
|
179
|
+
return live
|
|
180
|
+
try:
|
|
181
|
+
durable = await self.primary.get_session(
|
|
182
|
+
app_name=app_name,
|
|
183
|
+
user_id=user_id,
|
|
184
|
+
session_id=session_id,
|
|
185
|
+
config=config,
|
|
186
|
+
)
|
|
187
|
+
return await self._hydrate(durable) if durable is not None else live
|
|
188
|
+
except Exception as exc:
|
|
189
|
+
if not self._handle_primary_failure(exc):
|
|
190
|
+
raise
|
|
191
|
+
return live
|
|
192
|
+
|
|
193
|
+
async def list_sessions(
|
|
194
|
+
self,
|
|
195
|
+
*,
|
|
196
|
+
app_name: str,
|
|
197
|
+
user_id: Optional[str] = None,
|
|
198
|
+
) -> ListSessionsResponse:
|
|
199
|
+
if self._primary_enabled:
|
|
200
|
+
try:
|
|
201
|
+
durable = await self.primary.list_sessions(app_name=app_name, user_id=user_id)
|
|
202
|
+
for session in durable.sessions:
|
|
203
|
+
await self._hydrate(session)
|
|
204
|
+
except Exception as exc:
|
|
205
|
+
if not self._handle_primary_failure(exc):
|
|
206
|
+
raise
|
|
207
|
+
return await self.live.list_sessions(app_name=app_name, user_id=user_id)
|
|
208
|
+
|
|
209
|
+
async def delete_session(self, *, app_name: str, user_id: str, session_id: str) -> None:
|
|
210
|
+
await self.live.delete_session(app_name=app_name, user_id=user_id, session_id=session_id)
|
|
211
|
+
self._primary_sessions.pop(self._key(app_name, user_id, session_id), None)
|
|
212
|
+
if self._primary_enabled:
|
|
213
|
+
try:
|
|
214
|
+
await self.primary.delete_session(
|
|
215
|
+
app_name=app_name,
|
|
216
|
+
user_id=user_id,
|
|
217
|
+
session_id=session_id,
|
|
218
|
+
)
|
|
219
|
+
except Exception as exc:
|
|
220
|
+
if not self._handle_primary_failure(exc):
|
|
221
|
+
raise
|
|
222
|
+
|
|
223
|
+
async def append_event(self, session: Session, event: Event) -> Event:
|
|
224
|
+
stored = await self.live.append_event(session, event)
|
|
225
|
+
if not self._primary_enabled:
|
|
226
|
+
return stored
|
|
227
|
+
key = self._key(session.app_name, session.user_id, session.id)
|
|
228
|
+
durable_session = self._primary_sessions.get(key)
|
|
229
|
+
try:
|
|
230
|
+
if durable_session is None:
|
|
231
|
+
durable_session = await self.primary.get_session(
|
|
232
|
+
app_name=session.app_name,
|
|
233
|
+
user_id=session.user_id,
|
|
234
|
+
session_id=session.id,
|
|
235
|
+
)
|
|
236
|
+
if durable_session is None:
|
|
237
|
+
durable_session = await self.primary.create_session(
|
|
238
|
+
app_name=session.app_name,
|
|
239
|
+
user_id=session.user_id,
|
|
240
|
+
state=session.state,
|
|
241
|
+
session_id=session.id,
|
|
242
|
+
)
|
|
243
|
+
self._primary_sessions[key] = durable_session
|
|
244
|
+
await self.primary.append_event(durable_session, event)
|
|
245
|
+
except Exception as exc:
|
|
246
|
+
if not self._handle_primary_failure(exc):
|
|
247
|
+
raise
|
|
248
|
+
return stored
|
|
249
|
+
|
|
250
|
+
async def flush(self) -> None:
|
|
251
|
+
await self.live.flush()
|
|
252
|
+
if self._primary_enabled:
|
|
253
|
+
try:
|
|
254
|
+
await self.primary.flush()
|
|
255
|
+
except Exception as exc:
|
|
256
|
+
if not self._handle_primary_failure(exc):
|
|
257
|
+
raise
|
|
258
|
+
|
|
259
|
+
async def close(self) -> None:
|
|
260
|
+
if self._probe_task is not None and not self._probe_task.done():
|
|
261
|
+
self._probe_task.cancel()
|
|
262
|
+
try:
|
|
263
|
+
await self._probe_task
|
|
264
|
+
except asyncio.CancelledError:
|
|
265
|
+
pass
|
|
266
|
+
close = getattr(self.primary, "close", None)
|
|
267
|
+
if close is not None:
|
|
268
|
+
await close()
|
|
@@ -109,9 +109,7 @@ class ShortTermMemory(BaseModel):
|
|
|
109
109
|
def model_post_init(self, __context: Any) -> None:
|
|
110
110
|
# 优先使用 db_url
|
|
111
111
|
if self.db_url:
|
|
112
|
-
logger.info(
|
|
113
|
-
f"ShortTermMemory: using db_url (ignoring backend option)"
|
|
114
|
-
)
|
|
112
|
+
logger.info("ShortTermMemory: using db_url (ignoring backend option)")
|
|
115
113
|
self._init_database_service(self.db_url)
|
|
116
114
|
return
|
|
117
115
|
|
|
@@ -133,7 +131,8 @@ class ShortTermMemory(BaseModel):
|
|
|
133
131
|
case "database":
|
|
134
132
|
if not self.db_url:
|
|
135
133
|
raise ValueError(
|
|
136
|
-
"KSADK_SESSION_DSN is required when ADK session backend
|
|
134
|
+
"KSADK_SESSION_DSN is required when ADK session backend "
|
|
135
|
+
"resolves to database/postgres"
|
|
137
136
|
)
|
|
138
137
|
else:
|
|
139
138
|
self._init_database_service(self.db_url)
|
|
@@ -151,7 +150,20 @@ class ShortTermMemory(BaseModel):
|
|
|
151
150
|
try:
|
|
152
151
|
from google.adk.sessions import DatabaseSessionService
|
|
153
152
|
|
|
154
|
-
|
|
153
|
+
from ksadk.memory.adk.resilient_session_service import ResilientADKSessionService
|
|
154
|
+
from ksadk.sessions.resilience import session_backend_timeout_seconds
|
|
155
|
+
|
|
156
|
+
service_kwargs = {}
|
|
157
|
+
if normalized_db_url.startswith(("postgresql://", "postgresql+asyncpg://")):
|
|
158
|
+
timeout = session_backend_timeout_seconds()
|
|
159
|
+
service_kwargs["connect_args"] = {
|
|
160
|
+
"timeout": timeout,
|
|
161
|
+
"command_timeout": timeout,
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
self._session_service = ResilientADKSessionService(
|
|
165
|
+
DatabaseSessionService(db_url=normalized_db_url, **service_kwargs)
|
|
166
|
+
)
|
|
155
167
|
logger.info(
|
|
156
168
|
f"ShortTermMemory: using DatabaseSessionService "
|
|
157
169
|
f"({normalized_db_url[:30]}...)"
|
|
@@ -163,12 +175,14 @@ class ShortTermMemory(BaseModel):
|
|
|
163
175
|
"Falling back to InMemorySessionService."
|
|
164
176
|
)
|
|
165
177
|
self._session_service = InMemorySessionService()
|
|
178
|
+
self.backend = "local"
|
|
166
179
|
except Exception as e:
|
|
167
180
|
logger.error(
|
|
168
181
|
f"Failed to create DatabaseSessionService: {e}. "
|
|
169
182
|
f"Falling back to InMemorySessionService."
|
|
170
183
|
)
|
|
171
184
|
self._session_service = InMemorySessionService()
|
|
185
|
+
self.backend = "local"
|
|
172
186
|
|
|
173
187
|
@property
|
|
174
188
|
def session_service(self) -> BaseSessionService:
|
|
@@ -272,7 +286,8 @@ class ShortTermMemory(BaseModel):
|
|
|
272
286
|
backend = explicit_backend
|
|
273
287
|
if _session_backend_requires_database_url(backend) and not db_url:
|
|
274
288
|
raise ValueError(
|
|
275
|
-
"KSADK_SESSION_DSN is required when ADK session backend
|
|
289
|
+
"KSADK_SESSION_DSN is required when ADK session backend "
|
|
290
|
+
"resolves to database/postgres"
|
|
276
291
|
)
|
|
277
292
|
if not backend:
|
|
278
293
|
if db_url:
|