edgeone 1.5.8 → 1.6.0
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.
- package/README.md +26 -26
- package/edgeone-bin/edgeone.js +3 -3
- package/edgeone-dist/cli.js +86879 -2294
- package/edgeone-dist/libs-pages-agent-toolkit/README.md +8 -0
- package/edgeone-dist/libs-pages-agent-toolkit/pages_agent_toolkit-0.1.40-py3-none-any.whl +0 -0
- package/edgeone-dist/libs-pages-blob-python/README.md +38 -0
- package/edgeone-dist/libs-pages-blob-python/pages_blob_python-0.11.0-py3-none-any.whl +0 -0
- package/edgeone-dist/pages/dev/runner-worker.js +86519 -2075
- package/edgeone-dist/pages/observability-python/__init__.py +32 -0
- package/edgeone-dist/pages/observability-python/_compat.py +69 -0
- package/edgeone-dist/pages/observability-python/apm/__init__.py +13 -0
- package/edgeone-dist/pages/observability-python/apm/config.py +85 -0
- package/edgeone-dist/pages/observability-python/apm/llm_semconv.py +53 -0
- package/edgeone-dist/pages/observability-python/apm/metrics_bridge.py +226 -0
- package/edgeone-dist/pages/observability-python/apm/span_exporter.py +384 -0
- package/edgeone-dist/pages/observability-python/bootstrap.py +158 -0
- package/edgeone-dist/pages/observability-python/build.py +119 -0
- package/edgeone-dist/pages/observability-python/context_patches.py +167 -0
- package/edgeone-dist/pages/observability-python/context_propagator.py +78 -0
- package/edgeone-dist/pages/observability-python/registry.json +95 -0
- package/edgeone-dist/pages/observability-python/registry.py +141 -0
- package/edgeone-dist/pages/observability-python/telemetry.py +214 -0
- package/edgeone-dist/pages/observability-python/tracer.py +165 -0
- package/edgeone-dist/pages/templates/agent-python/__init__.py +11 -0
- package/edgeone-dist/pages/templates/agent-python/adapter.py +908 -0
- package/edgeone-dist/pages/templates/agent-python/context.py +689 -0
- package/edgeone-dist/pages/templates/agent-python/local_blob_store.py +172 -0
- package/edgeone-dist/pages/templates/agent-python/memory.py +2301 -0
- package/edgeone-dist/pages/templates/agent-python/runtime.py +839 -0
- package/edgeone-dist/pages/templates/agent-python/store.py +204 -0
- package/edgeone-dist/studio/ui/assets/agent-obs-Dvi4IpEy.js +4 -0
- package/edgeone-dist/studio/ui/assets/agent-obs-qDJCE0TQ.css +1 -0
- package/edgeone-dist/studio/ui/assets/highlight-ClXAL37H.js +3 -0
- package/edgeone-dist/studio/ui/assets/index-Cz5oQnXW.css +1 -0
- package/edgeone-dist/studio/ui/assets/index-DD3d108t.js +1 -0
- package/edgeone-dist/studio/ui/assets/moment-BYRO94Ou.js +10 -0
- package/edgeone-dist/studio/ui/assets/react-dom-ZzBHVjtL.js +24 -0
- package/edgeone-dist/studio/ui/assets/react-hnpCyKql.js +17 -0
- package/edgeone-dist/studio/ui/assets/tea-CADagUwM.css +1 -0
- package/edgeone-dist/studio/ui/assets/tea-Slf_ajmf.js +334 -0
- package/edgeone-dist/studio/ui/favicon.ico +0 -0
- package/edgeone-dist/studio/ui/index.html +31 -0
- package/libs-pages-agent-toolkit/README.md +8 -0
- package/libs-pages-agent-toolkit/pages_agent_toolkit-0.1.40-py3-none-any.whl +0 -0
- package/libs-pages-blob-python/README.md +38 -0
- package/libs-pages-blob-python/pages_blob_python-0.11.0-py3-none-any.whl +0 -0
- package/package.json +33 -7
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# src/pages/builder/templates/agent-python/local_blob_store.py
|
|
2
|
+
"""LocalFileBlobStore — 文件系统持久化的 pages-blob 替身,仅用于本地 dev。
|
|
3
|
+
|
|
4
|
+
触发方式:设置环境变量 PAGES_BLOB_LOCAL_PERSIST=1。
|
|
5
|
+
数据路径:默认 .edgeone/agent-python/.blob-local/<store-name>/<url_quote(key)>.bin
|
|
6
|
+
(可用 PAGES_BLOB_LOCAL_DIR 覆盖)
|
|
7
|
+
|
|
8
|
+
接口与 pages_blob.Store 鸭子兼容(只实现 BlobBackedStore 实际用到的 4 个方法:
|
|
9
|
+
get / set / delete / list)。不依赖 pages_blob 包,离线环境也能用。
|
|
10
|
+
|
|
11
|
+
不支持的能力:TTL 过期清理(由 BlobBackedStore 的 envelope 自己兜底)、
|
|
12
|
+
content-type、cacheControl、CDN 头——本地 dev 场景不需要。
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import asyncio
|
|
17
|
+
import os
|
|
18
|
+
import urllib.parse
|
|
19
|
+
from dataclasses import dataclass, field
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import List, Optional
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class _BlobInfo:
|
|
26
|
+
"""鸭子兼容 pages_blob.types.BlobInfo"""
|
|
27
|
+
key: str
|
|
28
|
+
etag: str = ""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class _ListResult:
|
|
33
|
+
"""鸭子兼容 pages_blob.types.ListResult"""
|
|
34
|
+
blobs: List[_BlobInfo] = field(default_factory=list)
|
|
35
|
+
directories: List[str] = field(default_factory=list)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _safe_filename(key: str) -> str:
|
|
39
|
+
"""key → 文件名。用 URL encode 保留可读性,同时避免路径穿越。
|
|
40
|
+
|
|
41
|
+
注意不用 hash:debug 时能直接 ls 看到原 key。
|
|
42
|
+
"""
|
|
43
|
+
encoded = urllib.parse.quote(key, safe="")
|
|
44
|
+
# 加后缀,方便 rm -rf 清理
|
|
45
|
+
return encoded + ".bin"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _unsafe_key(filename: str) -> str:
|
|
49
|
+
"""反向:文件名 → key"""
|
|
50
|
+
if filename.endswith(".bin"):
|
|
51
|
+
filename = filename[:-4]
|
|
52
|
+
return urllib.parse.unquote(filename)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class LocalFileBlobStore:
|
|
56
|
+
"""文件持久化的 pages_blob.Store 替身。
|
|
57
|
+
|
|
58
|
+
每个 store 对应 base_dir/<store_name>/ 目录,每个 key 对应一个文件。
|
|
59
|
+
同目录下多 handler 并行写入同一个 key:使用 atomic write(先写 .tmp
|
|
60
|
+
再 rename),确保读取端永远看到完整内容。
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def __init__(self, store_name: str, base_dir: Path) -> None:
|
|
64
|
+
self._store_name = store_name
|
|
65
|
+
self._dir = base_dir / store_name
|
|
66
|
+
self._dir.mkdir(parents=True, exist_ok=True)
|
|
67
|
+
|
|
68
|
+
def _path_for(self, key: str) -> Path:
|
|
69
|
+
return self._dir / _safe_filename(key)
|
|
70
|
+
|
|
71
|
+
async def get(self, key: str, *, type: str = "text", **_kwargs) -> Optional[object]:
|
|
72
|
+
path = self._path_for(key)
|
|
73
|
+
if not path.exists():
|
|
74
|
+
return None
|
|
75
|
+
# 用线程池读,避免阻塞 event loop(小文件其实无所谓,保持形状对齐)
|
|
76
|
+
body = await asyncio.to_thread(path.read_bytes)
|
|
77
|
+
if type == "bytes":
|
|
78
|
+
return body
|
|
79
|
+
if type == "json":
|
|
80
|
+
import json
|
|
81
|
+
return json.loads(body.decode("utf-8"))
|
|
82
|
+
# 默认 text
|
|
83
|
+
return body.decode("utf-8")
|
|
84
|
+
|
|
85
|
+
async def set(
|
|
86
|
+
self,
|
|
87
|
+
key: str,
|
|
88
|
+
value,
|
|
89
|
+
*,
|
|
90
|
+
only_if_new: bool = False,
|
|
91
|
+
**_kwargs,
|
|
92
|
+
) -> None:
|
|
93
|
+
path = self._path_for(key)
|
|
94
|
+
if only_if_new and path.exists():
|
|
95
|
+
# 与线上行为对齐:412 → BlobBackedStore 不直接调这个,
|
|
96
|
+
# 但独立调用方可能走到;直接抛出和 pages_blob 对齐的异常。
|
|
97
|
+
raise _PreconditionFailedError()
|
|
98
|
+
|
|
99
|
+
if isinstance(value, str):
|
|
100
|
+
data = value.encode("utf-8")
|
|
101
|
+
elif isinstance(value, (bytes, bytearray)):
|
|
102
|
+
data = bytes(value)
|
|
103
|
+
else:
|
|
104
|
+
# dict / list 等——直接 json 化
|
|
105
|
+
import json
|
|
106
|
+
data = json.dumps(value, ensure_ascii=False).encode("utf-8")
|
|
107
|
+
|
|
108
|
+
# Atomic write:rename 是 POSIX 原子操作
|
|
109
|
+
tmp = path.with_suffix(path.suffix + ".tmp")
|
|
110
|
+
await asyncio.to_thread(tmp.write_bytes, data)
|
|
111
|
+
await asyncio.to_thread(os.replace, tmp, path)
|
|
112
|
+
|
|
113
|
+
async def set_json(self, key: str, value, *, only_if_new: bool = False, **_kwargs) -> None:
|
|
114
|
+
import json
|
|
115
|
+
await self.set(key, json.dumps(value, ensure_ascii=False), only_if_new=only_if_new)
|
|
116
|
+
|
|
117
|
+
async def delete(self, key: str) -> None:
|
|
118
|
+
path = self._path_for(key)
|
|
119
|
+
try:
|
|
120
|
+
await asyncio.to_thread(path.unlink)
|
|
121
|
+
except FileNotFoundError:
|
|
122
|
+
# 对齐 pages_blob:delete 不存在的 key 是幂等操作
|
|
123
|
+
pass
|
|
124
|
+
|
|
125
|
+
async def list(
|
|
126
|
+
self,
|
|
127
|
+
*,
|
|
128
|
+
prefix: Optional[str] = None,
|
|
129
|
+
directories: bool = False,
|
|
130
|
+
cursor: Optional[str] = None,
|
|
131
|
+
paginate: bool = True,
|
|
132
|
+
**_kwargs,
|
|
133
|
+
) -> _ListResult:
|
|
134
|
+
entries = await asyncio.to_thread(lambda: list(self._dir.iterdir()))
|
|
135
|
+
keys = []
|
|
136
|
+
for entry in entries:
|
|
137
|
+
if not entry.is_file() or not entry.name.endswith(".bin"):
|
|
138
|
+
continue
|
|
139
|
+
key = _unsafe_key(entry.name)
|
|
140
|
+
if prefix and not key.startswith(prefix):
|
|
141
|
+
continue
|
|
142
|
+
keys.append(key)
|
|
143
|
+
keys.sort()
|
|
144
|
+
return _ListResult(blobs=[_BlobInfo(key=k) for k in keys])
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class _PreconditionFailedError(Exception):
|
|
148
|
+
"""和 pages_blob.errors.PreconditionFailedError 鸭子对齐"""
|
|
149
|
+
code = "PRECONDITION_FAILED"
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def get_local_base_dir() -> Optional[Path]:
|
|
153
|
+
"""解析本地持久化根目录;未启用返回 None。
|
|
154
|
+
|
|
155
|
+
- PAGES_BLOB_LOCAL_DIR 指定 → 用它
|
|
156
|
+
- PAGES_BLOB_LOCAL_PERSIST=1 → 用 .edgeone/agent-python/.blob-local
|
|
157
|
+
- 否则 → None
|
|
158
|
+
"""
|
|
159
|
+
explicit = os.environ.get("PAGES_BLOB_LOCAL_DIR")
|
|
160
|
+
if explicit:
|
|
161
|
+
return Path(explicit).expanduser().resolve()
|
|
162
|
+
|
|
163
|
+
flag = os.environ.get("PAGES_BLOB_LOCAL_PERSIST", "").strip().lower()
|
|
164
|
+
if flag in ("1", "true", "yes", "on"):
|
|
165
|
+
# adapter.py 同目录下的 .blob-local(main.py 位置就是 OUTPUT_DIR)
|
|
166
|
+
main_py = os.environ.get("_AGENT_PYTHON_MAIN", "")
|
|
167
|
+
if main_py:
|
|
168
|
+
return Path(main_py).parent / ".blob-local"
|
|
169
|
+
# Fallback: cwd 下
|
|
170
|
+
return Path.cwd() / ".blob-local"
|
|
171
|
+
|
|
172
|
+
return None
|