agentshim 0.11.0__tar.gz → 0.12.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {agentshim-0.11.0 → agentshim-0.12.0}/CHANGELOG.md +23 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/PKG-INFO +6 -3
- {agentshim-0.11.0 → agentshim-0.12.0}/README.md +5 -2
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/__init__.py +1 -1
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/core/errors.py +4 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/providers/claude/failures.py +17 -3
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/providers/claude/parser.py +30 -11
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/providers/claude/scripted.py +35 -10
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/providers/codex/scripted.py +13 -3
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/testing/__init__.py +5 -2
- {agentshim-0.11.0 → agentshim-0.12.0}/pyproject.toml +1 -1
- {agentshim-0.11.0 → agentshim-0.12.0}/.gitignore +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/agent.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/core/__init__.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/core/_files.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/core/env.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/core/events.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/core/mcp.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/core/pricing.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/core/profile.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/core/provider.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/core/schema.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/core/skills.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/core/stream.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/core/turn.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/core/usage.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/execution/__init__.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/execution/executor.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/execution/host.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/execution/transform.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/providers/__init__.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/providers/claude/__init__.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/providers/claude/events.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/providers/claude/hooks/__init__.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/providers/claude/hooks/confine_reads.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/providers/claude/provider.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/providers/claude/sandbox.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/providers/claude/user_hooks.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/providers/codex/__init__.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/providers/codex/_toml.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/providers/codex/events.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/providers/codex/failures.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/providers/codex/mcp_scope.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/providers/codex/parser.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/providers/codex/provider.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/providers/codex/rules.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/providers/codex/sandbox.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/providers/codex/skills.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/providers/copilot/__init__.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/providers/copilot/events.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/providers/copilot/parser.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/providers/copilot/provider.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/providers/copilot/scripted.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/providers/gemini/__init__.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/providers/gemini/events.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/providers/gemini/parser.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/providers/gemini/provider.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/providers/gemini/scripted.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/providers/opencode/__init__.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/providers/opencode/events.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/providers/opencode/parser.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/providers/opencode/provider.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/providers/opencode/scripted.py +0 -0
- {agentshim-0.11.0 → agentshim-0.12.0}/agentshim/py.typed +0 -0
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.12.0 (2026-10-03)
|
|
4
|
+
|
|
5
|
+
A turn whose output never matched its schema says so. Additive except that
|
|
6
|
+
Claude's `error_max_structured_output_retries` is no longer `OTHER`.
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- `FailureKind.SCHEMA`: the provider gave up producing output that matches the
|
|
11
|
+
requested schema. Claude Code reports it for the
|
|
12
|
+
`error_max_structured_output_retries` result, with the validation errors of
|
|
13
|
+
the last rejected `StructuredOutput` call on `CliExitError.detail`. A resumed
|
|
14
|
+
turn that fails this way keeps its conversation. Codex never reports it:
|
|
15
|
+
`--output-schema` constrains decoding, so its output always parses against
|
|
16
|
+
the schema.
|
|
17
|
+
- `scripted_failure("claude", FailureKind.SCHEMA)`.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- `scripted_failure(provider, FailureKind.OTHER)` scripts a failure that is
|
|
22
|
+
not about the schema (Claude `error_during_execution`, Codex running out of
|
|
23
|
+
context). `scripted_failure("codex", FailureKind.SCHEMA)` raises
|
|
24
|
+
`ValueError`.
|
|
25
|
+
|
|
3
26
|
## 0.11.0 (2026-10-03)
|
|
4
27
|
|
|
5
28
|
A failed turn says why it failed. Additive except that a classified failure of
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: agentshim
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.12.0
|
|
4
4
|
Summary: Provider-agnostic coding-agent CLI shims
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Requires-Dist: tomli>=2; python_full_version < '3.11'
|
|
@@ -200,8 +200,11 @@ AgentShimError
|
|
|
200
200
|
`CliExitError.kind` says why a turn failed, so a caller can pick a policy
|
|
201
201
|
without knowing how any CLI words its errors: `TRANSIENT` (an overload, rate
|
|
202
202
|
limit or server error that waiting may outlast), `USAGE_LIMIT` (a quota or
|
|
203
|
-
billing limit), `AUTH` (credentials),
|
|
204
|
-
|
|
203
|
+
billing limit), `AUTH` (credentials), `SCHEMA` (the provider gave up producing
|
|
204
|
+
output that matches the requested schema; `.detail` holds the last validation
|
|
205
|
+
errors and the conversation survives, so a correction can follow), or `OTHER`.
|
|
206
|
+
Claude Code and Codex classify their failures; the other providers report
|
|
207
|
+
`OTHER`. Codex never reports `SCHEMA`: it constrains decoding to the schema.
|
|
205
208
|
`agentshim.testing.scripted_failure(provider, kind)` scripts each one.
|
|
206
209
|
|
|
207
210
|
## Adding a provider
|
|
@@ -190,8 +190,11 @@ AgentShimError
|
|
|
190
190
|
`CliExitError.kind` says why a turn failed, so a caller can pick a policy
|
|
191
191
|
without knowing how any CLI words its errors: `TRANSIENT` (an overload, rate
|
|
192
192
|
limit or server error that waiting may outlast), `USAGE_LIMIT` (a quota or
|
|
193
|
-
billing limit), `AUTH` (credentials),
|
|
194
|
-
|
|
193
|
+
billing limit), `AUTH` (credentials), `SCHEMA` (the provider gave up producing
|
|
194
|
+
output that matches the requested schema; `.detail` holds the last validation
|
|
195
|
+
errors and the conversation survives, so a correction can follow), or `OTHER`.
|
|
196
|
+
Claude Code and Codex classify their failures; the other providers report
|
|
197
|
+
`OTHER`. Codex never reports `SCHEMA`: it constrains decoding to the schema.
|
|
195
198
|
`agentshim.testing.scripted_failure(provider, kind)` scripts each one.
|
|
196
199
|
|
|
197
200
|
## Adding a provider
|
|
@@ -29,6 +29,10 @@ class FailureKind(str, Enum):
|
|
|
29
29
|
USAGE_LIMIT = "usage_limit"
|
|
30
30
|
#: Missing, expired, or rejected credentials.
|
|
31
31
|
AUTH = "auth"
|
|
32
|
+
#: The provider gave up producing output that matches the requested output
|
|
33
|
+
#: schema. ``detail`` carries the last validation errors it reported, and
|
|
34
|
+
#: the conversation survives: a correction can be sent as the next turn.
|
|
35
|
+
SCHEMA = "schema"
|
|
32
36
|
#: Anything else, including every failure a provider cannot classify.
|
|
33
37
|
OTHER = "other"
|
|
34
38
|
|
|
@@ -4,7 +4,9 @@ Claude reports a failed API request three ways, newest first: an ``error``
|
|
|
4
4
|
kind on the synthetic assistant message it writes for the failure, an
|
|
5
5
|
``api_error_status`` on the final ``result`` frame, and the text
|
|
6
6
|
``API Error: <status> <body>`` in that frame's ``result``. Older builds only
|
|
7
|
-
have the text, so all three are read.
|
|
7
|
+
have the text, so all three are read. A turn whose ``StructuredOutput`` calls
|
|
8
|
+
never validated ends with the ``result`` subtype
|
|
9
|
+
``error_max_structured_output_retries`` instead.
|
|
8
10
|
"""
|
|
9
11
|
|
|
10
12
|
from __future__ import annotations
|
|
@@ -39,17 +41,29 @@ _AUTH_STATUSES = frozenset({401, 403})
|
|
|
39
41
|
#: A request timeout or a rate limit; every 5xx (529 is an overload) is transient too.
|
|
40
42
|
_TRANSIENT_STATUSES = frozenset({408, 429})
|
|
41
43
|
_SERVER_ERRORS = range(500, 600)
|
|
44
|
+
#: The ``result`` subtype of a turn that used up its output-schema retries.
|
|
45
|
+
SCHEMA_RETRIES_SUBTYPE = "error_max_structured_output_retries"
|
|
42
46
|
|
|
43
47
|
|
|
44
|
-
def classify_failure(
|
|
48
|
+
def classify_failure(
|
|
49
|
+
*, subtype: str | None, api_error: str | None, status: int | None, text: str
|
|
50
|
+
) -> FailureKind:
|
|
45
51
|
"""Classify one failed turn from what Claude reported about it.
|
|
46
52
|
|
|
47
|
-
``
|
|
53
|
+
``subtype`` is the error ``result`` frame's subtype, ``api_error`` the
|
|
54
|
+
assistant frame's ``error`` kind, ``status`` the
|
|
48
55
|
result frame's ``api_error_status``, and ``text`` the error text the
|
|
49
56
|
stream (or, when the stream had none, stderr) carried. Tool output is
|
|
50
57
|
never part of ``text``, so a tool that printed an API error cannot make
|
|
51
58
|
the turn look like one.
|
|
52
59
|
"""
|
|
60
|
+
if subtype == SCHEMA_RETRIES_SUBTYPE:
|
|
61
|
+
return FailureKind.SCHEMA
|
|
62
|
+
return _classify_api_failure(api_error=api_error, status=status, text=text)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _classify_api_failure(*, api_error: str | None, status: int | None, text: str) -> FailureKind:
|
|
66
|
+
"""Classify a failure from the API error Claude reported, if it reported one."""
|
|
53
67
|
if api_error in _AUTH_KINDS:
|
|
54
68
|
return FailureKind.AUTH
|
|
55
69
|
if api_error in _USAGE_LIMIT_KINDS:
|
|
@@ -32,7 +32,7 @@ from .events import (
|
|
|
32
32
|
ToolResultBlock,
|
|
33
33
|
parse_frame,
|
|
34
34
|
)
|
|
35
|
-
from .failures import classify_failure
|
|
35
|
+
from .failures import SCHEMA_RETRIES_SUBTYPE, classify_failure
|
|
36
36
|
|
|
37
37
|
if TYPE_CHECKING:
|
|
38
38
|
from collections.abc import Callable, Mapping
|
|
@@ -45,6 +45,10 @@ PROVIDER_NAME = "claude"
|
|
|
45
45
|
#: The built-in tool through which Claude Code loads a skill.
|
|
46
46
|
SKILL_TOOL = "Skill"
|
|
47
47
|
|
|
48
|
+
#: The tool through which Claude Code submits a schema-constrained answer;
|
|
49
|
+
#: its error result is the validation failure of one submission.
|
|
50
|
+
STRUCTURED_OUTPUT_TOOL = "StructuredOutput"
|
|
51
|
+
|
|
48
52
|
#: The built-in tool through which Claude Code reads a file.
|
|
49
53
|
READ_TOOL = "Read"
|
|
50
54
|
|
|
@@ -142,6 +146,9 @@ class ClaudeStreamParser:
|
|
|
142
146
|
self._api_error: str | None = None
|
|
143
147
|
self._api_error_text: str | None = None
|
|
144
148
|
self._api_error_status: int | None = None
|
|
149
|
+
self._subtype: str | None = None
|
|
150
|
+
# The validation errors of the last rejected StructuredOutput call.
|
|
151
|
+
self._schema_errors: str | None = None
|
|
145
152
|
self._stderr: list[str] = []
|
|
146
153
|
|
|
147
154
|
def feed_stdout(self, line: str) -> None:
|
|
@@ -189,7 +196,12 @@ class ClaudeStreamParser:
|
|
|
189
196
|
"""
|
|
190
197
|
reported = [text for text in (self._error, self._api_error_text) if text]
|
|
191
198
|
text = "\n".join(reported) if reported else "\n".join(self._stderr)
|
|
192
|
-
return classify_failure(
|
|
199
|
+
return classify_failure(
|
|
200
|
+
subtype=self._subtype,
|
|
201
|
+
api_error=self._api_error,
|
|
202
|
+
status=self._api_error_status,
|
|
203
|
+
text=text,
|
|
204
|
+
)
|
|
193
205
|
|
|
194
206
|
def _handle(self, frame: object) -> None:
|
|
195
207
|
if isinstance(frame, SystemInit):
|
|
@@ -238,6 +250,8 @@ class ClaudeStreamParser:
|
|
|
238
250
|
|
|
239
251
|
def _tool_result(self, frame: ToolResultBlock) -> None:
|
|
240
252
|
name = self._tools.name(frame.tool_id)
|
|
253
|
+
if name == STRUCTURED_OUTPUT_TOOL and frame.is_error:
|
|
254
|
+
self._schema_errors = frame.output
|
|
241
255
|
self._emit(
|
|
242
256
|
ToolResult(
|
|
243
257
|
tool_id=frame.tool_id,
|
|
@@ -262,7 +276,8 @@ class ClaudeStreamParser:
|
|
|
262
276
|
self._emit(UsageReport(self._usage, frame.total_cost_usd))
|
|
263
277
|
if frame.is_error:
|
|
264
278
|
self._api_error_status = frame.api_error_status
|
|
265
|
-
self.
|
|
279
|
+
self._subtype = frame.subtype
|
|
280
|
+
self._error = self._error_text(frame)
|
|
266
281
|
self._emit(ProviderError(self._error))
|
|
267
282
|
|
|
268
283
|
def _structured_payload(self, frame: ResultFrame) -> object | None:
|
|
@@ -281,12 +296,16 @@ class ClaudeStreamParser:
|
|
|
281
296
|
except (json.JSONDecodeError, ValueError):
|
|
282
297
|
return None
|
|
283
298
|
|
|
299
|
+
def _error_text(self, frame: ResultFrame) -> str:
|
|
300
|
+
"""Say what an error ``result`` frame reported.
|
|
284
301
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
302
|
+
A turn that used up its schema retries is described by the validation
|
|
303
|
+
errors of its last rejected submission, which only the
|
|
304
|
+
``StructuredOutput`` tool result carries. Otherwise an ``error_*``
|
|
305
|
+
subtype carries its messages in ``errors`` and often no ``result``
|
|
306
|
+
text at all, so both are read before falling back to the subtype,
|
|
307
|
+
which is the only part that names some failures.
|
|
308
|
+
"""
|
|
309
|
+
if frame.subtype == SCHEMA_RETRIES_SUBTYPE and self._schema_errors:
|
|
310
|
+
return self._schema_errors
|
|
311
|
+
return frame.text or "; ".join(frame.errors) or frame.subtype or "claude reported an error"
|
|
@@ -120,9 +120,7 @@ def resume_failure_lines(*, session_id: str | None = None) -> tuple[list[str], l
|
|
|
120
120
|
|
|
121
121
|
|
|
122
122
|
#: What Claude writes for a failed request of each kind: the assistant
|
|
123
|
-
#: ``error`` kind, the HTTP status, and the message.
|
|
124
|
-
#: ran but could not satisfy its output schema, the subtype Claude reports
|
|
125
|
-
#: when no StructuredOutput call validated.
|
|
123
|
+
#: ``error`` kind, the HTTP status, and the message.
|
|
126
124
|
_FAILURES: dict[FailureKind, tuple[str, int, str]] = {
|
|
127
125
|
FailureKind.TRANSIENT: (
|
|
128
126
|
"overloaded",
|
|
@@ -133,6 +131,10 @@ _FAILURES: dict[FailureKind, tuple[str, int, str]] = {
|
|
|
133
131
|
FailureKind.AUTH: ("authentication_failed", 401, "Invalid API key \u00b7 Please run /login"),
|
|
134
132
|
}
|
|
135
133
|
|
|
134
|
+
#: The validation errors Claude's ``StructuredOutput`` tool returns for a
|
|
135
|
+
#: submission that does not match the schema.
|
|
136
|
+
SCHEMA_ERRORS = "Output does not match required schema: root: must have required property 'answer'"
|
|
137
|
+
|
|
136
138
|
|
|
137
139
|
def failure_lines(
|
|
138
140
|
kind: FailureKind, *, session_id: str | None = None
|
|
@@ -140,8 +142,10 @@ def failure_lines(
|
|
|
140
142
|
"""Build the stdout, stderr and exit code of a turn that fails with *kind*.
|
|
141
143
|
|
|
142
144
|
An API failure is the synthetic assistant message Claude writes for it,
|
|
143
|
-
then an error ``result`` frame with the HTTP status. ``
|
|
144
|
-
``
|
|
145
|
+
then an error ``result`` frame with the HTTP status. ``SCHEMA`` is a
|
|
146
|
+
``StructuredOutput`` call the tool rejects, then an
|
|
147
|
+
``error_max_structured_output_retries`` result. ``OTHER`` is an
|
|
148
|
+
``error_during_execution`` result, which names no API error.
|
|
145
149
|
|
|
146
150
|
Args:
|
|
147
151
|
kind: The classification the scripted failure must produce.
|
|
@@ -156,12 +160,13 @@ def failure_lines(
|
|
|
156
160
|
result: dict[str, Any] = {"type": "result", "is_error": True, "num_turns": 1}
|
|
157
161
|
if session_id is not None:
|
|
158
162
|
result["session_id"] = session_id
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
result
|
|
162
|
-
|
|
163
|
+
if kind is FailureKind.SCHEMA:
|
|
164
|
+
lines.extend(_rejected_structured_output())
|
|
165
|
+
result.update(subtype="error_max_structured_output_retries", errors=[])
|
|
166
|
+
elif kind is FailureKind.OTHER:
|
|
167
|
+
result.update(subtype="error_during_execution", errors=["the turn stopped unexpectedly"])
|
|
163
168
|
else:
|
|
164
|
-
error, status, text =
|
|
169
|
+
error, status, text = _FAILURES[kind]
|
|
165
170
|
message = {"role": "assistant", "content": [{"type": "text", "text": text}]}
|
|
166
171
|
lines.append(_line({"type": "assistant", "message": message, "error": error}))
|
|
167
172
|
result.update(subtype="success", result=text, api_error_status=status)
|
|
@@ -169,6 +174,26 @@ def failure_lines(
|
|
|
169
174
|
return lines, [], 1
|
|
170
175
|
|
|
171
176
|
|
|
177
|
+
def _rejected_structured_output() -> list[str]:
|
|
178
|
+
"""One ``StructuredOutput`` call and the validation error it got back."""
|
|
179
|
+
call: dict[str, Any] = {
|
|
180
|
+
"type": "tool_use",
|
|
181
|
+
"id": "toolu_schema",
|
|
182
|
+
"name": "StructuredOutput",
|
|
183
|
+
"input": {},
|
|
184
|
+
}
|
|
185
|
+
rejection = {
|
|
186
|
+
"type": "tool_result",
|
|
187
|
+
"tool_use_id": "toolu_schema",
|
|
188
|
+
"content": SCHEMA_ERRORS,
|
|
189
|
+
"is_error": True,
|
|
190
|
+
}
|
|
191
|
+
return [
|
|
192
|
+
_line({"type": "assistant", "message": {"role": "assistant", "content": [call]}}),
|
|
193
|
+
_line({"type": "user", "message": {"role": "user", "content": [rejection]}}),
|
|
194
|
+
]
|
|
195
|
+
|
|
196
|
+
|
|
172
197
|
def _usage_payload(usage: TokenUsage | None) -> dict[str, Any]:
|
|
173
198
|
if usage is None:
|
|
174
199
|
return {
|
|
@@ -130,17 +130,25 @@ _FAILURES: dict[FailureKind, str] = {
|
|
|
130
130
|
"(https://chatgpt.com/explore/plus), or try again in 2 hours."
|
|
131
131
|
),
|
|
132
132
|
FailureKind.AUTH: "unexpected status 401 Unauthorized: Missing bearer authentication",
|
|
133
|
-
FailureKind.OTHER: "
|
|
133
|
+
FailureKind.OTHER: "Codex ran out of room in the model's context window.",
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
|
|
137
|
+
#: Codex sends ``--output-schema`` to the model as a strict response format,
|
|
138
|
+
#: so decoding is constrained to the schema and a turn never fails over it.
|
|
139
|
+
_NO_SCHEMA_FAILURE = (
|
|
140
|
+
"codex never fails a turn over its output schema: decoding is constrained to the schema"
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
137
144
|
def failure_lines(
|
|
138
145
|
kind: FailureKind, *, session_id: str | None = None
|
|
139
146
|
) -> tuple[list[str], list[str], int]:
|
|
140
147
|
"""Build the stdout, stderr and exit code of a turn that fails with *kind*.
|
|
141
148
|
|
|
142
149
|
Codex reports the failure as an ``error`` event followed by
|
|
143
|
-
``turn.failed``, both carrying the same message.
|
|
150
|
+
``turn.failed``, both carrying the same message. ``SCHEMA`` raises
|
|
151
|
+
``ValueError``: Codex has no such failure.
|
|
144
152
|
|
|
145
153
|
Args:
|
|
146
154
|
kind: The classification the scripted failure must produce.
|
|
@@ -149,7 +157,9 @@ def failure_lines(
|
|
|
149
157
|
Returns:
|
|
150
158
|
``(stdout, stderr, returncode)`` for a ``FakeRun``.
|
|
151
159
|
"""
|
|
152
|
-
message = _FAILURES
|
|
160
|
+
message = _FAILURES.get(kind)
|
|
161
|
+
if message is None:
|
|
162
|
+
raise ValueError(_NO_SCHEMA_FAILURE)
|
|
153
163
|
lines: list[str] = []
|
|
154
164
|
if session_id is not None:
|
|
155
165
|
lines.append(_line({"type": "thread.started", "thread_id": session_id}))
|
|
@@ -222,14 +222,17 @@ def scripted_failure(provider: str, kind: FailureKind, *, session_id: str | None
|
|
|
222
222
|
"""Build a ``FakeRun`` for a turn that fails and classifies as *kind*.
|
|
223
223
|
|
|
224
224
|
The run is *provider*'s real report of such a failure (an overload, a
|
|
225
|
-
usage limit, a rejected login,
|
|
225
|
+
usage limit, a rejected login, output that never matched the requested
|
|
226
|
+
schema, or an unclassifiable failure), so the
|
|
226
227
|
raised ``CliExitError`` carries ``kind`` through the real parser. A
|
|
227
228
|
consumer can therefore test its retry policy without knowing how any
|
|
228
229
|
CLI phrases a failure.
|
|
229
230
|
|
|
230
231
|
Args:
|
|
231
232
|
provider: A provider whose stream reports failure kinds (``claude``
|
|
232
|
-
or ``codex``); any other raises ``ValueError
|
|
233
|
+
or ``codex``); any other raises ``ValueError``, as does
|
|
234
|
+
``FailureKind.SCHEMA`` on ``codex``, whose decoding is
|
|
235
|
+
constrained to the schema.
|
|
233
236
|
kind: The ``FailureKind`` the raised error must carry.
|
|
234
237
|
session_id: The conversation the failed run names, if any.
|
|
235
238
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|