Skip to content

Commit 8f31b0e

Browse files
committed
fix(docs): align regional SDK release contracts
1 parent cbce0bd commit 8f31b0e

6 files changed

Lines changed: 121 additions & 8 deletions

File tree

docs/cn/zh-CN/js-sdk.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,16 @@ console.log(usage.total, ledger.total);
9090

9191
## API 参考
9292

93+
[根据源码生成的符号参考](js-sdk-api.md)列出当前包公开导出的全部 class、method、option、响应类型和
94+
AI SDK 集成。该页面直接根据 TypeScript 源码重新生成,并由 CI 检查是否漂移。
95+
9396
### `Qveris`
9497

9598
| 方法 | REST 端点 | 用途 |
9699
|------|-----------|------|
97100
| `discover(query, options?)` | `POST /search` | 发现能力;`view: 'routing'` 返回精简 routing card(免费) |
98101
| `inspect(toolIds, options?)` | `POST /tools/by-ids` | 获取能力完整元数据(免费) |
102+
| `probe(toolId, options?)` | `POST /tools/probe` | 校验参数并请求零成本报价 |
99103
| `call(toolId, options)` | `POST /tools/execute` | 执行能力;`respondWith` 可选择完整、摘要或 JSONPath 字段 |
100104
| `credits()` | `GET /auth/credits` | 当前积分余额与分桶 |
101105
| `usage(filters?)` | `GET /auth/usage/history/v2` | 审计请求状态与扣费结果 |
@@ -105,9 +109,10 @@ console.log(usage.total, ledger.total);
105109

106110
- `discover(query, { limit?, sessionId?, view?, lang?, timeoutMs? })`
107111
- `inspect(toolIds, { searchId?, sessionId?, timeoutMs? })` —— `toolIds` 接受单个字符串或数组;**空数组会短路**,直接返回空响应而不发起网络请求。
108-
- `call(toolId, { parameters, searchId?, sessionId?, maxResponseSize?, respondWith?, timeoutMs? })`
112+
- `probe(toolId, { parameters?, checks?, liveBudget?, timeoutMs? })`
113+
- `call(toolId, { parameters, searchId?, sessionId?, maxResponseSize?, respondWith?, timeoutMs?, compatibilityMode? })`
109114

110-
投影参数仅在显式指定时发送。旧服务返回 `422 extra_forbidden` 时仅移除对应可选字段并重试一次;无效投影仍按错误返回。
115+
投影参数仅在显式指定时发送。付费调用严格 single-submit:不会跟随 HTTP 重定向,`429`/`503` 和投影错误会直接返回,不会重放。已弃用的 `compatibilityMode: 'legacyOptionalFields'` 可显式允许一次删除旧服务拒绝的可选字段后重放;无效投影仍按错误返回。
111116

112117
`usage(...)``ledger(...)` 接受过滤对象,如 `start_date``end_date``summary``bucket``charge_outcome``execution_id``search_id``direction``entry_type``min_credits``max_credits``limit``page``page_size`
113118

docs/cn/zh-CN/python-sdk.md

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,11 @@ status = agent.budget_status() # {"limit": 25, "spent": 12.0, "remaining": 13.
172172
|------|---------|--------|------|
173173
| `api_key` | `QVERIS_API_KEY` | `None` | API 密钥,以 `Authorization: Bearer ...` 发送 |
174174
| `base_url` | `QVERIS_BASE_URL` | 按上文设置 | API 基础地址 |
175+
| `credential_audience` | `QVERIS_CREDENTIAL_AUDIENCE` | `None` | 透传给凭据 provider 的可选 audience |
176+
| `credential_scopes` | `QVERIS_CREDENTIAL_SCOPES` | `()` | 透传给凭据 provider 的可选 scopes |
177+
| `max_retries` | `QVERIS_MAX_RETRIES` | `3` | 读/审计操作的有界 `429`/`503` 重试;不适用于付费调用 |
178+
| `read_timeout` | `QVERIS_READ_TIMEOUT` | `30` | 读/审计操作的默认 HTTP 超时(秒) |
179+
| `call_timeout` | `QVERIS_CALL_TIMEOUT` | `120` | 付费调用的默认 HTTP 超时(秒) |
175180
| `enable_history_pruning` || `True` | 裁剪/压缩旧的工具输出以节省 token(agent 循环) |
176181
| `max_iterations` || `50` | agent 工具循环的最大迭代次数 |
177182

@@ -192,17 +197,49 @@ agent = Agent(
192197
)
193198
```
194199

200+
## 安全付费调用与 transport 所有权
201+
202+
付费 `call()` 默认严格 single-submit。SDK 不会跟随 HTTP 重定向,不会自动重试 `429`/`503`、超时或 transport 失败,也不会删除被拒绝的投影字段后再次提交;类型化错误会报告 `request_metadata.http_attempts == 1`。如果旧服务仍需要原来的投影降级,可显式选择:
203+
204+
```python
205+
result = await client.call(
206+
"tool.id",
207+
{"symbol": "AAPL"},
208+
respond_with="summary",
209+
compatibility_mode="legacy_optional_fields", # 已弃用;可能提交两次
210+
)
211+
```
212+
213+
该模式会发出 `DeprecationWarning`,并用 `request_metadata.compatibility_replays` 记录重放。读和审计操作仍使用有界 `max_retries`
214+
215+
`QverisClient` 可以注入共享 `http_client`,或配置由 SDK 持有的 `transport` / `limits`;两种形式互斥。`close()` 会等待在途操作、支持并发幂等调用、只关闭 SDK 自己创建的 client;关闭后新请求会抛出 `QverisClientClosedError`
216+
217+
每次物理 attempt 都会给凭据 provider 传入不可变 `CredentialContext`,其中包含 resource、显式配置的 audience/scopes、operation、purpose、session ID 和可选的非敏感 `correlation_id`。HTTP 超时从凭据获取完成后开始计算。
218+
219+
公开失败统一使用 `QverisError` 层级(`QverisApiError``QverisTransportError``QverisCredentialError``QverisContractError``QverisClientClosedError`)。错误会保留安全的机器字段和不可变 `RequestMetadata`,但不会保留原始 HTTP request/response、bearer credential、签名 URL 或底层异常对象。
220+
221+
### 从 0.5 迁移
222+
223+
- `max_retries` 仅用于读和审计操作,不再控制付费调用。
224+
- `call(..., respond_with=...)` 不再静默执行旧版重放;只应在短期迁移窗口使用 `compatibility_mode="legacy_optional_fields"`
225+
- 捕获 `QverisError` 或其类型化子类,不再捕获 `httpx.HTTPStatusError` / 原始 transport 异常。
226+
- 响应元数据位于 `response.request_metadata`,不会进入 wire 序列化。
227+
228+
Capability Resolve/Query、selection token、idempotency key 和 execution lookup 只会在对应端点及字段进入公开 OpenAPI 后提供;client 不会提前发明临时 wire 字段。
229+
195230
## API 参考
196231

232+
[根据源码生成的 API 参考](python-sdk-api.md)列出当前公开 client、Agent、配置和响应模型的签名。
233+
Sphinx 会根据 Python 对象与 docstring 重新生成该页面,CI 同时检查是否漂移。
234+
197235
### `QverisClient`
198236

199237
| 方法 | REST 端点 | 用途 |
200238
|------|-----------|------|
201-
| `discover(query, limit=20, session_id=None, view=None, lang=None)` | `POST /search` | 发现能力;`view="routing"` 返回精简 routing card(免费) |
202-
| `inspect(tool_ids, search_id=None, session_id=None)` | `POST /tools/by-ids` | 获取能力完整元数据(免费) |
203-
| `call(tool_id, parameters, search_id=None, session_id=None, max_response_size=None, respond_with=None)` | `POST /tools/execute` | 执行能力;可选择完整、摘要或 JSONPath 字段 |
204-
205-
投影参数仅在显式指定时发送。旧服务返回 `422 extra_forbidden` 时仅移除对应可选字段并重试一次;无效投影仍按错误返回。
239+
| `discover(query, limit=20, session_id=None, view=None, lang=None, timeout=None, correlation_id=None)` | `POST /search` | 发现能力;`view="routing"` 返回精简 routing card(免费) |
240+
| `inspect(tool_ids, search_id=None, session_id=None, timeout=None, correlation_id=None)` | `POST /tools/by-ids` | 获取能力完整元数据(免费) |
241+
| `probe(tool_id, parameters=None, checks=None, live_budget="none", timeout=None, correlation_id=None)` | `POST /tools/probe` | 校验参数并请求零成本报价 |
242+
| `call(tool_id, parameters, ..., compatibility_mode="strict", timeout=None, correlation_id=None)` | `POST /tools/execute` | 使用严格 single-submit 语义执行能力 |
206243
| `usage(**filters)` | `GET /auth/usage/history/v2` | 审计请求状态与扣费结果 |
207244
| `ledger(**filters)` | `GET /auth/credits/ledger` | 查看最终积分余额变动 |
208245
| `handle_tool_call(func_name, func_args, session_id=None)` || 把 LLM 工具调用桥接到对应的 QVeris 方法 |
@@ -212,6 +249,8 @@ agent = Agent(
212249

213250
仍保留向后兼容别名:`search_tools``discover``get_tools_by_ids``inspect``execute_tool``call`
214251

252+
投影参数仅在显式指定时发送。Discover 的读侧投影兼容仍然有界;付费调用只有在显式选择已弃用兼容模式时才会重放。无效投影仍按错误返回。
253+
215254
### `Agent`
216255

217256
| 成员 | 说明 |

docs/en-US/js-sdk.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ in CI.
101101
|--------|---------------|---------|
102102
| `discover(query, options?)` | `POST /search` | Find capabilities; `view: 'routing'` returns compact routing cards (free) |
103103
| `inspect(toolIds, options?)` | `POST /tools/by-ids` | Fetch full capability metadata (free) |
104+
| `probe(toolId, options?)` | `POST /tools/probe` | Validate parameters and request a zero-cost quote |
104105
| `call(toolId, options)` | `POST /tools/execute` | Execute a capability; `respondWith` selects full, summary, or JSONPath fields |
105106
| `credits()` | `GET /auth/credits` | Current credit balance and buckets |
106107
| `usage(filters?)` | `GET /auth/usage/history/v2` | Audit request status and charge outcome |
@@ -110,6 +111,7 @@ Option shapes:
110111

111112
- `discover(query, { limit?, sessionId?, view?, lang?, timeoutMs? })`
112113
- `inspect(toolIds, { searchId?, sessionId?, timeoutMs? })``toolIds` accepts a single string or an array; an **empty array short-circuits** and returns an empty response without a network request.
114+
- `probe(toolId, { parameters?, checks?, liveBudget?, timeoutMs? })`
113115
- `call(toolId, { parameters, searchId?, sessionId?, maxResponseSize?, respondWith?, timeoutMs?, compatibilityMode? })`
114116

115117
Projection options are opt-in. Paid calls are strict single-submit: HTTP redirects are not followed, and `429`/`503` and projection errors are returned without replay. The deprecated `compatibilityMode: 'legacyOptionalFields'` opt-in permits exactly one replay without an optional field rejected by an older service; invalid projections remain errors.

docs/zh-CN/js-sdk.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ AI SDK 集成。该页面直接根据 TypeScript 源码重新生成,并由 CI
9999
|------|-----------|------|
100100
| `discover(query, options?)` | `POST /search` | 发现能力;`view: 'routing'` 返回精简 routing card(免费) |
101101
| `inspect(toolIds, options?)` | `POST /tools/by-ids` | 获取能力完整元数据(免费) |
102+
| `probe(toolId, options?)` | `POST /tools/probe` | 校验参数并请求零成本报价 |
102103
| `call(toolId, options)` | `POST /tools/execute` | 执行能力;`respondWith` 可选择完整、摘要或 JSONPath 字段 |
103104
| `credits()` | `GET /auth/credits` | 当前积分余额与分桶 |
104105
| `usage(filters?)` | `GET /auth/usage/history/v2` | 审计请求状态与扣费结果 |
@@ -108,6 +109,7 @@ AI SDK 集成。该页面直接根据 TypeScript 源码重新生成,并由 CI
108109

109110
- `discover(query, { limit?, sessionId?, view?, lang?, timeoutMs? })`
110111
- `inspect(toolIds, { searchId?, sessionId?, timeoutMs? })` —— `toolIds` 接受单个字符串或数组;**空数组会短路**,直接返回空响应而不发起网络请求。
112+
- `probe(toolId, { parameters?, checks?, liveBudget?, timeoutMs? })`
111113
- `call(toolId, { parameters, searchId?, sessionId?, maxResponseSize?, respondWith?, timeoutMs?, compatibilityMode? })`
112114

113115
投影参数仅在显式指定时发送。付费调用严格 single-submit:不会跟随 HTTP 重定向,`429`/`503` 和投影错误会直接返回,不会重放。已弃用的 `compatibilityMode: 'legacyOptionalFields'` 可显式允许一次删除旧服务拒绝的可选字段后重放;无效投影仍按错误返回。

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"build": "npm --prefix packages/mcp run build && npm --prefix packages/js-sdk run build && npm --prefix packages/openclaw-qveris-plugin run build",
1919
"release:clients:check": "node scripts/release-client-packages.mjs check",
2020
"release:clients:publish": "node scripts/release-client-packages.mjs publish",
21-
"test:release-clients": "node --test scripts/release-client-packages.test.mjs scripts/benchmark-release-cadence.test.mjs",
21+
"test:release-clients": "node --test scripts/release-client-packages.test.mjs scripts/benchmark-release-cadence.test.mjs scripts/sdk-guide-contracts.test.mjs",
2222
"typecheck": "npm --prefix packages/mcp run typecheck && npm --prefix packages/js-sdk run typecheck && npm --prefix packages/openclaw-qveris-plugin run typecheck",
2323
"test": "npm --prefix packages/cli test && npm --prefix packages/mcp test && npm --prefix packages/js-sdk test && npm --prefix packages/openclaw-qveris-plugin test && npm run test:py && npm run test:release-clients",
2424
"test:py": "cd packages/python-sdk && uv run --frozen --extra dev pytest -q",
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import assert from "node:assert/strict";
2+
import { readFileSync } from "node:fs";
3+
import { dirname, resolve } from "node:path";
4+
import test from "node:test";
5+
import { fileURLToPath } from "node:url";
6+
7+
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
8+
9+
function read(relativePath) {
10+
return readFileSync(resolve(ROOT, relativePath), "utf8");
11+
}
12+
13+
const PYTHON_GUIDES = [
14+
"docs/en-US/python-sdk.md",
15+
"docs/zh-CN/python-sdk.md",
16+
"docs/cn/zh-CN/python-sdk.md",
17+
];
18+
19+
const JS_GUIDES = [
20+
"docs/en-US/js-sdk.md",
21+
"docs/zh-CN/js-sdk.md",
22+
"docs/cn/zh-CN/js-sdk.md",
23+
];
24+
25+
test("all Python SDK guides document the v0.6 paid-call contract", () => {
26+
for (const path of PYTHON_GUIDES) {
27+
const guide = read(path);
28+
for (const marker of [
29+
"credential_audience",
30+
"read_timeout",
31+
"call_timeout",
32+
"request_metadata.http_attempts == 1",
33+
'compatibility_mode="legacy_optional_fields"',
34+
"probe(tool_id",
35+
'compatibility_mode="strict"',
36+
]) {
37+
assert.ok(guide.includes(marker), `${path} is missing ${marker}`);
38+
}
39+
}
40+
});
41+
42+
test("all TypeScript SDK guides document Probe and strict paid calls", () => {
43+
for (const path of JS_GUIDES) {
44+
const guide = read(path);
45+
for (const marker of [
46+
"probe(toolId, options?)",
47+
"probe(toolId, { parameters?, checks?, liveBudget?, timeoutMs? })",
48+
"compatibilityMode: 'legacyOptionalFields'",
49+
"single-submit",
50+
]) {
51+
assert.ok(guide.includes(marker), `${path} is missing ${marker}`);
52+
}
53+
}
54+
});
55+
56+
test("China SDK guides keep the China endpoint boundary", () => {
57+
for (const path of [
58+
"docs/cn/zh-CN/python-sdk.md",
59+
"docs/cn/zh-CN/js-sdk.md",
60+
]) {
61+
const guide = read(path);
62+
assert.ok(guide.includes("https://qveris.cn/api/v1"), `${path} is missing the China API endpoint`);
63+
assert.ok(!guide.includes("qveris.ai"), `${path} crosses the public deployment boundary`);
64+
}
65+
});

0 commit comments

Comments
 (0)