Skip to content

Commit f79d691

Browse files
authored
Release 0.4.6 (#187)
2 parents 8a33b01 + e23db95 commit f79d691

61 files changed

Lines changed: 2767 additions & 678 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/skills/agenthub-dev/SKILL.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ CHANGELOG.md One brief line per release linking into change
5656
- **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.
5757
- **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.
5858
- **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.
59+
- **`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.
60+
- **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.
5961
- `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.
6062
- 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).
6163
- `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").

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
Here, we record the addition and removal times of models, major functional updates, bug fixes, and release times of key versions. Each release keeps one brief line here; the per-entry summaries live in `changelog/<version>/README.md`, and every entry links its detail file.
66

7+
- [2026-08-21] [Version 0.4.6](changelog/0.4.6/README.md): `deepseek-v4-flash-vision-exp` joins the registry and reads images in a prompt and in a tool result, the DeepSeek client moves onto the OpenAI Responses protocol, every Responses client replays content items in the order the model produced them, an internal `unused` event never reaches a caller, and the playground plays a spoken answer as one clip and keeps its configuration across a reload.
8+
79
- [2026-08-20] [Version 0.4.5](changelog/0.4.5/README.md): streaming clients skip output they do not recognize unless `AGENTHUB_DEBUG` is set, `AutoLLMClient` lists the model ids an endpoint serves, every client accepts default headers for endpoints that demand their own, GLM-5.3 and `claude-opus-5` join the registry, and the OpenRouter entries move to the `openai-responses` client.
810

911
- [2026-08-19] [Version 0.4.4](changelog/0.4.4/README.md): thinking levels gain a `MAX` tier above `XHIGH`, and each client maps the ladder onto its vendor's effort vocabulary (DeepSeek re-mapped to its current low/high/max values).

CHANGELOG.zh.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
在这里,我们记录模型的新增与移除时间、主要功能更新、缺陷修复,以及关键版本的发布时间。每个发布版本在此保留一行简述;逐条目的摘要位于 `changelog/<version>/README.md`,且每个条目都会链接到自己的详情文件。
66

7+
- [2026-08-21] [版本 0.4.6](changelog/0.4.6/README.zh.md):注册表新增 `deepseek-v4-flash-vision-exp`,提示词与工具返回里的图片都能读;DeepSeek client 改用 OpenAI Responses 协议;全部 Responses client 按模型产出的顺序回放内容条目;内部的 `unused` 事件不会流到调用方;Playground 把语音回复合成一条音频播放,并在刷新后保留配置。
8+
79
- [2026-08-20] [版本 0.4.5](changelog/0.4.5/README.zh.md):流式 client 默认静默跳过自己不认识的输出,`AGENTHUB_DEBUG` 开启时才抛出;`AutoLLMClient` 可以列出 endpoint 提供的模型 id;全部 client 支持传入默认 header 以对接要求特定 header 的 endpoint;注册表新增 GLM-5.3 与 `claude-opus-5`;OpenRouter 条目改用 `openai-responses` client。
810

911
- [2026-08-19] [版本 0.4.4](changelog/0.4.4/README.zh.md):思考档位在 `XHIGH` 之上新增 `MAX` 一档,各 client 按自家服务方的 effort 取值映射整条档位(DeepSeek 依其当前的 low/high/max 取值重排)。

README.md

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,22 +46,37 @@ https://github.com/user-attachments/assets/c49a21a1-5bf9-4768-a76d-f73c9a03ca87
4646
| Claude 4.6-5 | Official/Amazon Bedrock/UModelVerse | `claude-opus-5` | Text, Image | Text |
4747
| GPT-5.4-5.6 | Official/OpenRouter/UModelVerse | `gpt-5.6-sol` | Text, Image | Text, Embedding |
4848
| Kimi-K2.5/K2.6/K3 | Official/OpenRouter/SiliconFlow | `kimi-k3` | Text, Image | Text |
49-
| DeepSeek V4 | Official/OpenRouter/SiliconFlow | `deepseek-v4-pro` | Text | Text |
49+
| DeepSeek V4 | Official/OpenRouter/SiliconFlow | `deepseek-v4-pro` | Text, Image | Text |
5050
| GLM-5.1-5.3 | Official/OpenRouter/SiliconFlow | `glm-5.3` | Text | Text |
5151
| MiniMax-M3 | Official | `MiniMax-M3` | Text, Image | Text |
5252
| Qwen3.6 | OpenRouter/SiliconFlow/vLLM | `qwen/qwen3.6-35b-a3b` | Text, Image | Text, Embedding |
5353

5454
Beyond the model-specific clients, three generic protocol clients call any compatible
55-
endpoint: `client_type="openai-chat"` (OpenAI Chat Completions; bare `"openai"` is an
56-
alias), `"openai-responses"` (OpenAI Responses, served by OpenAI, OpenRouter, DeepSeek,
57-
Z.AI, and MiniMax), and `"ant-messages"` (Anthropic Messages, served by Anthropic,
58-
OpenRouter, DeepSeek, Z.AI, and MiniMax). Where a gateway serves more than one, prefer
59-
`"openai-responses"`: OpenRouter serves it for every model it hosts, while SiliconFlow
60-
serves Chat Completions only.
55+
endpoint:
56+
57+
- **`client_type="openai-chat"`** — OpenAI Chat Completions. Bare `"openai"` is an alias.
58+
- **`client_type="openai-responses"`** — OpenAI Responses, served by OpenAI, OpenRouter,
59+
DeepSeek, Z.AI, and MiniMax.
60+
- **`client_type="ant-messages"`** — Anthropic Messages, served by Anthropic, OpenRouter,
61+
DeepSeek, Z.AI, and MiniMax.
62+
63+
Where a gateway serves more than one, prefer `"openai-responses"`: OpenRouter serves it for
64+
every model it hosts, while SiliconFlow serves Chat Completions only.
6165

6266
The full machine-readable list — model, base URL, client, input/output modalities, context
63-
window, and per-million-token pricing in USD or CNY — is available via
64-
`agenthub.list_supported_models()` (Python) / `listSupportedModels()` (TypeScript).
67+
window, and per-million-token pricing in USD or CNY:
68+
69+
```python
70+
from agenthub import list_supported_models
71+
72+
models = list_supported_models(currency="CNY") # "USD" by default
73+
```
74+
75+
```typescript
76+
import { listSupportedModels } from "@prismshadow/agenthub";
77+
78+
const models = listSupportedModels("CNY"); // "USD" by default
79+
```
6580

6681
## Installation
6782

@@ -117,7 +132,7 @@ AgentHub provides Codex/Claude Code skill files for assistants that need to help
117132
- `get_history()`: Returns the history of the stateful LLM client.
118133
- `set_history(history)`: Replaces the history of the stateful LLM client with a copy of the provided list.
119134

120-
Streaming clients skip output they do not recognize, so a gateway's own frames cannot end a generation. Set `AGENTHUB_DEBUG` to anything other than `0`, `false`, `no` or `off` to make them raise instead.
135+
Streaming clients skip output they do not recognize, so a gateway's own frames cannot end a generation, and an event a client has nothing universal to emit for never reaches you. Set `AGENTHUB_DEBUG` to anything other than `0`, `false`, `no` or `off` to make both raise instead.
121136

122137
## Basic Usage
123138

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

686+
## Wire Protocols
687+
688+
Every client speaks one vendor protocol on the wire, whichever `client_type` reaches it:
689+
690+
| `client_type` | Wire protocol |
691+
| ---------------------------------------------------------- | ------------------ |
692+
| `gemini-3.7`, `gemini-3.6`, `gemini-3`, `gemini-embedding` | `google-genai` |
693+
| `claude-5`, `claude-4-8`, `claude-4-7`, `claude-4-6` | `ant-messages` |
694+
| `ant-messages` | `ant-messages` |
695+
| `gpt-5.6`, `gpt-5.5`, `gpt-5.4` | `openai-responses` |
696+
| `deepseek-v4` | `openai-responses` |
697+
| `minimax-m3` | `openai-responses` |
698+
| `openai-responses` | `openai-responses` |
699+
| `glm-5.3`, `glm-5.2`, `glm-5.1` | `openai-chat` |
700+
| `kimi-k3`, `kimi-k2.6`, `kimi-k2.5` | `openai-chat` |
701+
| `openai-chat` (alias `openai`) | `openai-chat` |
702+
| `openai-embedding` | `openai-embedding` |
703+
671704
## Related Work
672705

673706
- [OpenRouter](https://openrouter.ai/)
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Gemini TTS requests carry a single text turn, and the playground plays one audio clip
2+
3+
- **Date:** 2026-08-20
4+
- **Type:** fix
5+
- **Scope:** `gemini3_7`, `base_client`, `integration`, `tests`
6+
- **PR:** [#184](https://github.com/Prism-Shadow/agenthub/pull/184)
7+
8+
[中文版](2026-08-20-gemini-tts-single-turn.zh.md)
9+
10+
## What changed
11+
12+
- `Gemini3_7Client` sends only the newest message to a TTS model and leaves the rest of the
13+
conversation out of the request, so a stateful session keeps working after its first spoken
14+
answer instead of replaying recorded audio the model refuses.
15+
- `Gemini3_7Client` builds a TTS request from the speech settings and `max_tokens` alone; a system
16+
prompt, a thinking level or summary, tools, a tool choice, and an image config are left out, so
17+
configuration carried over from another model no longer turns a synthesis request into a 400.
18+
- `concat_uni_events_to_uni_message` / `concatUniEventsToUniMessage` merge consecutive
19+
`inline_data` items that share an `audio/` mime type, so a spoken response is one item in the
20+
history and one entry in the tracer rather than one per streamed chunk.
21+
- The playground collects the audio chunks of a response behind a `🔊 Receiving audio...` line that
22+
counts up the received duration, then renders one player when the stream ends; an interrupted
23+
stream keeps the audio that had already arrived. Chunks are joined as bytes before the WAV header
24+
is written, and the sample rate and channel count are read from the mime type parameters
25+
(`audio/l16; rate=24000; channels=1`).
26+
- The finished clip plays once by itself, and a browser that blocks autoplay leaves the player
27+
ready to press. A stream stopped from the Stop button does not play. The token footer is appended
28+
to the message card instead of re-parsed into it, which would have restarted the clip.
29+
30+
## TTS request limits
31+
32+
`gemini-3.1-flash-tts-preview` answered `400 INVALID_ARGUMENT` to each of these (verified live
33+
2026-08-20):
34+
35+
| Request carries | Message |
36+
| --- | --- |
37+
| more than one turn | `Multiturn chat is not enabled for this model` |
38+
| an audio part | `Audio input modality is not enabled for this model` |
39+
| `system_instruction` | `Developer instruction is not enabled for this model` |
40+
| `thinking_config.thinking_level` | `Thinking level is not supported for this model.` |
41+
| `thinking_config.include_thoughts` | `Thinking is not enabled for this model` |
42+
| `tools` | `Function calling is not enabled for this model` |
43+
44+
`max_output_tokens`, `response_modalities`, and `speech_config` were accepted.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Gemini TTS 请求只带一轮文本,Playground 播放单条音频
2+
3+
- **Date:** 2026-08-20
4+
- **Type:** fix
5+
- **Scope:** `gemini3_7`, `base_client`, `integration`, `tests`
6+
- **PR:** [#184](https://github.com/Prism-Shadow/agenthub/pull/184)
7+
8+
[English](2026-08-20-gemini-tts-single-turn.md)
9+
10+
## 变更内容
11+
12+
- `Gemini3_7Client` 面向 TTS 模型只发送最新一条消息,其余对话不再进入请求,于是有状态会话在第一次
13+
合成之后仍然可用,不会把模型拒收的历史音频重放回去。
14+
- `Gemini3_7Client` 构造 TTS 请求时只取语音设置与 `max_tokens`,system prompt、thinking level 与
15+
thinking summary、tools、tool choice、image config 一律不带,因此从别的模型沿用下来的配置不会再
16+
把一次合成请求变成 400。
17+
- `concat_uni_events_to_uni_message` / `concatUniEventsToUniMessage` 会合并相邻且 mime type 同为
18+
`audio/``inline_data` 条目,一次语音回复在历史与 tracer 里是一条,而不是每个流式分片一条。
19+
- Playground 先用一行 `🔊 Receiving audio...` 累计已收到的时长,把音频分片收集起来,流结束后渲染
20+
一个播放器;中途打断也会保留已经收到的音频。分片先按字节拼接再写 WAV 头,采样率与声道数取自
21+
mime type 参数(`audio/l16; rate=24000; channels=1`)。
22+
- 合成完的音频会自动播放一次,浏览器若拦截自动播放,播放器就停在那里等人点。用 Stop 打断的那次
23+
不会自动播放。token 页脚改为追加到消息卡片,而不是把整张卡片重新解析一遍,否则正在放的音频会被
24+
打断重来。
25+
26+
## TTS 请求限制
27+
28+
以下几种请求,`gemini-3.1-flash-tts-preview` 均返回 `400 INVALID_ARGUMENT`(2026-08-20 实测):
29+
30+
| 请求包含 | 报错信息 |
31+
| --- | --- |
32+
| 多于一轮的对话 | `Multiturn chat is not enabled for this model` |
33+
| 音频 part | `Audio input modality is not enabled for this model` |
34+
| `system_instruction` | `Developer instruction is not enabled for this model` |
35+
| `thinking_config.thinking_level` | `Thinking level is not supported for this model.` |
36+
| `thinking_config.include_thoughts` | `Thinking is not enabled for this model` |
37+
| `tools` | `Function calling is not enabled for this model` |
38+
39+
`max_output_tokens``response_modalities``speech_config` 可以接受。
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# DeepSeek moves onto the OpenAI Responses protocol
2+
3+
- **Date:** 2026-08-21
4+
- **Type:** refactor
5+
- **Scope:** `deepseek_v4`, `registry`, `tests`
6+
- **PR:** [#185](https://github.com/Prism-Shadow/agenthub/pull/185)
7+
8+
[中文版](2026-08-21-deepseek-responses-protocol.zh.md)
9+
10+
## What changed
11+
12+
- `DeepSeekV4Client` calls `/responses` instead of `/chat/completions`, reading the
13+
`response.reasoning_text.delta`, `response.output_text.delta`,
14+
`response.function_call_arguments.delta` and `response.completed` events, and rebuilding a
15+
replayed chain of thought as a `reasoning` item whose `content` is `reasoning_text`.
16+
- The two SiliconFlow entries, `deepseek-ai/DeepSeek-V4-Flash` and
17+
`deepseek-ai/DeepSeek-V4-Pro`, moved to the generic `openai-chat` client: SiliconFlow serves
18+
Chat Completions only. The OpenRouter and official entries keep the `deepseek-v4` client.
19+
- The DeepSeek cases in the empty-response, tool-call-argument and unknown-event unit suites
20+
moved to the Responses wire shape, and those first two suites gained a case for the generic
21+
`openai-responses` client alongside it.
22+
23+
- `README.md` gained a table of the wire protocol each `client_type` speaks: `google-genai`,
24+
`ant-messages`, `openai-responses`, `openai-chat`, and OpenAI Embeddings.
25+
26+
## Configuration behavior
27+
28+
| `UniConfig` key | On the wire |
29+
| --- | --- |
30+
| `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 |
31+
| `thinking_summary` | left out: the endpoint accepts `summary` but never generates one |
32+
| `max_tokens` | `max_output_tokens` |
33+
| `system_prompt` | `instructions` |
34+
| `temperature` | `UnsupportedParameterError` unless it is `1.0` |
35+
| `tool_choice` | `auto` and `none` only; anything else raises `UnsupportedParameterError` |
36+
| `fast_mode` | `UnsupportedParameterError`: `service_tier` is not supported |
37+
| `prompt_caching` | `ENABLE` only; caching is automatic |
38+
39+
Usage arrives as `input_tokens` with `input_tokens_details.cached_tokens`, and `output_tokens`
40+
with `output_tokens_details.reasoning_tokens`.

0 commit comments

Comments
 (0)