jdcloud-agentgrid 0.1.4__tar.gz → 0.2.1__tar.gz

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 (30) hide show
  1. {jdcloud_agentgrid-0.1.4 → jdcloud_agentgrid-0.2.1}/PKG-INFO +5 -4
  2. {jdcloud_agentgrid-0.1.4 → jdcloud_agentgrid-0.2.1}/README.md +2 -2
  3. {jdcloud_agentgrid-0.1.4 → jdcloud_agentgrid-0.2.1}/pyproject.toml +4 -2
  4. {jdcloud_agentgrid-0.1.4 → jdcloud_agentgrid-0.2.1}/src/agentgrid/apps/base_app.py +1 -1
  5. {jdcloud_agentgrid-0.1.4 → jdcloud_agentgrid-0.2.1}/src/agentgrid/apps/simple_app/simple_app_handlers.py +11 -2
  6. {jdcloud_agentgrid-0.1.4 → jdcloud_agentgrid-0.2.1}/src/agentgrid/config.py +1 -1
  7. {jdcloud_agentgrid-0.1.4 → jdcloud_agentgrid-0.2.1}/src/agentgrid/extensions/tools/browser_client.py +63 -17
  8. {jdcloud_agentgrid-0.1.4 → jdcloud_agentgrid-0.2.1}/src/agentgrid/extensions/tools/code_interpreter_client.py +32 -3
  9. {jdcloud_agentgrid-0.1.4 → jdcloud_agentgrid-0.2.1}/src/agentgrid/runtime/client.py +1 -1
  10. {jdcloud_agentgrid-0.1.4 → jdcloud_agentgrid-0.2.1}/src/agentgrid/session.py +9 -7
  11. {jdcloud_agentgrid-0.1.4 → jdcloud_agentgrid-0.2.1}/src/agentgrid/version.py +1 -1
  12. {jdcloud_agentgrid-0.1.4 → jdcloud_agentgrid-0.2.1}/.gitignore +0 -0
  13. {jdcloud_agentgrid-0.1.4 → jdcloud_agentgrid-0.2.1}/src/agentgrid/__init__.py +0 -0
  14. {jdcloud_agentgrid-0.1.4 → jdcloud_agentgrid-0.2.1}/src/agentgrid/apps/__init__.py +0 -0
  15. {jdcloud_agentgrid-0.1.4 → jdcloud_agentgrid-0.2.1}/src/agentgrid/apps/simple_app/__init__.py +0 -0
  16. {jdcloud_agentgrid-0.1.4 → jdcloud_agentgrid-0.2.1}/src/agentgrid/apps/simple_app/simple_app.py +0 -0
  17. {jdcloud_agentgrid-0.1.4 → jdcloud_agentgrid-0.2.1}/src/agentgrid/auth/__init__.py +0 -0
  18. {jdcloud_agentgrid-0.1.4 → jdcloud_agentgrid-0.2.1}/src/agentgrid/auth/credential_chain.py +0 -0
  19. {jdcloud_agentgrid-0.1.4 → jdcloud_agentgrid-0.2.1}/src/agentgrid/auth/providers.py +0 -0
  20. {jdcloud_agentgrid-0.1.4 → jdcloud_agentgrid-0.2.1}/src/agentgrid/errors.py +0 -0
  21. {jdcloud_agentgrid-0.1.4 → jdcloud_agentgrid-0.2.1}/src/agentgrid/extensions/__init__.py +0 -0
  22. {jdcloud_agentgrid-0.1.4 → jdcloud_agentgrid-0.2.1}/src/agentgrid/extensions/observability/__init__.py +0 -0
  23. {jdcloud_agentgrid-0.1.4 → jdcloud_agentgrid-0.2.1}/src/agentgrid/extensions/tools/__init__.py +0 -0
  24. {jdcloud_agentgrid-0.1.4 → jdcloud_agentgrid-0.2.1}/src/agentgrid/extensions/tools/code_interpreter_models.py +0 -0
  25. {jdcloud_agentgrid-0.1.4 → jdcloud_agentgrid-0.2.1}/src/agentgrid/profile.py +0 -0
  26. {jdcloud_agentgrid-0.1.4 → jdcloud_agentgrid-0.2.1}/src/agentgrid/runtime/__init__.py +0 -0
  27. {jdcloud_agentgrid-0.1.4 → jdcloud_agentgrid-0.2.1}/src/agentgrid/runtime/models.py +0 -0
  28. {jdcloud_agentgrid-0.1.4 → jdcloud_agentgrid-0.2.1}/src/agentgrid/transport/__init__.py +0 -0
  29. {jdcloud_agentgrid-0.1.4 → jdcloud_agentgrid-0.2.1}/src/agentgrid/transport/runtime_adapter.py +0 -0
  30. {jdcloud_agentgrid-0.1.4 → jdcloud_agentgrid-0.2.1}/src/agentgrid/transport/runtime_data_client.py +0 -0
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jdcloud_agentgrid
3
- Version: 0.1.4
3
+ Version: 0.2.1
4
4
  Summary: AgentGrid Python SDK for JDCloud AgentGrid Runtime
5
5
  Project-URL: Homepage, https://github.com/agentgrid/agentgrid-python
6
6
  Project-URL: Repository, https://github.com/agentgrid/agentgrid-python
7
7
  Author: AgentGrid SDK Team
8
8
  Requires-Python: >=3.10
9
- Requires-Dist: jdcloud-sdk>=1.6.314
9
+ Requires-Dist: jdcloud-sdk>=1.6.321
10
10
  Requires-Dist: pyyaml>=6.0.2
11
11
  Requires-Dist: starlette>=0.37.2
12
12
  Requires-Dist: uvicorn>=0.30.0
@@ -15,6 +15,7 @@ Requires-Dist: build>=1.2.2; extra == 'dev'
15
15
  Requires-Dist: httpx>=0.27.0; extra == 'dev'
16
16
  Requires-Dist: langchain-openrouter>=0.2.1; extra == 'dev'
17
17
  Requires-Dist: langchain>=0.3.28; extra == 'dev'
18
+ Requires-Dist: playwright>=1.52.0; extra == 'dev'
18
19
  Requires-Dist: pytest>=8.2.0; extra == 'dev'
19
20
  Description-Content-Type: text/markdown
20
21
 
@@ -60,7 +61,7 @@ uv sync
60
61
  basic:
61
62
  region: cn-north-1
62
63
  endpoint: https://agentgrid.jdcloud-api.com
63
- data_endpoint: https://agentgrid-cn-north-1.jdcloud.com
64
+ data_endpoint: https://agentgrid-api-cn-north-1.jdcloud.com
64
65
  timeout: 30
65
66
  connect_timeout: 5
66
67
  read_timeout: 30
@@ -80,7 +81,7 @@ from agentgrid import AgentGridProfile, runtime_session
80
81
 
81
82
  profile = AgentGridProfile(
82
83
  region="cn-north-1",
83
- data_endpoint="https://agentgrid-cn-north-1.jdcloud.com", # 可选:默认就是该地址
84
+ data_endpoint="https://agentgrid-api-cn-north-1.jdcloud.com", # 可选:默认就是该地址
84
85
  )
85
86
 
86
87
  with runtime_session("cn-north-1", "your-runtime-id", profile=profile) as runtime:
@@ -40,7 +40,7 @@ uv sync
40
40
  basic:
41
41
  region: cn-north-1
42
42
  endpoint: https://agentgrid.jdcloud-api.com
43
- data_endpoint: https://agentgrid-cn-north-1.jdcloud.com
43
+ data_endpoint: https://agentgrid-api-cn-north-1.jdcloud.com
44
44
  timeout: 30
45
45
  connect_timeout: 5
46
46
  read_timeout: 30
@@ -60,7 +60,7 @@ from agentgrid import AgentGridProfile, runtime_session
60
60
 
61
61
  profile = AgentGridProfile(
62
62
  region="cn-north-1",
63
- data_endpoint="https://agentgrid-cn-north-1.jdcloud.com", # 可选:默认就是该地址
63
+ data_endpoint="https://agentgrid-api-cn-north-1.jdcloud.com", # 可选:默认就是该地址
64
64
  )
65
65
 
66
66
  with runtime_session("cn-north-1", "your-runtime-id", profile=profile) as runtime:
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "jdcloud_agentgrid"
7
- version = "0.1.4"
7
+ version = "0.2.1"
8
8
  description = "AgentGrid Python SDK for JDCloud AgentGrid Runtime"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -12,7 +12,7 @@ authors = [
12
12
  { name = "AgentGrid SDK Team" },
13
13
  ]
14
14
  dependencies = [
15
- "jdcloud_sdk>=1.6.314",
15
+ "jdcloud_sdk>=1.6.321",
16
16
  "PyYAML>=6.0.2",
17
17
  "starlette>=0.37.2",
18
18
  "uvicorn>=0.30.0",
@@ -24,6 +24,7 @@ dev = [
24
24
  "httpx>=0.27.0",
25
25
  "langchain>=0.3.28",
26
26
  "langchain-openrouter>=0.2.1",
27
+ "playwright>=1.52.0",
27
28
  "pytest>=8.2.0",
28
29
  ]
29
30
 
@@ -49,5 +50,6 @@ breaking_change_requires_major_bump = true
49
50
 
50
51
  [dependency-groups]
51
52
  dev = [
53
+ "playwright>=1.52.0",
52
54
  "twine>=6.2.0",
53
55
  ]
@@ -9,5 +9,5 @@ class BaseAgentGridApp(ABC):
9
9
  """应用层统一运行接口。"""
10
10
 
11
11
  @abstractmethod
12
- def run(self, host: str | None = None, port: int = 8000) -> None:
12
+ def run(self, host: str | None = None, port: int = 8080) -> None:
13
13
  """启动应用服务。"""
@@ -141,12 +141,21 @@ class InvokeHandler(BaseHandler):
141
141
  class PingHandler(BaseHandler):
142
142
  """`/ping` 与健康检查处理器。"""
143
143
 
144
+ def __init__(self, func: Callable[..., Any] | None = None) -> None:
145
+ super().__init__(func=func)
146
+ self._default_ping_updated_at = int(time.time())
147
+
144
148
  async def handle(self, request: Request) -> Response:
145
149
  del request
146
150
 
147
151
  if not self.func:
148
- logger.error("Ping handler function is not set")
149
- return Response(status_code=404)
152
+ return JSONResponse(
153
+ content={
154
+ "status": "Healthy",
155
+ "time_of_last_update": self._default_ping_updated_at,
156
+ },
157
+ media_type="application/json",
158
+ )
150
159
 
151
160
  if asyncio.iscoroutinefunction(self.func):
152
161
  result = await self.func()
@@ -20,7 +20,7 @@ import yaml
20
20
 
21
21
  DEFAULT_RUNTIME_ENDPOINT_HOST = "agentgrid.jdcloud-api.com"
22
22
  DEFAULT_RUNTIME_ENDPOINT = f"http://{DEFAULT_RUNTIME_ENDPOINT_HOST}"
23
- DEFAULT_RUNTIME_DATA_ENDPOINT = "https://agentgrid-cn-north-1.jdcloud.com"
23
+ DEFAULT_RUNTIME_DATA_ENDPOINT = "http://agentgrid-api-cn-north-1.jdcloud.com"
24
24
 
25
25
 
26
26
  @dataclass(slots=True, frozen=True)
@@ -15,6 +15,7 @@ from __future__ import annotations
15
15
 
16
16
  from dataclasses import dataclass, field
17
17
  from typing import TYPE_CHECKING, Any
18
+ from urllib.parse import urlsplit
18
19
 
19
20
  from jdcloud_sdk.core.exception import ClientException, ServerException
20
21
  from jdcloud_sdk.core.jdcloudresponse import JDCloudResponse
@@ -69,8 +70,7 @@ def extract_browser_cdp_websocket_endpoint(payload: object) -> str | None:
69
70
  if not isinstance(payload, dict):
70
71
  return None
71
72
 
72
- result = payload.get("result")
73
- if isinstance(result, dict):
73
+ def _extract_from_mapping(mapping: dict[str, Any]) -> str | None:
74
74
  for key in (
75
75
  "cdpWebSocketEndpoint",
76
76
  "cdpWebsocketEndpoint",
@@ -79,25 +79,46 @@ def extract_browser_cdp_websocket_endpoint(payload: object) -> str | None:
79
79
  "websocketDebuggerUrl",
80
80
  "cdp_endpoint",
81
81
  ):
82
- endpoint = result.get(key)
82
+ endpoint = mapping.get(key)
83
83
  if isinstance(endpoint, str) and endpoint.strip():
84
84
  return endpoint.strip()
85
+ return None
85
86
 
86
- for key in (
87
- "cdpWebSocketEndpoint",
88
- "cdpWebsocketEndpoint",
89
- "cdpEndpoint",
90
- "webSocketDebuggerUrl",
91
- "websocketDebuggerUrl",
92
- "cdp_endpoint",
93
- ):
94
- endpoint = payload.get(key)
95
- if isinstance(endpoint, str) and endpoint.strip():
96
- return endpoint.strip()
87
+ candidates: list[dict[str, Any]] = []
88
+ result = payload.get("result")
89
+ if isinstance(result, dict):
90
+ browser_session = result.get("browserToolSession")
91
+ if isinstance(browser_session, dict):
92
+ candidates.append(browser_session)
93
+ candidates.append(result)
94
+
95
+ browser_session = payload.get("browserToolSession")
96
+ if isinstance(browser_session, dict):
97
+ candidates.append(browser_session)
98
+ candidates.append(payload)
99
+
100
+ for candidate in candidates:
101
+ endpoint = _extract_from_mapping(candidate)
102
+ if endpoint is not None:
103
+ return endpoint
97
104
 
98
105
  return None
99
106
 
100
107
 
108
+ def _normalize_cdp_websocket_endpoint(endpoint: str) -> str:
109
+ """归一化 CDP 地址,缺省协议时补全 `ws://`。"""
110
+
111
+ value = endpoint.strip()
112
+ if not value:
113
+ return value
114
+
115
+ if urlsplit(value).scheme:
116
+ return value
117
+ if value.startswith("//"):
118
+ return f"ws:{value}"
119
+ return f"ws://{value}"
120
+
121
+
101
122
  @dataclass(slots=True)
102
123
  class BrowserSessionHandle:
103
124
  """Browser Tool 已启动会话句柄。"""
@@ -105,9 +126,29 @@ class BrowserSessionHandle:
105
126
  profile: AgentGridProfile
106
127
  browser_tool_id: str
107
128
  session_id: str
129
+ _api_key: str | None = field(default=None, repr=False)
108
130
  _cdp_websocket_endpoint: str | None = field(default=None, repr=False)
109
131
  _stopped: bool = field(default=False, init=False, repr=False)
110
132
 
133
+ def get_cdp_connect_headers(self, *, api_key: str | None = None) -> dict[str, str]:
134
+ """返回 Playwright CDP 连接所需请求头。"""
135
+
136
+ headers = {
137
+ "x-agentgrid-browser-session-id": self.session_id,
138
+ }
139
+
140
+ effective_api_key = self._api_key if api_key is None else api_key
141
+ if effective_api_key is None:
142
+ raise ValueError(
143
+ "cdp api key is required, pass browser_session apiKey or set tools.browser.api_key/runtime.api_key"
144
+ )
145
+ normalized_api_key = effective_api_key.strip()
146
+ if not normalized_api_key:
147
+ raise ValueError("cdp api key must not be empty")
148
+ headers["x-api-key"] = normalized_api_key
149
+
150
+ return headers
151
+
111
152
  def stop(self, *, headers: dict[str, str] | None = None) -> dict[str, Any]:
112
153
  """停止当前 Browser 会话。"""
113
154
 
@@ -156,7 +197,11 @@ class BrowserSessionHandle:
156
197
  """获取可用于 CDP 连接的 websocket 地址。"""
157
198
 
158
199
  if not refresh and self._cdp_websocket_endpoint is not None:
159
- return self._cdp_websocket_endpoint
200
+ normalized_endpoint = _normalize_cdp_websocket_endpoint(
201
+ self._cdp_websocket_endpoint
202
+ )
203
+ self._cdp_websocket_endpoint = normalized_endpoint
204
+ return normalized_endpoint
160
205
 
161
206
  payload = self.get_session_detail(headers=headers)
162
207
  endpoint = extract_browser_cdp_websocket_endpoint(payload)
@@ -172,8 +217,9 @@ class BrowserSessionHandle:
172
217
  },
173
218
  )
174
219
 
175
- self._cdp_websocket_endpoint = endpoint
176
- return endpoint
220
+ normalized_endpoint = _normalize_cdp_websocket_endpoint(endpoint)
221
+ self._cdp_websocket_endpoint = normalized_endpoint
222
+ return normalized_endpoint
177
223
 
178
224
 
179
225
  @dataclass(slots=True)
@@ -8,7 +8,8 @@
8
8
 
9
9
  说明:
10
10
  - 控制面与数据面均在本文件内收敛实现。
11
- - 数据面统一复用 `basic.data_endpoint` 下的 `/invoke` 协议。
11
+ - 数据面走 `basic.data_endpoint` 下的
12
+ `/v1/regions/<region>/code-interpreters/<code-interpreter-identifier>:invoke`。
12
13
  """
13
14
 
14
15
  from __future__ import annotations
@@ -92,6 +93,7 @@ class CodeInterpreterSessionHandle:
92
93
  profile: AgentGridProfile
93
94
  code_interpreter_id: str
94
95
  session_id: str
96
+ _default_data_plane_headers: dict[str, str] | None = field(default=None, repr=False)
95
97
  _stopped: bool = field(default=False, init=False, repr=False)
96
98
  _started: bool = field(default=True, repr=False)
97
99
 
@@ -711,7 +713,10 @@ class CodeInterpreterSessionHandle:
711
713
  def _build_data_plane_headers(
712
714
  self, *, headers: dict[str, str] | None
713
715
  ) -> dict[str, str]:
714
- merged = dict(headers or {})
716
+ merged = dict(self._default_data_plane_headers or {})
717
+ merged.update(dict(headers or {}))
718
+ # 数据面会话绑定头必须携带真实 session_id,避免被外部误传覆盖。
719
+ merged["x-agentgrid-code-interpreter-session-id"] = self.session_id
715
720
  merged.setdefault("Content-Type", "application/json")
716
721
  merged.setdefault("Accept", "application/json, text/event-stream")
717
722
  return apply_authentication(
@@ -720,12 +725,32 @@ class CodeInterpreterSessionHandle:
720
725
  api_key=None,
721
726
  )
722
727
 
728
+ @staticmethod
729
+ def _extract_data_plane_default_headers(
730
+ headers: dict[str, str] | None,
731
+ ) -> dict[str, str] | None:
732
+ if not headers:
733
+ return None
734
+
735
+ for key, value in headers.items():
736
+ if key.lower() != "x-api-key":
737
+ continue
738
+ normalized = str(value).strip()
739
+ if not normalized:
740
+ continue
741
+ return {"x-api-key": normalized}
742
+ return None
743
+
723
744
  def _build_data_plane_url(self) -> str:
724
745
  endpoint = self.profile.config.effective_data_endpoint.strip()
725
746
  if "://" not in endpoint:
726
747
  endpoint = f"http://{endpoint}"
727
748
  endpoint = endpoint.rstrip("/")
728
- return f"{endpoint}/invoke"
749
+ region = str(self.profile.config.region).strip()
750
+ return (
751
+ f"{endpoint}/v1/regions/{region}/code-interpreters/"
752
+ f"{self.code_interpreter_id}:invoke"
753
+ )
729
754
 
730
755
  def _resolve_data_plane_timeout(self) -> float | None:
731
756
  if self.profile.config.effective_connect_timeout is not None:
@@ -986,10 +1011,14 @@ class CodeInterpreterClient:
986
1011
  raw_error=payload,
987
1012
  )
988
1013
 
1014
+ default_data_plane_headers = (
1015
+ CodeInterpreterSessionHandle._extract_data_plane_default_headers(headers)
1016
+ )
989
1017
  return CodeInterpreterSessionHandle(
990
1018
  profile=self.profile,
991
1019
  code_interpreter_id=resolved_code_interpreter_id,
992
1020
  session_id=session_id,
1021
+ _default_data_plane_headers=default_data_plane_headers,
993
1022
  )
994
1023
 
995
1024
  def get_code_interpreter_session(
@@ -96,7 +96,7 @@ class AgentGridRuntimeClient:
96
96
  )
97
97
  effective_api_key = self._resolve_api_key(api_key)
98
98
 
99
- headers: dict[str, str] = {"Host": f"{runtime_id}.agentgrid.jdcloud.com"}
99
+ headers: dict[str, str] = {}
100
100
  if effective_session_id is not None:
101
101
  headers["x-agentgrid-runtime-session-id"] = effective_session_id
102
102
  if request_id is not None:
@@ -151,6 +151,7 @@ def browser_session(
151
151
  profile=effective_profile,
152
152
  browser_tool_id=effective_identifier,
153
153
  session_id=session_id,
154
+ _api_key=effective_api_key,
154
155
  )
155
156
 
156
157
  try:
@@ -445,16 +446,17 @@ def _extract_browser_session_status(payload: object) -> str:
445
446
 
446
447
  result = payload.get("result")
447
448
  if isinstance(result, dict):
448
- status = (
449
- result.get("status")
450
- or result.get("state")
451
- or result.get("sessionStatus")
452
- or result.get("session_state")
453
- )
449
+ browser_tool_session = result.get("browserToolSession")
450
+ if isinstance(browser_tool_session, dict):
451
+ status = browser_tool_session.get("state")
452
+ if isinstance(status, str) and status.strip():
453
+ return status.strip().lower()
454
+
455
+ status = result.get("state")
454
456
  if isinstance(status, str) and status.strip():
455
457
  return status.strip().lower()
456
458
 
457
- status = payload.get("status") or payload.get("state")
459
+ status = payload.get("state")
458
460
  if isinstance(status, str) and status.strip():
459
461
  return status.strip().lower()
460
462
  return "unknown"
@@ -3,4 +3,4 @@
3
3
  v1 基线采用语义化版本策略,当前为初始开发版本。
4
4
  """
5
5
 
6
- __version__ = "0.1.0"
6
+ __version__ = "0.2.1"