ms-tau-sdk 1.2.7.dev7__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.
Files changed (59) hide show
  1. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/CHANGELOG.md +9 -0
  2. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/PKG-INFO +1 -1
  3. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/pyproject.toml +1 -1
  4. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/protocols/assistant_ui.py +49 -5
  5. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/runtime/failures.py +33 -16
  6. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/runtime/observability.py +1 -0
  7. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/.gitignore +0 -0
  8. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/README.md +0 -0
  9. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/__init__.py +0 -0
  10. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/agent_skills/tau_a2a_runtime_adapter/SKILL.md +0 -0
  11. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/agent_skills/tau_local_development/SKILL.md +0 -0
  12. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/agent_skills/tau_project_customization/SKILL.md +0 -0
  13. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/agent_skills/tau_repository_integration/SKILL.md +0 -0
  14. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/api/__init__.py +0 -0
  15. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/api/a2a.py +0 -0
  16. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/api/chat.py +0 -0
  17. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/api/dependencies.py +0 -0
  18. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/api/health.py +0 -0
  19. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/api/models.py +0 -0
  20. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/api/sessions.py +0 -0
  21. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/app.py +0 -0
  22. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/application.py +0 -0
  23. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/backend/__init__.py +0 -0
  24. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/backend/auth.py +0 -0
  25. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/backend/client.py +0 -0
  26. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/backend/local.py +0 -0
  27. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/backend/mcp.py +0 -0
  28. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/backend/models.py +0 -0
  29. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/backend/routes.py +0 -0
  30. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/cli.py +0 -0
  31. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/errors.py +0 -0
  32. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/logging.py +0 -0
  33. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/protocols/__init__.py +0 -0
  34. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/protocols/a2a_message.py +0 -0
  35. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/protocols/a2a_roles.py +0 -0
  36. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/protocols/strict_json.py +0 -0
  37. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/providers/__init__.py +0 -0
  38. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/providers/definitions.py +0 -0
  39. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/providers/factory.py +0 -0
  40. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/resources/SYSTEM.md +0 -0
  41. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/resources/__init__.py +0 -0
  42. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/resources/loader.py +0 -0
  43. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/resources/prompts/review-code-repository.md +0 -0
  44. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/runtime/__init__.py +0 -0
  45. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/runtime/deployment_health.py +0 -0
  46. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/runtime/events.py +0 -0
  47. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/runtime/extensions.py +0 -0
  48. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/runtime/manager.py +0 -0
  49. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/runtime/provenance.py +0 -0
  50. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/runtime/session.py +0 -0
  51. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/runtime/snapshots.py +0 -0
  52. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/runtime/task_context.py +0 -0
  53. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/sessions/__init__.py +0 -0
  54. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/sessions/storage.py +0 -0
  55. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/settings.py +0 -0
  56. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/skills.py +0 -0
  57. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/tools/__init__.py +0 -0
  58. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev8}/src/ms_tau_sdk/tools/mainsequence_mcp.py +0 -0
  59. {ms_tau_sdk-1.2.7.dev7 → 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.7.dev7
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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "ms-tau-sdk"
7
- version = "1.2.7.dev7"
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"
@@ -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.terminal_error_text = failure.message
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.terminal_error_text = None
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
- self.terminal_error_text = _assistant_error_message(data)
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
- return [{"type": "error", "errorText": self.terminal_error_text}]
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
- error_message = "Agent turn was aborted" if reason == "aborted" else "Provider error"
36
-
37
- status_code = None
38
- provider_error = False
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,