Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ jobs:
- name: Install dependencies
run: python -m pip install -e '.[dev]' build

- name: Check public documentation
shell: bash
run: |
set -euo pipefail
test -f README.md
test -f README.zh-CN.md
test -f PARITY.md
test -f PARITY.zh-CN.md
! grep -n -E 'OPENLINKER_CORE_URL|OPENLINKER_RUNTIME_CERT([^_]|$)|OPENLINKER_RUNTIME_KEY([^_]|$)|OPENLINKER_RUNTIME_CA([^_]|$)' + README.md README.zh-CN.md

- name: Lint
run: python -m ruff check .

Expand Down
2 changes: 2 additions & 0 deletions PARITY.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# OpenLinker SDK parity

[简体中文](PARITY.zh-CN.md)

The Python SDK follows the same product boundary and canonical Runtime contract as
`openlinker-go` and `openlinker-js`. Naming follows Python conventions; wire behavior
is shared.
Expand Down
67 changes: 67 additions & 0 deletions PARITY.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# OpenLinker SDK 功能对齐情况

[English](PARITY.md)

Python SDK 与 `openlinker-go`、`openlinker-js` 使用同一条产品边界和同一份 Runtime
契约。公开命名遵循 Python 习惯,网络行为保持一致。

## 平台 Client

已经实现:

- Agent 发现和 Agent Card;
- 异步 Client 的等待结果和仅启动两种 Run 创建方式;
- Run 详情、事件、子 Run、产物和消息;
- SSE 运行事件和 callback 辅助函数;
- 创建者 Agent 和 Agent Token 管理;
- Webhook 签名和验证;
- A2A JSON-RPC、REST 和可选 gRPC。

Client 只接受 User Token。Agent 端的 Session 和执行方法不会混入平台 Client。

## Runtime Worker

已经实现:

- 不带凭证发现独立 Runtime 地址;
- Agent Token、mTLS、最低 TLS 1.3 和拒绝重定向;
- Session 建立、心跳和关闭;
- WebSocket、长轮询和自动连接恢复;
- 先持久化任务再确认、Core 确认后才执行;
- 租约续期、取消、停止接收新任务、容量控制和安全关闭;
- 重连后继续未完成交付,不重复执行已经开始的 Handler;
- 加密的任务记录以及 Event/Result 待发送队列;
- 重试时保持 Event/Result ID 稳定,核对业务 ACK,并补发缺失 Event;
- 当前任务范围内调用其他 Agent,且必须提供幂等键;
- 稳定的 worker 身份、每次启动更新的 Session 身份和单调递增的 Session 次序;
- 文件权限、单进程锁、密钥和密文完整性、容量上限。

公开的 Python 生命周期只有:

```python
worker = RuntimeWorker(...)
await worker.run()
```

SDK 不提供产品 CLI、旧 Native runner、自动 Agent 注册,也不保留旧的
heartbeat/claim/result 接口。

## 验证范围

本地测试覆盖:

- 标准契约摘要、接口集合和 URL 生成名称;
- 任务确认顺序;
- 任务、Event 和 Result 的 ACK 丢失后,使用稳定 ID 重发;
- Pull 和 WebSocket 建立连接时的 Session 冲突恢复;
- 已建立 Session 出现冲突时安全失败;
- WebSocket 转 Pull 以及 WebSocket 恢复;
- 已经开始的任务无法安全恢复时拒绝重启;
- 取消传递和确认;
- 地址发现、来源限制、重定向和凭据隔离;
- 连接建立、心跳绑定和重新连接的代次隔离;
- 本地状态身份、重启重发、文件锁、权限、缺少密钥、密文被修改、符号链接和容量失败;
- Client、Webhook 和 A2A 行为。

真实沙箱闭环和可选 A2A gRPC 集成仍属于发布环境检查,单元测试不代表这些外部环境
已经连通。
19 changes: 12 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ from openlinker import client

async def main() -> None:
async with client.Client(
os.environ["OPENLINKER_CORE_URL"],
os.environ["OPENLINKER_URL"],
user_token=os.environ["OPENLINKER_USER_TOKEN"],
) as openlinker:
agents = await openlinker.list_agents(
Expand Down Expand Up @@ -157,7 +157,7 @@ from openlinker.a2a import A2AMessage, A2AMessageSendParams

async def main() -> None:
async with client.Client(
os.environ["OPENLINKER_CORE_URL"],
os.environ["OPENLINKER_URL"],
user_token=os.environ["OPENLINKER_USER_TOKEN"],
) as openlinker:
a2a = openlinker.a2a_agent("research-agent")
Expand Down Expand Up @@ -234,16 +234,21 @@ async def handle(context: runtime.RuntimeContext) -> runtime.RuntimeResult:

async def main() -> None:
worker = runtime.RuntimeWorker(
platform_url=os.environ["OPENLINKER_CORE_URL"],
platform_url=os.environ["OPENLINKER_URL"],
runtime_url=os.environ.get("OPENLINKER_RUNTIME_URL", ""),
node_id=os.environ["OPENLINKER_NODE_ID"],
agent_id=os.environ["OPENLINKER_AGENT_ID"],
agent_token=os.environ["OPENLINKER_AGENT_TOKEN"],
mtls=runtime.RuntimeMTLS(
cert_file=os.environ["OPENLINKER_RUNTIME_CERT"],
key_file=os.environ["OPENLINKER_RUNTIME_KEY"],
ca_file=os.environ["OPENLINKER_RUNTIME_CA"],
cert_file=os.environ["OPENLINKER_RUNTIME_MTLS_CERT_FILE"],
key_file=os.environ["OPENLINKER_RUNTIME_MTLS_KEY_FILE"],
ca_file=os.environ["OPENLINKER_RUNTIME_MTLS_CA_FILE"],
),
data_dir="./.openlinker-runtime",
data_dir=os.environ.get(
"OPENLINKER_RUNTIME_DATA_DIR",
"./.openlinker-runtime",
),
transport=os.environ.get("OPENLINKER_RUNTIME_TRANSPORT", "auto"),
handler=handle,
)
await worker.run()
Expand Down
21 changes: 13 additions & 8 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ from openlinker import client

async def main() -> None:
async with client.Client(
os.environ["OPENLINKER_CORE_URL"],
os.environ["OPENLINKER_URL"],
user_token=os.environ["OPENLINKER_USER_TOKEN"],
) as openlinker:
agents = await openlinker.list_agents(
Expand Down Expand Up @@ -149,7 +149,7 @@ from openlinker.a2a import A2AMessage, A2AMessageSendParams

async def main() -> None:
async with client.Client(
os.environ["OPENLINKER_CORE_URL"],
os.environ["OPENLINKER_URL"],
user_token=os.environ["OPENLINKER_USER_TOKEN"],
) as openlinker:
a2a = openlinker.a2a_agent("research-agent")
Expand Down Expand Up @@ -224,16 +224,21 @@ async def handle(context: runtime.RuntimeContext) -> runtime.RuntimeResult:

async def main() -> None:
worker = runtime.RuntimeWorker(
platform_url=os.environ["OPENLINKER_CORE_URL"],
platform_url=os.environ["OPENLINKER_URL"],
runtime_url=os.environ.get("OPENLINKER_RUNTIME_URL", ""),
node_id=os.environ["OPENLINKER_NODE_ID"],
agent_id=os.environ["OPENLINKER_AGENT_ID"],
agent_token=os.environ["OPENLINKER_AGENT_TOKEN"],
mtls=runtime.RuntimeMTLS(
cert_file=os.environ["OPENLINKER_RUNTIME_CERT"],
key_file=os.environ["OPENLINKER_RUNTIME_KEY"],
ca_file=os.environ["OPENLINKER_RUNTIME_CA"],
cert_file=os.environ["OPENLINKER_RUNTIME_MTLS_CERT_FILE"],
key_file=os.environ["OPENLINKER_RUNTIME_MTLS_KEY_FILE"],
ca_file=os.environ["OPENLINKER_RUNTIME_MTLS_CA_FILE"],
),
data_dir="./.openlinker-runtime",
data_dir=os.environ.get(
"OPENLINKER_RUNTIME_DATA_DIR",
"./.openlinker-runtime",
),
transport=os.environ.get("OPENLINKER_RUNTIME_TRANSPORT", "auto"),
handler=handle,
)
await worker.run()
Expand Down Expand Up @@ -266,7 +271,7 @@ contracts/ # Runtime 标准契约副本
tests/ # Client、Runtime、A2A、Webhook 与契约测试
```

兼容范围和验证矩阵见 [PARITY.md](./PARITY.md)。
兼容范围和验证矩阵见 [PARITY.zh-CN.md](./PARITY.zh-CN.md)。

## 开发

Expand Down