Atelia.Completion
0.1.0-preview.3
dotnet add package Atelia.Completion --version 0.1.0-preview.3
NuGet\Install-Package Atelia.Completion -Version 0.1.0-preview.3
<PackageReference Include="Atelia.Completion" Version="0.1.0-preview.3" />
<PackageVersion Include="Atelia.Completion" Version="0.1.0-preview.3" />
<PackageReference Include="Atelia.Completion" />
paket add Atelia.Completion --version 0.1.0-preview.3
#r "nuget: Atelia.Completion, 0.1.0-preview.3"
#:package Atelia.Completion@0.1.0-preview.3
#addin nuget:?package=Atelia.Completion&version=0.1.0-preview.3&prerelease
#tool nuget:?package=Atelia.Completion&version=0.1.0-preview.3&prerelease
Completion transport-liveness contract
Atelia.Completion 面向 net10.0,依赖 Abstractions 与 Diagnostics。本包使用 MIT 许可证。
dotnet add package Atelia.Completion --version 0.1.0-preview.3
完整离线 public client 示例与 HttpClient 所有权见 对应版本快速上手。Release 包中 Debug 级别调用已被 [Conditional("DEBUG")] 编译裁掉,无法由环境变量恢复;需要库内详细调试时使用源码 Debug 联调。
Completion只判断能够从HTTP/SSE链路和provider协议中直接观察到的事实,不猜测LLM是否仍在工作。
一次streaming调用没有elapsed-operation timeout,也没有stream-idle timeout;不可见reasoning、排队或长时间
没有SSE frame都不是失败证据。HttpClient.Timeout统一为Timeout.InfiniteTimeSpan,调用方只能通过自己传入的
CancellationToken取消。
自 0.1.0-preview.2 起的源码变更:CompletionFailureInfo(Kind, HttpStatusCode, ProviderCode, RetryAfter)
是调用异常与 provider Failed 结果共用的事实合同,不包含重试策略。HTTP 的 Retry-After 同时接受
delta 与 date;正文读取失败不会抹掉已观察到的状态码,response 仍会释放。
CompletionStreamInterruptedException 现在继承 CompletionFailureException,不再继承 IOException;
Codex 的 HTTP/transport 失败也使用共享异常,旧 Codex-specific 异常只表达 protocol compatibility。
HTTP 异常诊断正文限制为归一化单行的前 512 字符;结构化 code 从完整错误响应解析,不因诊断截断丢失。
该摘录仍可能包含 provider 数据,不应用作 UI 安全文本或持久业务原因;宿主应只发布归一化错误码。
未请求 stream usage 的 Chat 方言与 Gemini 在 finish terminal 后不等待 trailing usage;
若终止 frame 未带 usage,相应计量字段就是 unknown,完整业务结果不能因可选计量迟到而丢失。
显式请求 stream_options.include_usage 的 Chat 方言会继续读取 terminal 后的
empty-choices usage snapshot 与 [DONE];该快照是已请求的协议尾部事实,而不是可选等待。
Anthropic/Gemini 的共享 capability preflight 在最后 waiter 取消时会取消并等待 fetch 清理;
清理未完成前不启动替代 fetch。其他仍在等待同一 fetch 的调用不受单个 waiter 取消影响。
共享边界
- 成功HTTP响应必须声明
text/event-stream;HTTP、建连、打开和读取流的错误以CompletionFailureException.Failure报告结构化事实(Transport/Http、状态码、provider code、Retry-After)。parser/observer/投影异常不归类为网络错误。 - 所有Provider共享
CompletionSseEventReader:按SSE空行提交frame,支持CR/LF/CRLF、多行data:、注释、 UTF-8 BOM与replacement decoding;EOF时未提交的半个frame不会交给parser。 - 只有下表中的provider terminal evidence才能确定远端结果。显式terminal到达后立即返回,不等待连接EOF。
- 已收到合法frame但在terminal evidence前EOF,抛出
CompletionStreamInterruptedException。这表示远端结果不确定, 库不透明重试,也不把它伪装为LLM拒答;宿主按调用业务语义决定是否重算,可能重复计费。唯一的窄兼容例外是Anthropic:所有content block均已关闭、 已收到非空message_delta.stop_reason、随后无pending frame的clean EOF但缺少data-freemessage_stop时,按该stop reason结束。 - caller cancellation保持原
CancellationToken;observer cleanup失败不得覆盖原始read/cancellation异常。 - 未被当前版本识别、但外层event envelope合法的字段或事件保持forward-compatible;已知事件缺少必需shape、 生命周期乱序或event/data类型冲突则fail closed。
- 如果TCP/HTTP连接进入silent half-open且系统没有报告断开,调用会无限等待。这是刻意选择:没有独立可靠的 transport证据时,不用定时器推断LLM失败。
Provider terminal matrix
| Provider surface | 权威成功/不完整terminal | 权威provider失败 | 非terminal或特殊规则 |
|---|---|---|---|
| OpenAI Chat Completions | 单一choice的非空finish_reason;stop/tool_calls为Completed,其他值为Incomplete |
顶层error |
[DONE]只是传输哨兵;若它先于finish_reason到达,结果仍不确定。当前明确拒绝n > 1与多choice stream |
| OpenAI Responses | response.completed、response.incomplete;若已观察到typed refusal,两者均收口为Incomplete(response.refusal) |
response.failed、error |
response.refusal.delta/done与message refusal content不是terminal;event:与JSON type必须一致;[DONE]不能替代Responses terminal event |
| Anthropic Messages | 首选message_stop,并要求message_start -> content blocks -> message_delta(stop_reason);兼容缺尾帧relay时,blocks全关且已有非空stop_reason后的无pending-frame clean EOF也是降级terminal evidence |
error |
ping与合法unknown named event只表示收到frame,不代表成功或失败;Anthropic没有[DONE];read failure/cancellation/protocol error绝不走clean-EOF兼容 |
Gemini streamGenerateContent |
candidate.finishReason;STOP为Completed,其他值为Incomplete;无candidate时promptFeedback.blockReason为Incomplete |
顶层error envelope |
Gemini没有文档化的[DONE]、named terminal或heartbeat;responseId只用于关联,不是resume cursor |
OpenAI Responses refusal 只按typed wire evidence识别,不从普通正文猜测。response.refusal.delta/done、
response.output_item.done 的 message refusal content,以及terminal response.output fallback 使用
(item_id, content_index)协调;streamed prefix只补final suffix,重复final不重复输出,冲突或final后delta抛protocol
exception且不在异常中带refusal正文。同一时刻只允许一个未finalized key;message/output已知容器若缺失array shape、
entry不是object或缺少string type也fail closed,而合法unknown string type仍forward-compatible。正文可以作为
transient ActionMessage.Text / observer delta返回,但不会进入
Errors、termination reason/detail,也不会被SessionJournal持久化为成功AgentActionProduced。只有最终权威response
terminal到达后才产生non-success result;terminal前EOF、transport failure或cancellation仍是outcome uncertain,
response.failed / error仍覆盖为Failed。没有typed refusal witness时,既有response.incomplete的
content_filter等reason保持原语义;refusal始终使用独立response.refusal,不复用content_filter名称。
规格依据
- SSE framing:WHATWG Server-sent events
- OpenAI Responses events:OpenAI API reference
- Anthropic event types and lifecycle:Anthropic streaming Messages
- Gemini response and finish reasons:Gemini
generateContentAPI
ChatGPT Codex subscription client(Linux / Windows)
OpenAICodexResponsesClient 可以直接作为 ICompletionClient 使用,不需要启动 local proxy。它只借用 Codex CLI
file-backed auth.json 的当前 access-token snapshot;Codex CLI 仍是唯一 login/refresh/write-back owner。
using Atelia.Completion.Abstractions;
using Atelia.Completion.OpenAI;
var credentials = new CodexCliAuthFileCredentialProvider();
CodexSubscriptionCredential firstSnapshot =
await credentials.GetCredentialAsync(ct);
using var client = new OpenAICodexResponsesClient(
credentials,
new OpenAICodexResponsesClientOptions {
ExpectedAccountFingerprint = firstSnapshot.AccountFingerprint,
Originator = "atelia", // 构造期可配;默认值也是 atelia
MaxConcurrentRequests = 3
}
);
CompletionResult result = await client.StreamCompletionAsync(
request,
observer: null,
ct
);
if (result.Termination.Kind == CompletionTerminationKind.Completed) {
string text = result.Message.GetFlattenedText();
// 将成功完成的正文交给业务层。
}
// Incomplete / Failed 应由调用方单独处理,不能把部分正文当作成功结果。
运行边界:
- 支持 Linux / Windows file-backed credential。默认读取
CODEX_HOME下的auth.json;未设置时使用Environment.SpecialFolder.UserProfile下的.codex/auth.json。例如 Windows 用户gdtut的默认路径是C:\Users\gdtut\.codex\auth.json,不需要 WSL 或启动 Codex app-server; CODEX_HOME与显式 auth-file override 都必须是绝对路径,并指向 Codex 的普通凭据文件。 两个平台使用同一.NET File.OpenHandle只读实现,正常跟随 symlink/junction,由 OS 解析路径和判断读取权限;- provider 不检查或修改 owner/mode/ACL,不承担 no-follow 路径隔离职责。句柄允许 read/write/delete sharing, 同一句柄进行有界双读和长度检查,兼容 Codex 原地重写和原子替换;
- provider 每个 logical attempt 重读 snapshot,但从不 materialize refresh/id token,不 refresh、不写文件;
- access token 过期或 backend 401 且文件 generation 未变化时,先运行 Codex 让它 refresh,必要时重新
codex login; - endpoint 固定为
https://chatgpt.com/backend-api/codex/responses,它不是公开稳定 API; originator必须诚实稳定,允许构造时覆盖,不要伪装codex_cli_rs、Pi 或 OpenCode;- public OpenAI Responses 与 Codex Responses 使用不同
ApiSpecId,两边的 provider-native reasoning payload 不能交叉 replay。
文件存储模式见 OpenAI 官方 credential storage 文档。
仅存于 OS keyring 的凭据不在此 provider 的支持范围;没有 auth.json 时返回 AuthStorageUnavailable。
可选的 JsonLinesCompletionHttpExchangeFileSink 仍只支持 Linux;Windows 默认调用不依赖 raw exchange 文件日志。
接入 Player 等调用方时,期限由传入的 CancellationToken 控制;CompletionStreamInterruptedException 表示
结果不确定,库不自动重试,宿主可以按纯生成等业务语义重新调用。HTTP 401/403/429 同样通过共享失败事实报告;401 后 credential generation 确实变化时仍只允许一次立即认证协商,不对 429/5xx/断流增加内部重试。CompletionUsage 中的 null 表示该维度未知,0 才是明确报告的零;不要为了适配
profiler 而补零或推算缺失维度。以上语义在 Linux / Windows 相同。
Galatea 接入、connection shape、安全 preflight、环境变量和 live smoke 见
docs/Completion/openai-codex-subscription-client-design.md。
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Atelia.Completion.Abstractions (>= 0.1.0-preview.3)
- Atelia.Diagnostics (>= 0.1.0-preview.3)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.1.0-preview.3 | 48 | 9/23/2026 |
| 0.1.0-preview.2 | 47 | 9/20/2026 |
| 0.1.0-preview.1 | 63 | 9/14/2026 |