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
2 changes: 2 additions & 0 deletions .agents/skills/agenthub-dev/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ CHANGELOG.md One brief line per release linking into change
- **Follow the reference client; do not redesign.** `gpt5_5/` is the shape for Responses-style protocols, `openai/` for Chat Completions: same method order, same control flow, same names. A new client should read as a diff against its reference, because that is how it will be reviewed.
- **Keep it readable top to bottom.** A reader should follow one request or response straight through the file without jumping between definitions, so inlining is the default: read the SDK's attributes directly (`model_output.delta`, `model_output.item.call_id`) and keep field access, usage arithmetic, and error text where they are used. Extract a helper only for a genuinely large, self-contained block — the kind that would bury the main flow if inlined, such as fetching and decoding an image — never for a few lines. Mirror the reference client's own private methods (`_convert_thinking_level_*`, `_convert_tool_choice`) instead of inventing a layer beside them; a shim that accepts both dicts and SDK objects is never one of them, because the events are typed with the SDK's own types.
- **Stream on deltas only.** Open a partial tool call on the item-added event (name plus call id), accumulate its arguments from the argument deltas, and emit the complete `tool_call` item at the terminal event, exactly as `gpt5_5` does — every client must emit a complete `tool_call`, not just partials. List completion events (`response.output_item.done` and its equivalents) with the ignored types: never re-read a completed item or cross-check it against what the deltas produced. Leave provider error events (`response.failed`, `response.error`, `error`) to the unknown-event guard rather than translating them into AgentHub errors.
- **`unused` never leaves the client.** A wire event a client has nothing universal to emit for gets `event_type: "unused"`, and the streaming loop skips it — it is the client's own bookkeeping, not an event a caller may see. The base client drops one that escapes and raises on it under `AGENTHUB_DEBUG`, so a client that forgets the filter fails in CI rather than shipping empty events.
- **The message transform keeps the order of the content items.** Whatever a turn produced — thinking, then text, then a tool call — has to leave the transform in that order. Where a protocol makes an item its own entry (a Responses `reasoning` / `function_call`, say) and the message text is collected separately, flush the collected text before appending the entry; a message appended after the items it preceded is what DeepSeek answers with "No tool output found for tool call". Anthropic Messages and Gemini keep one ordered block list per message, so appending in item order is enough; Chat Completions splits content and `tool_calls` into fields of one message and carries no interleaving at all.
- `UniConfig` keys rarely map one-to-one onto provider config keys. **Stop and ask**: list every non-obvious mapping and confirm it with the user before coding. Never decide silently.
- Every `ThinkingLevel` must stay usable on every client — never raise for a thinking level. Map each level to the closest level the model supports and degrade silently when a level has no exact equivalent (e.g. `gemini3_7` maps `NONE` to `MINIMAL`, or to `low` on the models that reject `minimal`; `kimi_k3` maps `NONE` to `low` because K3 cannot disable reasoning).
- `temperature` and `tool_choice` (and other unsupported parameter values, e.g. `prompt_caching`) may reject with an exception, but must raise the AgentHub-specific `UnsupportedParameterError` from `errors.py` / `errors.ts`, never a bare `ValueError`/`Error`. Keep the message wording consistent with existing clients (containing "not support").
Expand Down
51 changes: 42 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,22 +46,37 @@ https://github.com/user-attachments/assets/c49a21a1-5bf9-4768-a76d-f73c9a03ca87
| Claude 4.6-5 | Official/Amazon Bedrock/UModelVerse | `claude-opus-5` | Text, Image | Text |
| GPT-5.4-5.6 | Official/OpenRouter/UModelVerse | `gpt-5.6-sol` | Text, Image | Text, Embedding |
| Kimi-K2.5/K2.6/K3 | Official/OpenRouter/SiliconFlow | `kimi-k3` | Text, Image | Text |
| DeepSeek V4 | Official/OpenRouter/SiliconFlow | `deepseek-v4-pro` | Text | Text |
| DeepSeek V4 | Official/OpenRouter/SiliconFlow | `deepseek-v4-pro` | Text, Image | Text |
| GLM-5.1-5.3 | Official/OpenRouter/SiliconFlow | `glm-5.3` | Text | Text |
| MiniMax-M3 | Official | `MiniMax-M3` | Text, Image | Text |
| Qwen3.6 | OpenRouter/SiliconFlow/vLLM | `qwen/qwen3.6-35b-a3b` | Text, Image | Text, Embedding |

Beyond the model-specific clients, three generic protocol clients call any compatible
endpoint: `client_type="openai-chat"` (OpenAI Chat Completions; bare `"openai"` is an
alias), `"openai-responses"` (OpenAI Responses, served by OpenAI, OpenRouter, DeepSeek,
Z.AI, and MiniMax), and `"ant-messages"` (Anthropic Messages, served by Anthropic,
OpenRouter, DeepSeek, Z.AI, and MiniMax). Where a gateway serves more than one, prefer
`"openai-responses"`: OpenRouter serves it for every model it hosts, while SiliconFlow
serves Chat Completions only.
endpoint:

- **`client_type="openai-chat"`** — OpenAI Chat Completions. Bare `"openai"` is an alias.
- **`client_type="openai-responses"`** — OpenAI Responses, served by OpenAI, OpenRouter,
DeepSeek, Z.AI, and MiniMax.
- **`client_type="ant-messages"`** — Anthropic Messages, served by Anthropic, OpenRouter,
DeepSeek, Z.AI, and MiniMax.

Where a gateway serves more than one, prefer `"openai-responses"`: OpenRouter serves it for
every model it hosts, while SiliconFlow serves Chat Completions only.

The full machine-readable list — model, base URL, client, input/output modalities, context
window, and per-million-token pricing in USD or CNY — is available via
`agenthub.list_supported_models()` (Python) / `listSupportedModels()` (TypeScript).
window, and per-million-token pricing in USD or CNY:

```python
from agenthub import list_supported_models

models = list_supported_models(currency="CNY") # "USD" by default
```

```typescript
import { listSupportedModels } from "@prismshadow/agenthub";

const models = listSupportedModels("CNY"); // "USD" by default
```

## Installation

Expand Down Expand Up @@ -668,6 +683,24 @@ cd src_ts && npm run playground
You can access the playground at `http://localhost:25751/`.
The integrated tracer is available at `http://localhost:25751/tracer/`.

## Wire Protocols

Every client speaks one vendor protocol on the wire, whichever `client_type` reaches it:

| `client_type` | Wire protocol |
| ---------------------------------------------------------- | ------------------ |
| `gemini-3.7`, `gemini-3.6`, `gemini-3`, `gemini-embedding` | `google-genai` |
| `claude-5`, `claude-4-8`, `claude-4-7`, `claude-4-6` | `ant-messages` |
| `ant-messages` | `ant-messages` |
| `gpt-5.6`, `gpt-5.5`, `gpt-5.4` | `openai-responses` |
| `deepseek-v4` | `openai-responses` |
| `minimax-m3` | `openai-responses` |
| `openai-responses` | `openai-responses` |
| `glm-5.3`, `glm-5.2`, `glm-5.1` | `openai-chat` |
| `kimi-k3`, `kimi-k2.6`, `kimi-k2.5` | `openai-chat` |
| `openai-chat` (alias `openai`) | `openai-chat` |
| `openai-embedding` | `openai-embedding` |

## Related Work

- [OpenRouter](https://openrouter.ai/)
Expand Down
40 changes: 40 additions & 0 deletions changelog/unreleased/2026-08-21-deepseek-responses-protocol.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# DeepSeek moves onto the OpenAI Responses protocol

- **Date:** 2026-08-21
- **Type:** refactor
- **Scope:** `deepseek_v4`, `registry`, `tests`
- **PR:** [#185](https://github.com/Prism-Shadow/agenthub/pull/185)

[中文版](2026-08-21-deepseek-responses-protocol.zh.md)

## What changed

- `DeepSeekV4Client` calls `/responses` instead of `/chat/completions`, reading the
`response.reasoning_text.delta`, `response.output_text.delta`,
`response.function_call_arguments.delta` and `response.completed` events, and rebuilding a
replayed chain of thought as a `reasoning` item whose `content` is `reasoning_text`.
- The two SiliconFlow entries, `deepseek-ai/DeepSeek-V4-Flash` and
`deepseek-ai/DeepSeek-V4-Pro`, moved to the generic `openai-chat` client: SiliconFlow serves
Chat Completions only. The OpenRouter and official entries keep the `deepseek-v4` client.
- The DeepSeek cases in the empty-response, tool-call-argument and unknown-event unit suites
moved to the Responses wire shape, and those first two suites gained a case for the generic
`openai-responses` client alongside it.

- `README.md` gained a table of the wire protocol each `client_type` speaks: `google-genai`,
`ant-messages`, `openai-responses`, `openai-chat`, and OpenAI Embeddings.

## Configuration behavior

| `UniConfig` key | On the wire |
| --- | --- |
| `thinking_level` | `reasoning.effort`, pre-mapped to what DeepSeek settles on: `none` / `low` / `high` / `high` / `high` / `max`. Effort `none` turns thinking off; the Chat Completions `thinking` toggle is ignored on this endpoint |
| `thinking_summary` | left out: the endpoint accepts `summary` but never generates one |
| `max_tokens` | `max_output_tokens` |
| `system_prompt` | `instructions` |
| `temperature` | `UnsupportedParameterError` unless it is `1.0` |
| `tool_choice` | `auto` and `none` only; anything else raises `UnsupportedParameterError` |
| `fast_mode` | `UnsupportedParameterError`: `service_tier` is not supported |
| `prompt_caching` | `ENABLE` only; caching is automatic |

Usage arrives as `input_tokens` with `input_tokens_details.cached_tokens`, and `output_tokens`
with `output_tokens_details.reasoning_tokens`.
39 changes: 39 additions & 0 deletions changelog/unreleased/2026-08-21-deepseek-responses-protocol.zh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# DeepSeek 改用 OpenAI Responses 协议

- **Date:** 2026-08-21
- **Type:** refactor
- **Scope:** `deepseek_v4`, `registry`, `tests`
- **PR:** [#185](https://github.com/Prism-Shadow/agenthub/pull/185)

[English](2026-08-21-deepseek-responses-protocol.md)

## 变更内容

- `DeepSeekV4Client` 改为调用 `/responses` 而非 `/chat/completions`,解析
`response.reasoning_text.delta`、`response.output_text.delta`、
`response.function_call_arguments.delta` 与 `response.completed` 事件,回放思维链时重建成
`content` 为 `reasoning_text` 的 `reasoning` 条目。
- SiliconFlow 的两个条目 `deepseek-ai/DeepSeek-V4-Flash` 与 `deepseek-ai/DeepSeek-V4-Pro` 改用通用
的 `openai-chat` 客户端:SiliconFlow 只提供 Chat Completions。OpenRouter 与官方条目仍走
`deepseek-v4` 客户端。
- 空响应、工具参数与未知事件三个单测套件里的 DeepSeek 用例改为 Responses 线格式,前两个套件同时
新增了通用 `openai-responses` 客户端的用例。

- `README.md` 新增一张表,列出每个 `client_type` 在线上说的协议:`google-genai`、`ant-messages`、
`openai-responses`、`openai-chat` 与 OpenAI Embeddings。

## 配置映射

| `UniConfig` 键 | 线上字段 |
| --- | --- |
| `thinking_level` | `reasoning.effort`,按 DeepSeek 实际生效的值预先映射:`none` / `low` / `high` / `high` / `high` / `max`。effort 取 `none` 才能关闭思考,Chat Completions 的 `thinking` 开关在该端点上被忽略 |
| `thinking_summary` | 不下发:该端点接受 `summary` 但从不生成 |
| `max_tokens` | `max_output_tokens` |
| `system_prompt` | `instructions` |
| `temperature` | 非 `1.0` 时抛 `UnsupportedParameterError` |
| `tool_choice` | 只接受 `auto` 与 `none`,其余抛 `UnsupportedParameterError` |
| `fast_mode` | 抛 `UnsupportedParameterError`:不支持 `service_tier` |
| `prompt_caching` | 只接受 `ENABLE`,缓存由服务端自动处理 |

用量字段为 `input_tokens`(含 `input_tokens_details.cached_tokens`)与 `output_tokens`
(含 `output_tokens_details.reasoning_tokens`)。
25 changes: 25 additions & 0 deletions changelog/unreleased/2026-08-21-deepseek-vision.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# deepseek-v4-flash-vision-exp reads images, in a prompt and in a tool result

- **Date:** 2026-08-21
- **Type:** feature
- **Scope:** `deepseek_v4`, `registry`, `llmsdk_docs`, `tests`
- **PR:** [#185](https://github.com/Prism-Shadow/agenthub/pull/185)

[中文版](2026-08-21-deepseek-vision.zh.md)

## What changed

- `deepseek-v4-flash-vision-exp` joined the registry on the official API with `Text, Image`
input, a 1M context window, and the `deepseek-v4-flash` price (¥1.5 / ¥4.5 per million
tokens off-peak, ¥0.05 on a cache hit).
- `DeepSeekV4Client` sends an `image_url` item as an `input_image` content part and a
`tool_result` image as an `input_image` inside `function_call_output`, so a tool can hand the
model a picture it produced. Both an HTTP(S) URL and a base64 data URL are accepted.
- A model without vision refuses an image item with
`DeepSeek <model> does not support image inputs.` rather than sending one: Chat Completions
answers `400` and the Responses API silently substitutes placeholder text.
- `deepseek-v4-flash-vision-exp` replaced `deepseek-v4-flash` as the official DeepSeek entry in
both E2E model lists, with image understanding enabled; the three protocol-mode entries stay
on `deepseek-v4-flash`.
- `llmsdk_docs/deepseek_v4/` gained snapshots of the vision and Responses API guides, and its
quickstart lists the third model.
22 changes: 22 additions & 0 deletions changelog/unreleased/2026-08-21-deepseek-vision.zh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# deepseek-v4-flash-vision-exp 可读图片,提示词与工具返回都支持

- **Date:** 2026-08-21
- **Type:** feature
- **Scope:** `deepseek_v4`, `registry`, `llmsdk_docs`, `tests`
- **PR:** [#185](https://github.com/Prism-Shadow/agenthub/pull/185)

[English](2026-08-21-deepseek-vision.md)

## 变更内容

- 注册表新增官方 API 上的 `deepseek-v4-flash-vision-exp`,输入模态为 `Text, Image`,上下文窗口
1M,价格与 `deepseek-v4-flash` 相同(谷值每百万 token 输入 ¥1.5、输出 ¥4.5,命中缓存 ¥0.05)。
- `DeepSeekV4Client` 把 `image_url` 条目发成 `input_image` 内容块,把 `tool_result` 里的图片发成
`function_call_output` 内的 `input_image`,于是工具可以把自己产出的图片交给模型。HTTP(S) 链接与
base64 data URL 都可用。
- 不带视觉能力的模型遇到图片条目会直接报
`DeepSeek <model> does not support image inputs.` 而不是照发:Chat Completions 会返回 `400`,
Responses API 则会静默替换成占位文本。
- 两份 E2E 模型清单里,官方 DeepSeek 条目由 `deepseek-v4-flash` 换成
`deepseek-v4-flash-vision-exp` 并打开图像理解;三个协议模式条目仍用 `deepseek-v4-flash`。
- `llmsdk_docs/deepseek_v4/` 新增视觉与 Responses API 两份官方文档快照,quickstart 补上第三个模型。
22 changes: 22 additions & 0 deletions changelog/unreleased/2026-08-21-responses-input-order.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Responses input items keep the order the assistant produced them in

- **Date:** 2026-08-21
- **Type:** fix
- **Scope:** `deepseek_v4`, `openai_responses`, `gpt5_6`, `minimax_m3`, `tests`
- **PR:** [#185](https://github.com/Prism-Shadow/agenthub/pull/185)

[中文版](2026-08-21-responses-input-order.zh.md)

## What changed

- All four Responses-protocol clients — `DeepSeekV4Client`, `OpenaiResponsesClient`,
`GPT5_6Client` and `MiniMaxM3Client` — flush the message text collected so far before
appending a reasoning, `function_call` or `function_call_output` item, so an assistant turn
that spoke before calling a tool replays as text, then call, then output.
- Before the fix the assistant message was appended after the items it preceded, which DeepSeek
answered with `400 No tool output found for tool call <id>` on the next turn.
- `test_message_order.py` / `message-order.test.ts` pin the emitted order for every client
against a turn of thinking, text and a tool call: the Responses clients keep the three as
separate items in order, the Anthropic and Gemini clients keep them as ordered blocks inside
one message, and the Chat Completions clients keep their text-plus-`tool_calls` shape, which
carries no interleaving.
20 changes: 20 additions & 0 deletions changelog/unreleased/2026-08-21-responses-input-order.zh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Responses 输入条目保持助手产出时的顺序

- **Date:** 2026-08-21
- **Type:** fix
- **Scope:** `deepseek_v4`, `openai_responses`, `gpt5_6`, `minimax_m3`, `tests`
- **PR:** [#185](https://github.com/Prism-Shadow/agenthub/pull/185)

[English](2026-08-21-responses-input-order.md)

## 变更内容

- 四个 Responses 协议客户端 —— `DeepSeekV4Client`、`OpenaiResponsesClient`、`GPT5_6Client`
与 `MiniMaxM3Client` —— 在追加 reasoning、`function_call` 或 `function_call_output` 条目之前,
先把已经收集的消息正文落盘,因此「先说话再调工具」的助手轮次会按正文、调用、结果的顺序回放。
- 修复前助手消息被排到它本应领先的条目之后,DeepSeek 会在下一轮返回
`400 No tool output found for tool call <id>`。
- `test_message_order.py` / `message-order.test.ts` 用「思考 + 正文 + 工具调用」的一轮对话钉住每个
客户端产出的顺序:Responses 客户端保持三者为顺序排列的独立条目,Anthropic 与 Gemini 客户端保持
为同一条消息内按序排列的块,Chat Completions 客户端保持正文加 `tool_calls` 的形状(该协议本就
无法表达交错)。
22 changes: 22 additions & 0 deletions changelog/unreleased/2026-08-21-unused-event-guarantee.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# An internal "unused" event never reaches a caller

- **Date:** 2026-08-21
- **Type:** refactor
- **Scope:** `base_client`, `types`, `skills`, `tests`
- **PR:** [#185](https://github.com/Prism-Shadow/agenthub/pull/185)

[中文版](2026-08-21-unused-event-guarantee.zh.md)

## What changed

- `streaming_response` / `streamingResponse` drop an event whose `event_type` is `unused`
instead of forwarding it, and raise on one when `AGENTHUB_DEBUG` is set, so a client that
skips its own filter fails in CI rather than shipping empty events.
- The `EventType` definition in `types.py` / `types.ts` states what the four kinds mean and
that `unused` never leaves the client.
- The development skill records two client rules: `unused` stays inside the client, and the
message transform keeps the order of the content items, flushing collected message text
before appending an item that becomes its own input entry.
- `test_unknown_events.py` / `unknown-events.test.ts` run every client over the ignorable
events its protocol carries with the debug guard on, and a deliberately leaky client covers
both halves of the base-client behavior.
19 changes: 19 additions & 0 deletions changelog/unreleased/2026-08-21-unused-event-guarantee.zh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# 内部的 "unused" 事件绝不会流到调用方

- **Date:** 2026-08-21
- **Type:** refactor
- **Scope:** `base_client`, `types`, `skills`, `tests`
- **PR:** [#185](https://github.com/Prism-Shadow/agenthub/pull/185)

[English](2026-08-21-unused-event-guarantee.md)

## 变更内容

- `streaming_response` / `streamingResponse` 遇到 `event_type` 为 `unused` 的事件会直接丢弃而不再
向外抛出,并在设置了 `AGENTHUB_DEBUG` 时报错,于是漏掉自身过滤的客户端会在 CI 里失败,而不是把
空事件发出去。
- `types.py` / `types.ts` 里的 `EventType` 定义写清了四种取值的含义,以及 `unused` 不会离开客户端。
- 开发 skill 记下两条客户端规则:`unused` 只在客户端内部使用;消息 transform 必须保持内容条目的
顺序,在追加会成为独立输入条目的项之前先把已收集的正文落盘。
- `test_unknown_events.py` / `unknown-events.test.ts` 在打开 debug 守卫的情况下,让每个客户端跑一遍
自己协议里的可忽略事件;另有一个故意「漏事件」的客户端覆盖基类两种模式下的行为。
Loading
Loading