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
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ put the values under an `env` object. Empty or whitespace-only values are ignore
{
"env": {
"ANTHROPIC_API_KEY": "",
"ANTHROPIC_AUTH_TOKEN": "",
"ANTHROPIC_BASE_URL": "",
"ANTHROPIC_MODEL": ""
}
Expand All @@ -105,7 +106,8 @@ put the values under an `env` object. Empty or whitespace-only values are ignore

| Variable | Required | Default | Description |
| --- | --- | --- | --- |
| `ANTHROPIC_API_KEY` | Yes | — | Your Anthropic API key, or the key for a third-party / proxy service. |
| `ANTHROPIC_API_KEY` | One credential required | — | Your Anthropic API key, or an API key accepted by a third-party service. |
| `ANTHROPIC_AUTH_TOKEN` | One credential required | — | A bearer token used instead of `ANTHROPIC_API_KEY`; OpenRouter recommends this mode for its Anthropic-compatible endpoint. |
| `ANTHROPIC_BASE_URL` | No | `https://api.anthropic.com` | Point the SDK at a non-official / proxy endpoint. Leave it unset to use the official API — an empty value breaks requests. |
| `ANTHROPIC_MODEL` | No | `claude-sonnet-4-6` | Override the model. An empty or whitespace-only value falls back to the default. |

Expand Down
4 changes: 3 additions & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ uvx --from "git+https://github.com/minixalpha/[email protected]" nanoPyCode
{
"env": {
"ANTHROPIC_API_KEY": "",
"ANTHROPIC_AUTH_TOKEN": "",
"ANTHROPIC_BASE_URL": "",
"ANTHROPIC_MODEL": ""
}
Expand All @@ -100,7 +101,8 @@ uvx --from "git+https://github.com/minixalpha/[email protected]" nanoPyCode

| 变量 | 是否必填 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `ANTHROPIC_API_KEY` | 是 | 无 | 你的 Anthropic API key,或第三方/代理服务的 key。 |
| `ANTHROPIC_API_KEY` | 两种凭据任选其一 | 无 | 你的 Anthropic API key,或第三方服务接受的 API key。 |
| `ANTHROPIC_AUTH_TOKEN` | 两种凭据任选其一 | 无 | 代替 `ANTHROPIC_API_KEY` 使用的 bearer token;OpenRouter 的 Anthropic-compatible endpoint 推荐这种方式。 |
| `ANTHROPIC_BASE_URL` | 否 | `https://api.anthropic.com` | 将 SDK 指向非官方/代理 endpoint。使用官方 API 时保持不设置;留空值会导致请求失败。 |
| `ANTHROPIC_MODEL` | 否 | `claude-sonnet-4-6` | 覆盖默认模型。空值或纯空白会回退到默认值。 |

Expand Down
5 changes: 5 additions & 0 deletions docs/changelogs/0.8.x.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ All notable changes in the **0.8.x** release series are documented here.
user/model steps, tool observations, token metrics, and terminal state,
while preserving text stdout, refusing to overwrite an existing file, and
explicitly marking fields shortened by Event Journal persistence limits.
- Provider-reported model costs in ATIF trajectories. Costs are captured
directly from response usage when available or reconciled asynchronously by
generation ID with bounded retries, support both API-key and bearer-token
authentication, and retain per-attempt diagnostics when reconciliation
remains incomplete.

### Fixed
- Declare `httpx` as a direct runtime dependency so clean and containerized
Expand Down
8 changes: 8 additions & 0 deletions docs/dev_docs/en/event-journal-protocol-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,14 @@ stable user-output contract. External programs SHOULD NOT treat

## Versioning and compatibility

A terminal `run.completed` or `run.failed` payload may include an optional
`cost_reconciliation` array. Each item contains a `generation_id`, a final
`resolved` or `unresolved` status, and an `attempts` list. Attempts retain only
their ordinal, outcome classification, and, when applicable, an HTTP status or
exception type; credentials and response bodies are never stored. This adds
diagnostics without changing terminal-event semantics and is therefore a
v1-compatible optional payload extension.

A v1 reader fails closed on an unknown `schema_version` or event type. The
compatibility rules are:

Expand Down
25 changes: 22 additions & 3 deletions docs/dev_docs/zh-CN/event-journal-protocol-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ v1 支持九种事件:
| `model.started` | 一次模型调用开始。 |
| `model.output_delta` | 模型流式产生一段文本。 |
| `model.completed` | 一次模型调用成功完成,最终消息和 usage 已可用。 |
| `model.cost_resolved` | provider 的异步账单查询已解析一次模型调用的真实 cost。 |
| `tool.started` | 一个工具调用开始。 |
| `tool.completed` | 一个工具调用以正常结果、工具级错误或异常结束。 |
| `run.completed` | Agent Run 正常结束,包括达到轮次上限。 |
Expand Down Expand Up @@ -188,6 +189,7 @@ interactive 模式下,每次用户输入建立一个新的 Agent Run 和 Journ
| `usage` | object 或 `null` | 本次模型调用的 token usage;schema 见下文。 |
| `provider_response_id` | non-empty string 或 `null` | provider 原始 response/message ID。 |
| `generation_id` | non-empty string 或 `null` | provider generation ID;当前从 `x-generation-id` response header 读取。 |
| `cost` | object(可选) | 本次调用的真实 cost 状态;schema 见下文。旧版 v1 entry 可以没有该字段。 |
| `duration_ms` | non-negative number | 从开始请求到完整消息和响应头可用的耗时。 |

`content` 支持以下 block:
Expand All @@ -209,7 +211,24 @@ interactive 模式下,每次用户输入建立一个新的 Agent Run 和 Journ
| `cache_read_input_tokens` | 否 | non-negative integer | 从 prompt cache 读取的输入 token 数。 |
| `cache_creation_input_tokens` | 否 | non-negative integer | 写入 prompt cache 的输入 token 数。 |

provider 返回的其他 JSON usage 字段可以原样保留。v1 不根据 usage 或价格目录计算 cost。
provider 返回的其他 JSON usage 字段可以原样保留。v1 不根据 token usage 或价格目录估算 cost。

`cost.status` 为 `resolved`、`pending` 或 `unknown`。响应直接带有真实 cost 时,`resolved` 同时记录 decimal string `amount`、`currency: "USD"`、`source: "provider_response.usage.cost"` 和 `kind: "provider_reported"`;有 generation ID 但响应未带 cost 时记录 `pending`;没有可补账身份时记录 `unknown`。未知 cost 不能写成零。

### `model.cost_resolved`

| 字段 | 类型 | 含义 |
|---|---|---|
| `generation_id` | non-empty string | 与 `model.completed` 关联的 provider generation ID。 |
| `amount` | non-negative finite decimal string | provider 报告的实际金额。 |
| `currency` | non-empty string | 金额币种;OpenRouter 当前为 `USD`。 |
| `source` | non-empty string | 事实来源;generation 查询当前为 `provider_generation.total_cost`。 |
| `model` | non-empty string(可选) | generation 记录返回的实际模型。 |
| `provider_name` | non-empty string(可选) | generation 记录返回的上游 provider。 |

该事件只追加、不修改先前的 `model.completed`。查询地址由当前 Anthropic SDK base URL 同源派生为 `v1/generation`,不会按 provider 名称把 credential 发送到另一个固定渠道。实现已用 OpenRouter 验证;其他兼容 provider 不提供 generation ID 或该查询接口时保持未知。查询失败不产生该事件,也不改变 run 的成功或失败终态。

`run.completed` 与 `run.failed` 可以带可选的 `cost_reconciliation` array。每项包含 `generation_id`、最终 `status`(`resolved` 或 `unresolved`)和 `attempts`;attempt 只保存序号、结果分类,以及适用时的 HTTP 状态码或异常类型,不保存 credential 或响应正文。该字段为现有终态增加诊断信息,不改变终态语义,因此属于 v1 兼容的可选 payload 扩展。

### `tool.started`

Expand Down Expand Up @@ -372,7 +391,7 @@ Journal 明确记录:
- 本次用户输入;
- 完整模型输出、流式文本和工具调用;
- 完整工具输入和返回给模型的工具结果;
- provider message/generation ID、stop reason 和 usage
- provider message/generation ID、stop reason、usage 和真实 cost
- 产生本次运行的程序名称和包版本;
- 错误类型、错误消息和各阶段耗时。

Expand All @@ -384,7 +403,7 @@ v1 没有专门记录:
- 完整 provider request、HTTP header 或 SDK 原始 response;
- system prompt 和发给模型的完整历史快照;
- spinner、ANSI 颜色、提示符、banner 等 stdout 表现细节;
- token cost 或价格目录解析结果
- 价格目录或由 token 数估算的 cost
- session 身份、跨 run 父子关系;
- ATIF trajectory 或 public `stream-json` 记录。

Expand Down
15 changes: 15 additions & 0 deletions docs/dev_notes/en/0.8.x.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,3 +210,18 @@ uv run --project benchmarks/harbor \
```

Development acceptance requires the validator to return zero, stdout to retain its existing text format, and the target to contain complete JSON. Harbor adapter collection and a real trial belong to the later Harbor integration acceptance work.

#### Provider-reported cost collection and reconciliation

**Goal:** trajectories record the actual model-call charges reported by the provider. The provider response is the source of truth; the Event Journal preserves the direct or deferred resolution state; and ATIF exposes per-step `metrics.cost_usd` plus a complete run total only when every billable call has been resolved.

There are two collection paths:

- **Synchronous response accounting.** OpenRouter's [Usage Accounting](https://openrouter.ai/docs/cookbook/administration/usage-accounting) defines a complete `usage` object for OpenAI-compatible Chat Completions and streaming responses. Its `usage.cost` is the total amount charged for the request, delivered in the complete non-streaming response or the final SSE message. When present, nano records it immediately as a resolved provider-reported USD cost. Token usage does not imply that cost is present: OpenRouter's [Anthropic Messages API](https://openrouter.ai/docs/api/api-reference/anthropic-messages/create-messages) preserves an Anthropic-compatible usage schema containing token, cache, service-tier, and speed fields but does not define `cost`. nano currently calls this endpoint through the Anthropic SDK, so a response may contain token counts and an extension such as `speed: "standard"` without a cost.
- **Asynchronous generation reconciliation.** When `usage.cost` is absent but `X-Generation-Id` is available, nano records the generation ID and marks the cost pending. This covers the current OpenRouter Anthropic Messages path and compatible APIs whose usage record becomes visible after the model response. OpenRouter's documented alternative is to retain the generation ID, then call [`GET /api/v1/generation?id=...`](https://openrouter.ai/docs/api/api-reference/generations/get-generation) and read `data.total_cost`. The endpoint documents 404, 429, and 5xx responses, so possession of an ID does not guarantee that its billing record is immediately queryable.

The lookup endpoint is derived from the configured Anthropic SDK base URL and stays on the same origin. Reconciliation uses six bounded attempts with exponential delays of 1, 2, 4, 8, and 15 seconds, for about 30 seconds of total waiting. A missing record, transient HTTP status, network error, or HTTP 200 response without `total_cost` remains retryable; permanent client and authentication failures stop immediately. A successful lookup appends `model.cost_resolved` without rewriting the earlier `model.completed` entry.

Reconciliation is observable rather than collapsing every failure into `None`. The terminal Journal event carries an optional `cost_reconciliation` list with the final status for each generation and each attempt classified as resolved, cost unavailable, HTTP error with its status code, request error with its exception type, or unsupported endpoint. Credentials and response bodies are never persisted. The ATIF projector copies the diagnostics into `extra.terminal.cost_reconciliation`. Cost enrichment remains best effort and never changes the task outcome; an unknown amount is never reported as zero.

**Validation:** unit tests cover direct response extraction, pending and unknown states, successful deferred retry, permanent-error short circuiting, bounded failure, and attempt diagnostics. Agent event tests verify successful reconciliation before the terminal event and persistence of unresolved diagnostics. ATIF tests cover per-step costs, complete and partial totals, and terminal diagnostics. The complete acceptance command is `uv run pytest`.
Loading