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
28 changes: 28 additions & 0 deletions apps/daemon/internal/agent/codex/error_classification.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ func classifyTurnError(failure *TurnError) proto.ErrorPayload {
code = "invalid_request"
case "cyberPolicy":
code = "cyber_policy"
case "other":
code = providerRejectionCode(failure.Message)
}
return proto.ErrorPayload{Code: code}
}
Expand All @@ -58,3 +60,29 @@ func classifyTurnError(failure *TurnError) proto.ErrorPayload {
}
return proto.ErrorPayload{}
}

// Codex 0.153.4 reports a provider HTTP 400 as "other" and forwards the
// response body verbatim; its own "other" failures are prose. A JSON object with
// an object "error" member is the provider's rejection: a declared code in its
// own error.code or error.type wins, otherwise it is invalid_request. Prose, such
// as a plain-text 400 body, stays unclassified.
func providerRejectionCode(message string) string {
var body struct {
Error *struct {
Code json.RawMessage `json:"code"`
Type json.RawMessage `json:"type"`
} `json:"error"`
}
if json.Unmarshal([]byte(message), &body) != nil || body.Error == nil {
return ""
}
for _, raw := range []json.RawMessage{body.Error.Code, body.Error.Type} {
var name string
if json.Unmarshal(raw, &name) == nil {
if code, _ := proto.NormalizeEngineFailure(name, nil); code != "" {
return code
}
}
}
return "invalid_request"
}
35 changes: 35 additions & 0 deletions apps/daemon/internal/agent/codex/error_classification_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,38 @@ func TestClassificationOnlyFromRootTerminalError(t *testing.T) {
})
}
}

// Codex 0.153.4 reports a provider HTTP 400 as "other" with the verbatim body and
// repeats it on the error notification stream.
func TestProviderRejectionClassifiedOnce(t *testing.T) {
body := `{"error":{"message":"invalid params, unknown model","code":"invalid_prompt"}}`
for message, want := range map[string]string{
body: "invalid_request",
`{"error":{"message":"too long","code":"context_length_exceeded"}}`: "context_length_exceeded",
`{"error":{"message":"bad key","type":"authentication_error"}}`: "authentication_error",
"plain text bad request": "", "unexpected status 404 Not Found: The model bogus does not exist": "", `{"error":"bad"}`: "",
} {
if got := classifyTurnError(&TurnError{Message: message, CodexErrorInfo: json.RawMessage(`"other"`)}); got.Code != want {
t.Fatal(message, got)
}
}
out := make(chan proto.Envelope, 16)
s := &Session{runID: "run", out: out, cancelCtx: context.Background(), cfg: defaultSessionConfig(testStateRoot(t)), rpc: NewJSONRPCClient(JSONRPCConfig{})}
s.registerHandlers()
s.setThreadID("root")
turnError, _ := json.Marshal(TurnError{Message: body, CodexErrorInfo: json.RawMessage(`"other"`)})
scopeNotification(t, s, "turn/started", `{"threadId":"root","turn":{"id":"turn"}}`)
scopeNotification(t, s, "error", `{"threadId":"root","turnId":"turn","error":`+string(turnError)+`,"willRetry":false}`)
scopeNotification(t, s, "turn/completed", `{"threadId":"root","turn":{"id":"turn","status":"failed","error":`+string(turnError)+`}}`)
var failures []proto.ErrorPayload
for _, env := range collectObserved(t, out) {
if env.Type == proto.TypeError {
var p proto.ErrorPayload
_ = env.DecodePayload(&p)
failures = append(failures, p)
}
}
if len(failures) != 1 || failures[0].Code != "invalid_request" || failures[0].Error != "invalid_prompt: invalid params, unknown model" {
t.Fatalf("%+v", failures)
}
}
11 changes: 6 additions & 5 deletions apps/daemon/internal/agent/codex/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,12 +210,13 @@ func (s *Session) onTurnCompleted(raw json.RawMessage) {
// operators can't see why a key was rejected.
// 3. buffered text from the "error" notification stream
// (sandbox warnings, late stream packets) — last because
// it's noisier than turn.error.
// it's noisier than turn.error. Codex also reports the
// terminal error on that stream, so identical text is
// kept once.
body := finalText
if turnErrMsg := turnErrorMessage(p.Turn.Error); turnErrMsg != "" {
body = appendOnNewline(body, turnErrMsg)
}
if errText != "" {
turnErrMsg := turnErrorMessage(p.Turn.Error)
body = appendOnNewline(body, turnErrMsg)
if errText != turnErrMsg {
body = appendOnNewline(body, errText)
}
s.emitTerminalFailure(body, true, classifyTurnError(p.Turn.Error))
Expand Down
2 changes: 1 addition & 1 deletion docs/runtime-protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ An adapter may add `code` and `http_status` to a Run's `error` frame. They are o

The accepted codes are `authentication_error`, `rate_limit_exceeded`, `usage_limit_exceeded`, `server_overloaded`, `server_error`, `invalid_request`, `resource_not_found`, `request_timeout`, `context_length_exceeded`, `cyber_policy` and `connection_failed` ([`engine_failure.go`](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/internal/agentdaemon/proto/engine_failure.go)). Only `connection_failed` keeps `http_status`, and only an integer from 100 to 599; every other status is discarded. A missing, malformed or unknown value leaves the error unclassified without discarding Usage or `done`.

Core stores accepted values in the Turn outcome as `engine_error_code` and `engine_http_status`. The classification is subordinate to the terminal status and Core's `error_code`: it cannot turn a completed or cancelled Turn into a failure, hide an incomplete event stream, or override a persistence or cancellation-receipt failure. Normal delivery and terminal journal draining use the same extraction. [Session diagnostics](../contracts/agents-api/session-diagnostics.md) expose the category only for a failed Turn whose Core error is `engine_failed`. The [Codex](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/services/core/deploy/codex/README.md) and [Claude Code](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/services/core/deploy/claude/README.md) adapter guides give each Harness's mapping; an adapter never classifies error prose.
Core stores accepted values in the Turn outcome as `engine_error_code` and `engine_http_status`. The classification is subordinate to the terminal status and Core's `error_code`: it cannot turn a completed or cancelled Turn into a failure, hide an incomplete event stream, or override a persistence or cancellation-receipt failure. Normal delivery and terminal journal draining use the same extraction. [Session diagnostics](../contracts/agents-api/session-diagnostics.md) expose the category only for a failed Turn whose Core error is `engine_failed`. The [Codex](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/services/core/deploy/codex/README.md) and [Claude Code](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/services/core/deploy/claude/README.md) adapter guides give each Harness's mapping. An adapter classifies only from structured error data: the Harness's own error variant or status, or a model provider's JSON error body that the Harness passes through, whose own `error.code` or `error.type` may name an accepted code. It never classifies from error prose.

## Workspace operations

Expand Down
4 changes: 2 additions & 2 deletions docs/zh/runtime-protocol.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "Core–Runtime 协议"
source: docs/runtime-protocol.md
source_hash: 8349aca995e66669b7941b6aad5f28e6ae3259516d923a0494938a61fb3e711e
source_hash: 47121442f49eb3b7d6590fa0769d1531fc49fc397169cdc219f9bf9eb05f80f2
---

此协议在 Runtime daemon 获取机器凭据后连接 Core 与 daemon,定义 daemon 连接上消息的含义和顺序。wire 类型、限制和验证器仅在 [`internal/agentdaemon/proto`](https://github.com/MiniMax-AI/OpenAgentCore/tree/main/internal/agentdaemon/proto) 中定义一次;Core 的 [gateway](https://github.com/MiniMax-AI/OpenAgentCore/tree/main/services/core/internal/runtimegateway) 与参考 Runtime 的 [dispatcher](https://github.com/MiniMax-AI/OpenAgentCore/tree/main/apps/daemon/internal/dispatch) 都使用它们,因此无需同步第二套 payload schema。签发凭据和打开连接的 HTTP 路由见[机器连接 API](../../contracts/agents-api/zh/machine-api.md)。
Expand Down Expand Up @@ -185,7 +185,7 @@ adapter 可以给 Run 的 `error` frame 添加 `code` 和 `http_status`。它们

接受的 code 为 `authentication_error`、`rate_limit_exceeded`、`usage_limit_exceeded`、`server_overloaded`、`server_error`、`invalid_request`、`resource_not_found`、`request_timeout`、`context_length_exceeded`、`cyber_policy` 和 `connection_failed`([`engine_failure.go`](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/internal/agentdaemon/proto/engine_failure.go))。仅 `connection_failed` 保留 `http_status`,且只能是 100 到 599 的整数;其他 status 都丢弃。值缺失、格式错误或未知时保持未分类错误,不丢弃 Usage 或 `done`。

Core 在 Turn outcome 中将接受的值保存为 `engine_error_code` 和 `engine_http_status`。分类从属于终结状态和 Core 的 `error_code`:不能将已完成或已取消 Turn 变成失败、隐藏不完整事件流,或覆盖持久化或取消回执失败。正常 delivery 和 terminal journal draining 使用同一提取逻辑。[Session 诊断](../../contracts/agents-api/zh/session-diagnostics.md)仅为 Core error 为 `engine_failed` 的失败 Turn 暴露类别。[Codex](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/services/core/deploy/codex/README.md) 和 [Claude Code](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/services/core/deploy/claude/README.md) adapter 指南给出各 Harness 的映射;adapter 不依据错误文字分类。
Core 在 Turn outcome 中将接受的值保存为 `engine_error_code` 和 `engine_http_status`。分类从属于终结状态和 Core 的 `error_code`:不能将已完成或已取消 Turn 变成失败、隐藏不完整事件流,或覆盖持久化或取消回执失败。正常 delivery 和 terminal journal draining 使用同一提取逻辑。[Session 诊断](../../contracts/agents-api/zh/session-diagnostics.md)仅为 Core error 为 `engine_failed` 的失败 Turn 暴露类别。[Codex](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/services/core/deploy/codex/README.md) 和 [Claude Code](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/services/core/deploy/claude/README.md) adapter 指南给出各 Harness 的映射。adapter 只依据结构化错误数据分类:Harness 自身的错误类型或状态,或 Harness 原样转交的模型服务 JSON 错误 body,其 `error.code` 或 `error.type` 可以给出接受的 code。adapter 不依据错误文字分类。

## 工作区操作 {#workspace-operations}

Expand Down
3 changes: 2 additions & 1 deletion services/core/deploy/codex/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,11 @@ The adapter classifies a failed Turn only from the exact root terminal Turn's `c
| `internalServerError` | `server_error` |
| `badRequest` | `invalid_request` |
| `cyberPolicy` | `cyber_policy` |
| `other` whose message is a JSON object with an object `error` member | that member's `code` or `type` when it is an accepted code, otherwise `invalid_request` |
| `httpConnectionFailed`, `responseStreamConnectionFailed`, `responseStreamDisconnected` | `connection_failed`, with the upstream `httpStatusCode` |
| `responseTooManyFailedAttempts` | `rate_limit_exceeded` when `httpStatusCode` is 429, otherwise `connection_failed` |

Every other variant stays unclassified.
Codex `0.153.4` reports a provider HTTP 400 as `other` and puts the provider's error body in the message, such as the rejection of an unknown model name. Every other variant stays unclassified, and so does `other` with any other message, including a plain-text 400 body and the `unexpected status` text Codex writes for provider 401 and 404 responses.

## Runtime image

Expand Down
2 changes: 1 addition & 1 deletion services/core/deploy/mcode/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ In the workspace profile, the frozen installation's Skills are linked into the S
- Continuation requires the exact recorded native Session ID and the same working directory. Recovery without a recorded ID is rejected.
- MiniMax Code reports no usage: ACP context occupancy and cumulative cost are not per-Turn usage.
- Workspace `workspace_bash` calls become command observations with the command text, output text and status; exit code and duration stay unknown. Native task and Skill utilities produce no public Items.
- The adapter attaches no [native failure classification](../../../../docs/runtime-protocol.md#native-failure-classification) to its errors.
- The adapter attaches no [native failure classification](../../../../docs/runtime-protocol.md#native-failure-classification) to its errors: MiniMax Code `0.4.12` reports a model provider rejection over ACP as JSON-RPC internal error `-32603` whose only detail is prose, so these failures stay unclassified.

## Runtime image

Expand Down
Loading