AAEE86
1d3051cb89
refactor(ws): structured terminal observation without SSE text round-trips
...
评审第 5 条:Responses WebSocket 收到的本来就是结构化协议事件,但为了复用面向
SSE 的 push_line,观测路径要先把每个事件序列化成 data: {json}\n\n,解析器再
decode 回 Value——一次纯粹的往返。这个「伪 SSE」形状是随手拼的,一旦拼装函数
以后被加上换行或分块逻辑,观测结果就会和真实事件悄悄分叉。
aether-ai-formats:
- OpenAIResponsesProviderState::push_line 机械拆成 decode + push_event,
push_line 现在只做解码。协议状态机一行未动,diff 里除函数签名外只有
&value → value(value 从拥有改成借用,持有结构化事件的传输不必为了调用它
先克隆一份)。
- StreamingStandardTerminalObserver::push_event 走 TerminalStreamParser::Standard,
service tier 的记录方式与 push_line 完全相同。openai:image 的终态状态机按 SSE
行做增量解析、没有结构化入口,返回 AiSurfaceFinalizeError 让调用方
disable_with_error 标记 parser_error,而不是静默丢事件、把摘要留成「未观察到
终态」。ProviderStreamParser 的其余三个格式同样返回 Err:机械拆分随时可做,
但不建无调用方的接口。
WS 侧:
- 新增 responses/observation.rs 的 ResponsesStructuredTerminalObserver,直接消费
frame.protocol_events() 借出的事件。包一层的意义是让「不再拼 SSE」成为类型层面
的事实——这个类型没有任何接受字节的方法,改回 push_line 不可能悄悄发生。
finish() 里的 Ok(None) / Err → disable_with_error 兜底也一并收进来。
- body capture 不动,仍然是 SSE 形状(data: 开头、\n\n 结尾):
aether_usage_runtime::report 用 line.strip_prefix("data:") 解析被捕获的 body
判定 StreamCapturedTerminalState,而它是 stream_report_represents_failure 的一个
OR 项,换成结构化 JSON 会让终态判定恒为 Missing。capture_sse_event /
capture_client_frame / websocket_event_as_sse_line 全部保留,原因写在模块文档
注释里。这一层只换观测,不换捕获。
差分测试(8 个,aether-ai-formats):同一组事件序列分别走 push_line 与
push_event,断言 ExecutionStreamTerminalSummary 完全相等——批量 delta 序列、
completed 带 usage、合法 incomplete、error、response.failed、未知事件、
service tier、缺终态;外加 openai:image 拒绝结构化入口。两条入口不可能有
过滤差异:任何 Value 序列化出来都不会命中 decode_json_data_line 的 empty /
":" / "event:" / [DONE] 四个过滤条件。
turn.rs 里三个既有的 WS 观测测试改走结构化入口;SSE 形状的断言留在 capture 一侧。
验收:crates/aether-usage 零 diff。
2026-08-17 14:53:33 +08:00
AAEE86
247e7105a2
fix(ws): bill a provider-reached terminal even when client delivery fails
...
评审第 5 条后半:provider 终态已经到达、只是 gateway 写客户端 socket 失败时,
relay loop 用 client_disconnected() 覆盖了结算信号,于是一条供应商已经完成推理
并消耗了 token 的响应被记成 void billing、candidate 记 Cancelled、不投射供应商
效果、也不提交 execution report。上游成本凭空消失。
结算表只改一行:作废账单的条件从
provider.cancelled_by_provider() || delivery.is_aborted()
收紧为
provider.cancelled_by_provider() || (delivery.is_aborted() && !provider.is_terminal())
于是 Terminal{cancelled=false} + delivery Aborted 与 delivery Complete 落在同一侧:
Billed、candidate Success 或 Failed、投射供应商效果、提交 execution report。
状态码随之变成纯 provider 事实(不再把 200 改写成 499);作废分支的 provider
状态码本身就是 499,取值不变。
依据:供应商已经完成推理并消耗 token,客户端还能用 previous_response_id 续取
这条响应。供应商没给出终态时(客户端先走了)仍然作废,这一侧未改。
配套改动:
- connection.rs 写客户端失败处改为 record_client_delivery_aborted(reason) +
settle_signal_for_client_delivery_failure(terminal_outcome):provider 终态已到达
就用那条终态作结算信号,不再无条件覆盖。投递失败原因也不再谎称
「客户端在终态前断开」。
- 投递结果记在 attempt 上而非 logical turn 上:结算按 attempt 进行,且配额透明
重试时各 attempt 的投递结果彼此独立。
- report_context 新增 websocket_client_delivery="aborted" 与
websocket_client_delivery_reason,只增字段不改既有字段,便于事后区分
「客户端拿到了」和「客户端没拿到但已计费」。
- candidate error_type 新增 client_delivery_failed(原先这个场景写的是
websocket_cancelled)。它排在供应商侧分类之前:这条记录之所以特别正是因为
内容没送到客户端,供应商侧判定仍由 candidate_status 与 error_message 保留。
- finish_summary 改用作废判定而非「投递失败」判定:provider 终态已到达时摘要
必须保留真实的 finish_reason 与 usage,否则计费记录会被写坏。
e2e 期望值变化:client_disconnect_mid_turn_still_settles_the_usage_row 改名为
client_disconnect_before_any_provider_output_settles_a_void_row,并补上
「不计费 + status=cancelled + status_code=499」的断言。原用例的 mock 行为是
StallAfterCreated(只发 response.created 就静默),provider 从未给出终态,所以
它走的是未改动的作废一侧;原来的文档注释说「must still be billed」与实际语义
不符,一并纠正。真正被修正的那一行无法在 e2e 里确定性触发——它取决于 relay
loop 的 select! 先观察到上游终态帧还是先观察到已关闭的客户端 socket,是构造性
竞态——因此由 relay 级单测确定性覆盖,e2e 里以注释指向这两个单测。
新增 7 个测试:结算表修正行(并与「投递成功」逐字段对照,只有 candidate 错误
分类不同)、无终态时仍作废、供应商声明取消即使送达也不计费、结算信号选择、
已记录的投递失败不被结算信号覆盖、relay 级「终态到达 + 客户端已关闭 ⇒ Billed /
Success / ProviderSuccess / 已提交 report 且 usage 完整保留」及其镜像、
report_context 只增不改。
2026-08-17 14:53:12 +08:00
AAEE86
621a528083
test(ws): Responses WebSocket 端到端套件接入 CI
...
补齐 aether-integration-tests 的 responses_websocket_e2e 集成测试,
并把 CI 的 scenario 任务从 --bins 改为 --bins --tests,否则该套件
不会被执行。
2026-08-17 14:51:24 +08:00
AAEE86
71b54070e8
feat(gateway): Codex/OpenAI Responses WebSocket 代理模式
...
在 /v1/responses 上支持 WebSocket 升级,把客户端帧中继到上游 Codex /
OpenAI Responses WebSocket 端点,同时保持既有的路由、鉴权、配额与用量
语义:
- 路由与准入:control/route/ai.rs 识别 WebSocket 升级请求;
websocket/ingress.rs 复用 API Key 鉴权、IP 规则与并发许可,并引入
独立的 WebSocket 连接许可
- 中继:websocket/responses/* 按 connection / session / turn 分层,
帧解析归一化、socket 写入有界、continuation 保持调度亲和性
- 配额:orchestration/codex_quota_breaker.rs 在账号配额耗尽时熔断并
自动恢复,不再直接断开客户端连接
- 用量:每个 turn 的终态用量落库,request_metadata 记录
websocket_mode / websocket_transport,管理端与 usage 视图暴露
is_websocket
- 管理端:provider 可配置 Responses WebSocket 开关
2026-08-17 14:50:33 +08:00
ZheFox
32944538e9
fix(gateway): stop candidate persistence retry storms
2026-08-17 13:49:12 +08:00
ZheFox
b13d9b9b40
fix(codex): restore upstream model discovery
2026-08-15 19:36:28 +08:00
zhefox
810c3dfe2b
fix(codex): serve versioned dynamic model catalogs
2026-08-14 18:41:44 +08:00
elky
a1d64e5239
fix(routing): preserve allowlist edits and save state
2026-08-14 11:43:24 +08:00
elky
fb33ea57b0
Merge PR #715 : decouple routing model overrides
2026-08-14 11:10:21 +08:00
elky
5b0c763086
fix(codex): fence concurrent quota updates
2026-08-14 09:28:07 +08:00
elky
f3a12c1008
fix(ai): preserve Codex image edit validation
2026-08-13 11:31:17 +08:00
elky
ca35e09eaa
Merge pull request #718 from zjm54321/fix/custom-image-edit-json
2026-08-13 11:00:15 +08:00
elky
8cf381b0c3
feat(codex): add OAuth fingerprint convergence
2026-08-13 09:57:17 +08:00
zjm54321
41e93858e1
refactor(ai): simplify image edit serialization
2026-08-11 00:36:33 +08:00
zjm54321
8d918d0459
fix(ai): serialize image edits with images array
2026-08-11 00:30:00 +08:00
zhefox
908d4f2603
style(provider): apply rustfmt to claude_code tests
2026-08-05 13:58:46 +08:00
zhefox
4d67569873
fix(gateway): support claude_code cross-format Claude messages
2026-08-05 13:57:20 +08:00
zhefox
aedff9a704
fix(provider): validate mapped model reasoning effort
2026-08-03 19:22:51 +08:00
zhefox
669f4bddc5
fix: align Responses routing and model permissions
2026-08-03 18:48:01 +08:00
zbs
1a4eede34d
fix(routing): decouple model overrides from allowed scope
2026-08-03 07:52:49 +08:00
elky
06f5d3c8c0
fix(gateway): complete worker registration cleanup
2026-07-31 11:32:07 +08:00
fawney19
6688ee26db
Merge pull request #702 from MMEXA/fix/reconcile-auth-channel-mismatch-formats
...
fix(gateway): 修复批量更新 API 格式时的认证通道状态冲突
2026-07-31 10:28:37 +08:00
elky
beb003b7ad
feat(models): add external catalog proxy selection
2026-07-31 09:32:25 +08:00
MMEXA
6ecfe0f0a1
fix(gateway): reconcile auth mismatch formats on key update
2026-07-30 22:14:08 +08:00
ZheFox
ef5f36cc2b
fix(ai): satisfy response history clippy checks
2026-07-30 20:13:36 +08:00
ZheFox
84022c4d48
Merge upstream/main into main
2026-07-30 19:40:39 +08:00
ZheFox
118f441029
feat(gateway): persist OpenAI Responses continuation history
2026-07-30 19:26:52 +08:00
elky
20399b004d
Merge PR #700 : fix admin pool batch update body buffering
...
Preserve main's failover and usage metadata fixes, restore default tunnel regression coverage, and satisfy current Clippy.
2026-07-30 17:56:37 +08:00
elky
050eb77508
fix(ai): harden responses replay and failure diagnostics
2026-07-30 17:19:54 +08:00
elky
1ab4f079c9
fix(gateway): restore failover and usage diagnostics
2026-07-30 09:12:11 +08:00
MMEXA
6c733f7590
fix(usage): preserve request diagnostics in event seeds
2026-07-30 06:44:59 +08:00
MMEXA
8cf9af79da
fix(ci): remove redundant usage policy update
2026-07-30 05:45:38 +08:00
elky
a04673a90d
feat(gateway): harden failover and payload handling
...
Retry pre-response transport failures across candidates with an explicit stop policy, and propagate end-to-end timing into usage records and UI diagnostics.
Remove legacy body, import, cookie, PII, and tunnel replay caps while preserving optional operator-configured gateway limits.
2026-07-30 01:03:27 +08:00
zhefox
f8000012f7
fix(ci): stabilize cross-platform workflow checks
2026-07-29 21:55:43 +08:00
ZheFox
2db32ea97e
Merge branch 'main' of https://github.com/zhefox/Aether
2026-07-28 17:40:46 +08:00
ZheFox
581897ee74
fix(formats): ignore responses client metadata across targets
2026-07-28 17:40:41 +08:00
ZheFox
9d9316e434
Merge branch 'fawney19:main' into main
2026-07-28 13:56:34 +08:00
ZheFox
1b697b1111
feat(providers): support FedRAMP Codex agent identity registration
2026-07-28 13:29:30 +08:00
ZheFox
0bf92ffffc
feat(providers): advertise responses API agent capability
2026-07-28 12:02:03 +08:00
ZheFox
f0f87b56a3
feat(providers): add credential-fenced OAuth key cleanup
2026-07-28 11:32:11 +08:00
elky
4148ab1931
fix(routing): harden routed pool scheduling
2026-07-27 22:06:28 +08:00
elky
550cc36760
feat(providers): expand OAuth account management
...
Add Claude Code manual and cookie authorization, including redacted batch tasks. Harden OAuth imports, duplicate replacement, provider dialogs, and related account-management tests.
2026-07-27 15:53:28 +08:00
elky
531cf11025
feat(gateway): harden provider request execution
...
Preserve exact request payloads and model client surface and API operation explicitly.
Add Anthropic compatibility profiles, bounded stream commitment, and scoped OAuth retry behavior across provider transports.
2026-07-27 09:36:31 +08:00
elky
10d369f59c
feat(providers): add provider transfer limits
2026-07-26 15:06:56 +08:00
elky
778cfb1a5c
feat(data): complete portable SQL backend parity
...
Align MySQL and SQLite schemas, migrations, usage, stats, export, and backfill behavior with the shared data contracts. Extend gateway startup and maintenance support across all SQL drivers.
2026-07-25 21:28:21 +08:00
elky
387134ca87
fix(models): correct fast pricing and online sync
2026-07-24 01:45:38 +08:00
ZheFox
e186cc6848
Merge branch 'main' of https://github.com/zhefox/Aether
2026-07-23 12:17:46 +08:00
ZheFox
615ac99ad7
fix(formats): ignore Responses client transport metadata
2026-07-23 12:16:44 +08:00
ZheFox
ec36cfbf75
Merge pull request #690 from zhefox/main
...
fix(provider): classify deleted Codex agent runtime as invalid
2026-07-23 11:22:30 +08:00
ZheFox
7bf228a33c
fix(provider): classify deleted Codex agent runtime as invalid
2026-07-23 11:21:55 +08:00