Skip to content

Commit 14db646

Browse files
committed
Verify OpenCode ACP usage
1 parent 7048818 commit 14db646

3 files changed

Lines changed: 170 additions & 3 deletions

File tree

doc/investigation/acp-price-calc-track.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,64 @@ code it describes.
458458
- Canonical report runner: 13 passed, 0 failed; HTML/XML/Markdown artifacts generated under the
459459
ignored artifacts directory.
460460
- Bootstrap `-Check`: exit 0; winapp, Pester 6.0.1, Dev package, and module import succeeded.
461+
462+
### Step 13 - OpenCode Native ACP Usage POC
463+
464+
**Investigation gate**
465+
466+
- Read the OpenCode repository README, native `opencode acp` entry point, ACP Usage service, and
467+
existing OpenCode Usage tests. The local `dev` source identifies as `1.18.4`.
468+
- Stopped public package-source access after the corporate policy warning. npm is configured to
469+
`https://packagefeedproxy.microsoft.io/npm/`; no public npm, PyPI, or NuGet registry was used.
470+
- The source monorepo lockfile contains `github:` and `pkg.pr.new` direct dependencies, so a full
471+
source `bun install` remains blocked until approved internal equivalents are available.
472+
- Installed fixed `opencode-ai@1.18.3` and `opencode-windows-x64@1.18.3` from the Microsoft-protected
473+
npm feed. Both tarball URLs resolved to Microsoft Azure Artifacts.
474+
- Confirmed OpenCode is a native ACP agent (`opencode acp`), not a TUI substituted for an ACP server
475+
and not a third-party adapter.
476+
477+
**Real wire**
478+
479+
- Ran `initialize`, `session/new`, and `session/prompt` against native OpenCode ACP with an isolated
480+
inline OpenAI-compatible provider pointed at local Agent Maestro.
481+
- Handshake reported `agentInfo.name = OpenCode`, version `1.18.3`, protocol version 1.
482+
- Prompt completed with `end_turn`, returned the expected marker, and emitted one standard
483+
`usage_update`: `used=6092`, `size=271790`, `cost=0 USD`.
484+
- Prompt response Usage reported input 460, output 11, thought 15, cached-read 5632, total 6118.
485+
This confirms context occupancy is input + cached-read (`6092`), not total processed tokens.
486+
- `0 USD` is a deliberate zero-rate custom model result. Maestro exposes AIC rates, while OpenCode
487+
labels ACP cost as USD; the POC does not convert or relabel AICs.
488+
- Raw/pretty wire, stderr, summary, sanitized config, and the PowerShell capture harness remain
489+
local under ignored `test/e2e/artifacts/opencode-acp/`; the wire scan found no sensitive keys.
490+
491+
**TDD / characterization**
492+
493+
- Added the verified OpenCode payload as a provider-local Rust characterization test before making
494+
any production change.
495+
- The focused test passed on its first run because the existing standard ACP normalizer already
496+
handles OpenCode correctly. No private parser, runtime provider dispatch, credential access, or
497+
provider API call was added merely to manufacture a RED phase.
498+
- The test also locks `opencode` to `StandardAcpOnly` with an empty private trusted-reporter
499+
allowlist, preserving the fail-closed provider boundary.
500+
501+
**Validation**
502+
503+
- Real OpenCode ACP capture: 1 standard Usage update; marker present; sensitive-key hits: 0.
504+
- Focused offline Rust test:
505+
`normalizes_verified_opencode_1_18_3_standard_usage_capture` passed (1 passed, 0 failed).
506+
- Offline Usage/provider slice: 10 passed, 0 failed.
507+
- Targeted `rustfmt --check tools/wta/src/usage/providers/opencode.rs`: passed.
508+
- Full offline WTA Rust suite: 1144 passed, 0 failed, 0 ignored.
509+
- OpenCode source tests were not run because the monorepo dependency set includes non-CFS direct
510+
URLs; no public-source exception or bypass was attempted.
511+
512+
**Committed files**
513+
514+
- `tools/wta/src/usage/providers/opencode.rs`
515+
- `doc/investigation/acp-price-calc.md`
516+
- `doc/investigation/acp-price-calc-track.md`
517+
- No package cache, OpenCode binary, raw wire, local capture harness, credentials, or user-global
518+
OpenCode configuration.
461519
- Windows PowerShell 5.1 module import was rejected by the 7.2 requirement.
462520
- Both policy setup tools rejected Windows PowerShell before UAC or registry work.
463521
- ACP mock probe launched through the canonical host and completed initialize, tokens-only Usage,

doc/investigation/acp-price-calc.md

Lines changed: 80 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -296,8 +296,9 @@ Claude binary;不要求另行安装全局 Claude Code CLI。它也会读取 us
296296
- 已运行 `Agent Maestro: Configure Claude Code Settings`,user-level
297297
`~/.claude/settings.json` 指向 `http://127.0.0.1:23333/api/anthropic`,模型为
298298
`claude-sonnet-5[1m]`
299-
- `npx` 下载 pinned adapter 时仍出现间歇性 `ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE`
300-
adapter 尚未成功缓存/启动;需要先恢复到 `registry.npmjs.org` 的稳定 TLS 连接。
299+
- `npx` 下载 pinned adapter 曾出现间歇性 `ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE`。公司设备
300+
不得切回公共 npm registry;package 必须通过
301+
`https://packagefeedproxy.microsoft.io/npm/` 或批准的 Azure Artifacts feed 获取。
301302

302303
#### 可行的实验步骤
303304

@@ -443,7 +444,83 @@ compatible family;如果 handshake 未返回该 ID,保持 Generic/unknown。
443444
首版产品方案仍不把 Agent Maestro 作为正式 provider dependency;它仅作为本地实验桥和
444445
fixture 产生工具。
445446

446-
### 2.5 Gemini 暂停,后续迁移到 Antigravity
447+
### 2.5 OpenCode native ACP Usage 调查(2026-07-24)
448+
449+
OpenCode 不需要第三方 adapter。发布版 `opencode-ai@1.18.3` 原生提供:
450+
451+
```text
452+
opencode acp
453+
```
454+
455+
公司设备上的安装固定使用 Microsoft-protected npm feed,未访问公共 npm registry:
456+
457+
```powershell
458+
npm install --global opencode-ai@1.18.3 `
459+
--registry https://packagefeedproxy.microsoft.io/npm/ `
460+
--include=optional
461+
```
462+
463+
`opencode-ai@1.18.3``opencode-windows-x64@1.18.3` 的 tarball 均来自 Microsoft Azure
464+
Artifacts。当前本地 OpenCode `dev` 源码版本为 `1.18.4`;源码 monorepo lockfile 还包含
465+
`github:``pkg.pr.new` 直链,因此在这些依赖有 approved internal source 前,不使用公共源
466+
强行完成全仓 `bun install`
467+
468+
真实 ACP round-trip 使用隔离配置,将 OpenCode 的 OpenAI-compatible provider 指向本机 Agent
469+
Maestro `http://127.0.0.1:23333/api/openai/v1`。OpenCode handshake 报告:
470+
471+
```json
472+
{
473+
"agentInfo": { "name": "OpenCode", "version": "1.18.3" },
474+
"protocolVersion": 1
475+
}
476+
```
477+
478+
一次真实 `session/prompt` 在 agent message chunks 后发送:
479+
480+
```json
481+
{
482+
"sessionUpdate": "usage_update",
483+
"used": 6092,
484+
"size": 271790,
485+
"cost": { "amount": 0, "currency": "USD" }
486+
}
487+
```
488+
489+
同一 prompt response 的 Usage 为:
490+
491+
```json
492+
{
493+
"inputTokens": 460,
494+
"outputTokens": 11,
495+
"thoughtTokens": 15,
496+
"cachedReadTokens": 5632,
497+
"totalTokens": 6118
498+
}
499+
```
500+
501+
这验证了 OpenCode 的 `used` 口径是当前 assistant message 的
502+
`inputTokens + cachedReadTokens`,即 `460 + 5632 = 6092`,而不是 `totalTokens``size` 来自
503+
provider/model config 的 context limit。源码 `packages/opencode/src/acp/usage.ts` 也显示同一
504+
算法,并将当前 session 所有 assistant message 的 `cost` 累加为标准 ACP USD cost。
505+
506+
本次 `0 USD` 不是实际账单。Agent Maestro model catalog 报告的是 AICs/1M tokens,而 OpenCode
507+
ACP 当前把 currency 固定为 `USD`;测试配置故意将价格设为零,避免把 AIC rates 错标成 USD。
508+
若未来通过 OpenCode 使用真实 provider,cost 仍是 OpenCode 根据 message usage 与 model price
509+
metadata 计算或使用 Copilot `totalNanoAiu` metadata 得到的报告值,必须单独验证 issuer、单位和
510+
定价来源,不能仅因 ACP 字段写着 USD 就承诺账单准确。
511+
512+
完整 raw/pretty wire、stderr、去敏配置与摘要保存在本地 ignored 目录:
513+
514+
```text
515+
test/e2e/artifacts/opencode-acp/real-wire/
516+
```
517+
518+
POC 结论:OpenCode `1.18.3` 已发送合法的标准 ACP `usage_update`,现有 provider-neutral
519+
normalizer 可以直接消费;`opencode` private adapter 继续保持 `StandardAcpOnly`、空 trusted
520+
reporter allowlist 和 no-op,不需要新增 runtime dispatch、私有 schema parser 或 provider API
521+
访问。
522+
523+
### 2.6 Gemini 暂停,后续迁移到 Antigravity
447524

448525
这是产品范围决策,不是对 Gemini 当前 ACP 能力的技术否定:Google 已发布新的 agent tool
449526
Antigravity,而 Intelligent Terminal 计划未来逐步迁移到它。为避免在即将退出的 provider

tools/wta/src/usage/providers/opencode.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,35 @@ impl ProviderUsageAdapter for OpenCodeUsageAdapter {
2727
super::no_verified_private_usage(request)
2828
}
2929
}
30+
31+
#[cfg(test)]
32+
mod tests {
33+
use super::*;
34+
use crate::usage::{normalize_standard_usage, UsageCost, UsageSnapshot};
35+
use agent_client_protocol as acp;
36+
37+
#[test]
38+
fn normalizes_verified_opencode_1_18_3_standard_usage_capture() {
39+
let update: acp::schema::v1::UsageUpdate = serde_json::from_str(
40+
r#"{"used":6092,"size":271790,"cost":{"amount":0,"currency":"USD"}}"#,
41+
)
42+
.expect("verified OpenCode UsageUpdate should deserialize");
43+
44+
assert_eq!(
45+
normalize_standard_usage(&update),
46+
Ok(UsageSnapshot {
47+
used: 6092,
48+
size: 271790,
49+
cost: Some(UsageCost {
50+
amount_decimal_text: "0".to_string(),
51+
currency: "USD".to_string(),
52+
}),
53+
})
54+
);
55+
assert_eq!(
56+
ADAPTER.private_usage_policy(),
57+
PrivateUsagePolicy::StandardAcpOnly
58+
);
59+
assert!(ADAPTER.trusted_reporter_ids().is_empty());
60+
}
61+
}

0 commit comments

Comments
 (0)