ms-tau-sdk 1.2.6.dev6__tar.gz → 1.2.7.dev8__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.
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/.gitignore +2 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/CHANGELOG.md +9 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/PKG-INFO +17 -1
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/README.md +14 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/pyproject.toml +7 -1
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/protocols/assistant_ui.py +49 -5
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/runtime/failures.py +33 -16
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/runtime/observability.py +1 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/__init__.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/agent_skills/tau_a2a_runtime_adapter/SKILL.md +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/agent_skills/tau_local_development/SKILL.md +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/agent_skills/tau_project_customization/SKILL.md +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/agent_skills/tau_repository_integration/SKILL.md +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/api/__init__.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/api/a2a.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/api/chat.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/api/dependencies.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/api/health.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/api/models.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/api/sessions.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/app.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/application.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/backend/__init__.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/backend/auth.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/backend/client.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/backend/local.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/backend/mcp.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/backend/models.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/backend/routes.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/cli.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/errors.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/logging.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/protocols/__init__.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/protocols/a2a_message.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/protocols/a2a_roles.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/protocols/strict_json.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/providers/__init__.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/providers/definitions.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/providers/factory.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/resources/SYSTEM.md +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/resources/__init__.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/resources/loader.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/resources/prompts/review-code-repository.md +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/runtime/__init__.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/runtime/deployment_health.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/runtime/events.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/runtime/extensions.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/runtime/manager.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/runtime/provenance.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/runtime/session.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/runtime/snapshots.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/runtime/task_context.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/sessions/__init__.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/sessions/storage.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/settings.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/skills.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/tools/__init__.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/tools/mainsequence_mcp.py +0 -0
- {ms_tau_sdk-1.2.6.dev6 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/tools/task_control.py +0 -0
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
- Stopped discarding the only diagnostic a message-less provider failure carries. When Tau ends a
|
|
6
|
+
turn with `stopReason: "error"` and no `errorMessage`, the status code from the `provider_error`
|
|
7
|
+
diagnostic is composed into the message the user sees (`Provider error (HTTP 402)`) instead of
|
|
8
|
+
the bare `Provider error` constant, `agent.model.failed` now emits `status_code`, and the
|
|
9
|
+
assistant-ui `error` frame carries `status` and `error_code`. An unpaid account and a
|
|
10
|
+
misconfigured credential are distinguishable again, in the stream and in the logs. The
|
|
11
|
+
message/body split is unchanged: the provider's message is still forwarded verbatim and its raw
|
|
12
|
+
response body still never leaves the process.
|
|
13
|
+
|
|
5
14
|
## 1.2.6 — 2026-09-21
|
|
6
15
|
|
|
7
16
|
- Made `pyproject.toml` the only source of the version. `development` declares the release being
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ms-tau-sdk
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.7.dev8
|
|
4
4
|
Summary: Workspace-bound Tau application primitives for Main Sequence projects
|
|
5
5
|
Project-URL: Changelog, https://github.com/mainsequence-sdk/ms-tau-sdk/blob/development/CHANGELOG.md
|
|
6
6
|
Project-URL: Documentation, https://github.com/mainsequence-sdk/ms-tau-sdk/tree/development/docs
|
|
@@ -24,6 +24,8 @@ Requires-Dist: python-multipart>=0.0.20
|
|
|
24
24
|
Requires-Dist: structlog<27,>=26.1
|
|
25
25
|
Requires-Dist: tau-ai==0.4.2
|
|
26
26
|
Requires-Dist: uvicorn[standard]>=0.35
|
|
27
|
+
Provides-Extra: tau-board
|
|
28
|
+
Requires-Dist: ms-tau-board<0.2,>=0.1; extra == 'tau-board'
|
|
27
29
|
Description-Content-Type: text/markdown
|
|
28
30
|
|
|
29
31
|
<p align="center">
|
|
@@ -148,6 +150,20 @@ Local mode also appends privacy-filtered, structured JSON Lines to
|
|
|
148
150
|
and log move under `TAU_LOCAL_STATE_ROOT` when set; neither is written to the project `.tau` or
|
|
149
151
|
uploaded to a platform AgentSession. Console logging remains available.
|
|
150
152
|
|
|
153
|
+
For a small browser view of a local run, install the optional, separately running
|
|
154
|
+
[Tau Board](packages/tau-board/README.md):
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
uv add 'ms-tau-sdk[tau-board]'
|
|
158
|
+
uv run tau-board --tau-url http://127.0.0.1:8010
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Open `http://127.0.0.1:8788` to chat, send A2A Messages and Tasks, inspect local SQLite state,
|
|
162
|
+
and read logs. The board serves packaged Bulma CSS with no frontend build or CDN. It can select
|
|
163
|
+
another loopback Tau endpoint and local state directory in its Connect view. Its Settings tab shows
|
|
164
|
+
the board process's safe environment values and edits named non-secret variables in a local `.env`
|
|
165
|
+
file; Tau or the board must restart before saved startup values take effect.
|
|
166
|
+
|
|
151
167
|
Platform discovery of the unregistered process, internal backend dispatch/caller-delivery hooks,
|
|
152
168
|
push notifications, and `resume_caller` remain unavailable. Outbound A2A through Main Sequence MCP
|
|
153
169
|
supports Messages and Tasks with polling under authenticated-user semantics. Local mode binds to
|
|
@@ -120,6 +120,20 @@ Local mode also appends privacy-filtered, structured JSON Lines to
|
|
|
120
120
|
and log move under `TAU_LOCAL_STATE_ROOT` when set; neither is written to the project `.tau` or
|
|
121
121
|
uploaded to a platform AgentSession. Console logging remains available.
|
|
122
122
|
|
|
123
|
+
For a small browser view of a local run, install the optional, separately running
|
|
124
|
+
[Tau Board](packages/tau-board/README.md):
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
uv add 'ms-tau-sdk[tau-board]'
|
|
128
|
+
uv run tau-board --tau-url http://127.0.0.1:8010
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Open `http://127.0.0.1:8788` to chat, send A2A Messages and Tasks, inspect local SQLite state,
|
|
132
|
+
and read logs. The board serves packaged Bulma CSS with no frontend build or CDN. It can select
|
|
133
|
+
another loopback Tau endpoint and local state directory in its Connect view. Its Settings tab shows
|
|
134
|
+
the board process's safe environment values and edits named non-secret variables in a local `.env`
|
|
135
|
+
file; Tau or the board must restart before saved startup values take effect.
|
|
136
|
+
|
|
123
137
|
Platform discovery of the unregistered process, internal backend dispatch/caller-delivery hooks,
|
|
124
138
|
push notifications, and `resume_caller` remain unavailable. Outbound A2A through Main Sequence MCP
|
|
125
139
|
supports Messages and Tasks with polling under authenticated-user semantics. Local mode binds to
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ms-tau-sdk"
|
|
7
|
-
version = "1.2.
|
|
7
|
+
version = "1.2.7.dev8"
|
|
8
8
|
description = "Workspace-bound Tau application primitives for Main Sequence projects"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.13"
|
|
@@ -30,6 +30,9 @@ dependencies = [
|
|
|
30
30
|
"uvicorn[standard]>=0.35",
|
|
31
31
|
]
|
|
32
32
|
|
|
33
|
+
[project.optional-dependencies]
|
|
34
|
+
tau-board = ["ms-tau-board>=0.1,<0.2"]
|
|
35
|
+
|
|
33
36
|
[project.urls]
|
|
34
37
|
Changelog = "https://github.com/mainsequence-sdk/ms-tau-sdk/blob/development/CHANGELOG.md"
|
|
35
38
|
Documentation = "https://github.com/mainsequence-sdk/ms-tau-sdk/tree/development/docs"
|
|
@@ -62,6 +65,9 @@ include = [
|
|
|
62
65
|
"/src/ms_tau_sdk",
|
|
63
66
|
]
|
|
64
67
|
|
|
68
|
+
[tool.uv.sources]
|
|
69
|
+
ms-tau-board = { path = "packages/tau-board" }
|
|
70
|
+
|
|
65
71
|
[tool.pytest.ini_options]
|
|
66
72
|
addopts = [
|
|
67
73
|
"--import-mode=importlib",
|
|
@@ -8,7 +8,9 @@ Chunk vocabulary (what a ``ui-message-stream`` consumer receives):
|
|
|
8
8
|
- ``tool-call-start`` / ``tool-call-delta`` (``argsText``) / ``tool-call-end``
|
|
9
9
|
when the model has finished a tool call, then ``tool-result`` (``result``,
|
|
10
10
|
``isError``) when the tool has run;
|
|
11
|
-
- ``data-runtime-lifecycle``, ``error`` and ``finish``.
|
|
11
|
+
- ``data-runtime-lifecycle``, ``error`` and ``finish``. An ``error`` chunk
|
|
12
|
+
carries ``errorText`` plus, when the runtime diagnosed them, the ``status``
|
|
13
|
+
and ``error_code`` fields the Command Center stream consumer reads.
|
|
12
14
|
|
|
13
15
|
The tool chunk names are the ones assistant-stream's ``UIMessageStreamDecoder``
|
|
14
16
|
understands; the earlier ``tool-input-available`` / ``tool-output-available``
|
|
@@ -34,6 +36,8 @@ class AssistantUiEncoder:
|
|
|
34
36
|
tool_names: dict[str, str] = field(default_factory=dict)
|
|
35
37
|
finished: bool = False
|
|
36
38
|
terminal_error_text: str | None = None
|
|
39
|
+
terminal_error_status: int | None = None
|
|
40
|
+
terminal_error_code: str | None = None
|
|
37
41
|
|
|
38
42
|
def _tool_call_chunks(
|
|
39
43
|
self,
|
|
@@ -161,15 +165,25 @@ class AssistantUiEncoder:
|
|
|
161
165
|
if event_type == "message_end":
|
|
162
166
|
failure = terminal_assistant_failure(data)
|
|
163
167
|
if failure is not None:
|
|
164
|
-
self.
|
|
168
|
+
self._set_terminal_error(
|
|
169
|
+
failure.message,
|
|
170
|
+
status_code=failure.status_code,
|
|
171
|
+
error_code=failure.error_type,
|
|
172
|
+
)
|
|
165
173
|
elif is_terminal_assistant_message(data):
|
|
166
174
|
# A later successful terminal message means Tau recovered from
|
|
167
175
|
# an earlier error, for example through overflow compaction.
|
|
168
|
-
self.
|
|
176
|
+
self._set_terminal_error(None)
|
|
169
177
|
return []
|
|
170
178
|
if event_type == "error":
|
|
171
179
|
if self.terminal_error_text is None:
|
|
172
|
-
|
|
180
|
+
error = data.get("error")
|
|
181
|
+
error = error if isinstance(error, dict) else {}
|
|
182
|
+
self._set_terminal_error(
|
|
183
|
+
_assistant_error_message(data),
|
|
184
|
+
status_code=_error_status_code(error),
|
|
185
|
+
error_code=_error_code(error),
|
|
186
|
+
)
|
|
173
187
|
return []
|
|
174
188
|
if event_type == "agent_settled" and not self.finished:
|
|
175
189
|
if self.terminal_error_text is not None:
|
|
@@ -178,13 +192,29 @@ class AssistantUiEncoder:
|
|
|
178
192
|
return [{"type": "finish", "finishReason": "stop"}]
|
|
179
193
|
return []
|
|
180
194
|
|
|
195
|
+
def _set_terminal_error(
|
|
196
|
+
self,
|
|
197
|
+
text: str | None,
|
|
198
|
+
*,
|
|
199
|
+
status_code: int | None = None,
|
|
200
|
+
error_code: str | None = None,
|
|
201
|
+
) -> None:
|
|
202
|
+
self.terminal_error_text = text
|
|
203
|
+
self.terminal_error_status = status_code if text is not None else None
|
|
204
|
+
self.terminal_error_code = error_code if text is not None else None
|
|
205
|
+
|
|
181
206
|
def finalize(self) -> list[dict[str, Any]]:
|
|
182
207
|
"""Emit the one terminal frame after the runtime has finished settling."""
|
|
183
208
|
if self.finished:
|
|
184
209
|
return []
|
|
185
210
|
self.finished = True
|
|
186
211
|
if self.terminal_error_text is not None:
|
|
187
|
-
|
|
212
|
+
frame: dict[str, Any] = {"type": "error", "errorText": self.terminal_error_text}
|
|
213
|
+
if self.terminal_error_status is not None:
|
|
214
|
+
frame["status"] = self.terminal_error_status
|
|
215
|
+
if self.terminal_error_code is not None:
|
|
216
|
+
frame["error_code"] = self.terminal_error_code
|
|
217
|
+
return [frame]
|
|
188
218
|
return [{"type": "finish", "finishReason": "stop"}]
|
|
189
219
|
|
|
190
220
|
@staticmethod
|
|
@@ -211,6 +241,20 @@ def _step_finish_reason(data: dict[str, Any]) -> str:
|
|
|
211
241
|
return _STEP_FINISH_REASONS.get(str(stop_reason or ""), "stop")
|
|
212
242
|
|
|
213
243
|
|
|
244
|
+
def _error_status_code(error: dict[str, Any]) -> int | None:
|
|
245
|
+
candidate = error.get("status_code", error.get("statusCode", error.get("status")))
|
|
246
|
+
if isinstance(candidate, bool) or not isinstance(candidate, int):
|
|
247
|
+
return None
|
|
248
|
+
return candidate
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _error_code(error: dict[str, Any]) -> str | None:
|
|
252
|
+
candidate = error.get("errorType", error.get("error_type"))
|
|
253
|
+
if not isinstance(candidate, str) or not candidate.strip():
|
|
254
|
+
return None
|
|
255
|
+
return candidate
|
|
256
|
+
|
|
257
|
+
|
|
214
258
|
def _assistant_error_message(data: dict[str, Any]) -> str:
|
|
215
259
|
error = data.get("error", {})
|
|
216
260
|
if isinstance(error, dict):
|
|
@@ -30,24 +30,14 @@ def terminal_assistant_failure(
|
|
|
30
30
|
if reason not in {"error", "aborted"}:
|
|
31
31
|
return None
|
|
32
32
|
|
|
33
|
+
provider_error, status_code = _provider_error(message.get("diagnostics"))
|
|
34
|
+
|
|
33
35
|
error_message = _nonempty_text(message.get("errorMessage", message.get("error_message")))
|
|
34
36
|
if error_message is None:
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
diagnostics = message.get("diagnostics")
|
|
40
|
-
if isinstance(diagnostics, list):
|
|
41
|
-
for diagnostic in diagnostics:
|
|
42
|
-
if not isinstance(diagnostic, Mapping) or diagnostic.get("type") != "provider_error":
|
|
43
|
-
continue
|
|
44
|
-
provider_error = True
|
|
45
|
-
details = diagnostic.get("details")
|
|
46
|
-
if isinstance(details, Mapping):
|
|
47
|
-
candidate = details.get("status_code")
|
|
48
|
-
if isinstance(candidate, int) and not isinstance(candidate, bool):
|
|
49
|
-
status_code = candidate
|
|
50
|
-
break
|
|
37
|
+
# Tau can end a turn with ``stopReason: "error"`` and no message at all.
|
|
38
|
+
# Returning the bare constant would discard the only diagnostic left, so
|
|
39
|
+
# the status code is composed into the message the caller sees.
|
|
40
|
+
error_message = _composed_message(reason=reason, status_code=status_code)
|
|
51
41
|
|
|
52
42
|
return TerminalAssistantFailure(
|
|
53
43
|
reason=reason,
|
|
@@ -72,6 +62,33 @@ def is_terminal_assistant_message(data: Mapping[str, Any]) -> bool:
|
|
|
72
62
|
return isinstance(reason, str) and bool(reason)
|
|
73
63
|
|
|
74
64
|
|
|
65
|
+
def _provider_error(diagnostics: object) -> tuple[bool, int | None]:
|
|
66
|
+
"""Return whether a provider error was diagnosed, and its status code.
|
|
67
|
+
|
|
68
|
+
Only the status code is read out of ``details``; the raw provider response
|
|
69
|
+
body that sits beside it never leaves this function.
|
|
70
|
+
"""
|
|
71
|
+
if not isinstance(diagnostics, list):
|
|
72
|
+
return False, None
|
|
73
|
+
for diagnostic in diagnostics:
|
|
74
|
+
if not isinstance(diagnostic, Mapping) or diagnostic.get("type") != "provider_error":
|
|
75
|
+
continue
|
|
76
|
+
details = diagnostic.get("details")
|
|
77
|
+
if isinstance(details, Mapping):
|
|
78
|
+
candidate = details.get("status_code")
|
|
79
|
+
if isinstance(candidate, int) and not isinstance(candidate, bool):
|
|
80
|
+
return True, candidate
|
|
81
|
+
return True, None
|
|
82
|
+
return False, None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _composed_message(*, reason: str, status_code: int | None) -> str:
|
|
86
|
+
fallback = "Agent turn was aborted" if reason == "aborted" else "Provider error"
|
|
87
|
+
if status_code is None:
|
|
88
|
+
return fallback
|
|
89
|
+
return f"{fallback} (HTTP {status_code})"
|
|
90
|
+
|
|
91
|
+
|
|
75
92
|
def _nonempty_text(value: object) -> str | None:
|
|
76
93
|
if not isinstance(value, str) or not value.strip():
|
|
77
94
|
return None
|
|
@@ -229,6 +229,7 @@ class TauTurnObserver:
|
|
|
229
229
|
model_attempt=attempt,
|
|
230
230
|
model_duration_ms=round((time.monotonic() - started) * 1000, 3),
|
|
231
231
|
model_error_type=error_type,
|
|
232
|
+
status_code=status_code,
|
|
232
233
|
provider_request_id=_bounded(_first(data, "provider_request_id", "response_id")),
|
|
233
234
|
rate_limited=rate_limited,
|
|
234
235
|
retryable=retryable,
|
|
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
|