Skip to content

refactor(bindings,ffi)!: 稳定跨语言错误模型 - #158

Open
cunninghamcard-bit wants to merge 7 commits into
arcships:masterfrom
cunninghamcard-bit:feat/error-model-bindings-ffi
Open

refactor(bindings,ffi)!: 稳定跨语言错误模型#158
cunninghamcard-bit wants to merge 7 commits into
arcships:masterfrom
cunninghamcard-bit:feat/error-model-bindings-ffi

Conversation

@cunninghamcard-bit

@cunninghamcard-bit cunninghamcard-bit commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Refs #95

本次改动

本 PR 确定跨语言错误处理的稳定形态:

  • Node.js、Python 抛出真实原生错误,支持 instanceofisinstance
  • C ABI 统一返回不透明 aimux_error_t *NULL 表示成功,失败对象由调用方释放一次。
  • 核心、录制、绑定边界分别使用 1..13100..105200..206
  • RecordingError 保持独立;绑定错误不冒充核心错误。
  • 删除临时兼容层:错误 JSON、通用空字段、公开 C 错误结构体和混合返回约定。

兼容性

已有名称、错误码、所有权和调用约定保持稳定;以后通过新增错误类型、可选字段或 getter 扩展。破坏这些约定需要主版本变更。

完整说明见 docs/error-model.md

参考

参考了 AI SDK、libsignal FFI、Node-API/napi-rs、PyO3 和 openai-go;只借鉴错误处理范式,不照抄其结构。

@cunninghamcard-bit
cunninghamcard-bit marked this pull request as draft August 19, 2026 03:49
@cunninghamcard-bit
cunninghamcard-bit force-pushed the feat/error-model-bindings-ffi branch 6 times, most recently from d6ada6c to 1ba57b8 Compare August 19, 2026 08:23
Node and Python now project each core error variant into a real runtime exception class instead of rebuilding a generic metadata object.

Node registers the canonical JavaScript constructors with napi-rs and Rust instantiates them in the owning environment. Synchronous throws, promise rejections, streams and workers therefore preserve instanceof. Python raises the matching PyO3 exception class.

Variant payload lives only on the class that owns it: APICallError carries provider-call details, TokenExpiredError carries its defined 401 status, and the registry errors carry their identifiers. AimuxError has no compatibility code, status or retryable fields, and the serialized errorValue/error_value companion is removed.

RecordingError remains a second family, unrelated to AimuxError, for recorder construction and flush failures. Binding-layer failures keep the native framework behavior: napi-rs Error statuses in Node and ValueError or RuntimeError in Python.
Every fallible C function now returns aimux_error_t *: NULL on success with the result in a trailing out-parameter, or one owned error on failure, released exactly once with aimux_error_free(). The caller-allocated AimuxError struct, aimux_error_clear, error_value and mixed sentinel polarities are removed.

One code space identifies the source. AiMuxError uses 1–13: Other takes the retired UNKNOWN slot at 1, codes 2–13 retain their existing values, and the old Other value 14 is retired. RecordingError uses 100–105 and C-boundary failures use 200–206. A non-NULL error never reports code 0.

aimux_error_message() works for every code. Detail getters read only the payload owned by that variant and return NULL, -1 or 0 otherwise. The error is a Box transferred directly to the caller, not a registry handle or a JSON projection.

Router and MoA constructors reject every dead member handle instead of silently changing the composite. The C and C++ examples use the same ownership and result convention.
The Go binding reads the new ABI: every call gets an `aimux_error_t *`,
NULL means success, the result arrives through an out-parameter, and the
error is freed once. `error.go` rebuilds the separate types from the code
range — the `AimuxError` hierarchy for 1–13, a distinct `RecordingError`
for 100–105, and a plain Go error for the C boundary's own 200–206, which
callers should never have to branch on.

The arcships#157 lifecycle model is unchanged: atomic handle owners, Close that
never waits for an in-flight C call. Only the ABI call sites and the error
decoder move.
Both JVM bindings follow the new ABI: an opaque error pointer per call,
freed once, with the payload read through the getters. `AimuxCError` is
gone — the code range now decides the type: `AimuxException` and its
subclasses for 1–13, a separate `RecordingException` /
`RecordingErrorCode` for 100–105, and the language's own
`IllegalStateException` / `IllegalArgumentException` for the C boundary's
200–206.
Swift and Flutter follow the new ABI the same way: one opaque error
pointer per call, freed once, payload through the getters, and the code
range rebuilding the separate types — the engine hierarchy for 1–13, a
distinct recording error for 100–105, and the language's own invariant
error for the C boundary's 200–206.
Rewrites the error sections for C, Go, Java, Kotlin, Swift, Flutter, Node
and Python: the return convention, the code ranges and which type each
range becomes in that language.
@cunninghamcard-bit cunninghamcard-bit changed the title refactor(bindings,ffi)!: one error model — typed Node/Python errors, opaque C error pointer with a unified code space (RFC-0030) refactor(bindings,ffi)!: 稳定跨语言错误模型 Aug 19, 2026
@cunninghamcard-bit
cunninghamcard-bit force-pushed the feat/error-model-bindings-ffi branch from 1ba57b8 to 8b4e885 Compare August 19, 2026 17:06
@cunninghamcard-bit
cunninghamcard-bit marked this pull request as ready for review August 19, 2026 17:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant