ms-tau-sdk 1.2.7.dev7__tar.gz → 1.2.7.dev9__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.dev9}/CHANGELOG.md +14 -0
  2. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/PKG-INFO +5 -4
  3. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/README.md +4 -3
  4. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/pyproject.toml +1 -1
  5. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/protocols/assistant_ui.py +49 -5
  6. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/runtime/failures.py +33 -16
  7. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/runtime/observability.py +1 -0
  8. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/.gitignore +0 -0
  9. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/__init__.py +0 -0
  10. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/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.dev9}/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.dev9}/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.dev9}/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.dev9}/src/ms_tau_sdk/api/__init__.py +0 -0
  15. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/api/a2a.py +0 -0
  16. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/api/chat.py +0 -0
  17. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/api/dependencies.py +0 -0
  18. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/api/health.py +0 -0
  19. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/api/models.py +0 -0
  20. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/api/sessions.py +0 -0
  21. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/app.py +0 -0
  22. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/application.py +0 -0
  23. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/backend/__init__.py +0 -0
  24. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/backend/auth.py +0 -0
  25. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/backend/client.py +0 -0
  26. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/backend/local.py +0 -0
  27. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/backend/mcp.py +0 -0
  28. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/backend/models.py +0 -0
  29. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/backend/routes.py +0 -0
  30. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/cli.py +0 -0
  31. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/errors.py +0 -0
  32. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/logging.py +0 -0
  33. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/protocols/__init__.py +0 -0
  34. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/protocols/a2a_message.py +0 -0
  35. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/protocols/a2a_roles.py +0 -0
  36. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/protocols/strict_json.py +0 -0
  37. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/providers/__init__.py +0 -0
  38. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/providers/definitions.py +0 -0
  39. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/providers/factory.py +0 -0
  40. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/resources/SYSTEM.md +0 -0
  41. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/resources/__init__.py +0 -0
  42. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/resources/loader.py +0 -0
  43. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/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.dev9}/src/ms_tau_sdk/runtime/__init__.py +0 -0
  45. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/runtime/deployment_health.py +0 -0
  46. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/runtime/events.py +0 -0
  47. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/runtime/extensions.py +0 -0
  48. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/runtime/manager.py +0 -0
  49. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/runtime/provenance.py +0 -0
  50. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/runtime/session.py +0 -0
  51. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/runtime/snapshots.py +0 -0
  52. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/runtime/task_context.py +0 -0
  53. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/sessions/__init__.py +0 -0
  54. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/sessions/storage.py +0 -0
  55. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/settings.py +0 -0
  56. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/skills.py +0 -0
  57. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/tools/__init__.py +0 -0
  58. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/tools/mainsequence_mcp.py +0 -0
  59. {ms_tau_sdk-1.2.7.dev7 → ms_tau_sdk-1.2.7.dev9}/src/ms_tau_sdk/tools/task_control.py +0 -0
@@ -2,6 +2,20 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ - A merge to `main` is the release. The release workflow runs on the merge instead of on a
6
+ hand-pushed tag: it publishes the version `pyproject.toml` declares, creates the tag `vX.Y.Z` and
7
+ the GitHub release after the upload, and brings the release merge and the next patch number to
8
+ `development` in one push, so no `X.Y.Z.devN` can follow the final `X.Y.Z`. A merge that still
9
+ declares a released version, or whose tag already names another commit, publishes nothing.
10
+ - Stopped discarding the only diagnostic a message-less provider failure carries. When Tau ends a
11
+ turn with `stopReason: "error"` and no `errorMessage`, the status code from the `provider_error`
12
+ diagnostic is composed into the message the user sees (`Provider error (HTTP 402)`) instead of
13
+ the bare `Provider error` constant, `agent.model.failed` now emits `status_code`, and the
14
+ assistant-ui `error` frame carries `status` and `error_code`. An unpaid account and a
15
+ misconfigured credential are distinguishable again, in the stream and in the logs. The
16
+ message/body split is unchanged: the provider's message is still forwarded verbatim and its raw
17
+ response body still never leaves the process.
18
+
5
19
  ## 1.2.6 — 2026-09-21
6
20
 
7
21
  - 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.dev9
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
@@ -225,9 +225,10 @@ uv run ruff check .
225
225
  uv run mypy
226
226
  ```
227
227
 
228
- Releases are immutable and tag-driven. Pushing a tag that exactly matches the package version—for
229
- example, `v1.0.0`—builds and verifies the wheel and source distribution, then publishes them to
230
- PyPI through OIDC trusted publishing. No PyPI API token or container registry is involved.
228
+ Releases are immutable, and a merge to `main` is the release. Merging `development` into `main`
229
+ builds and verifies the wheel and source distribution of the version `pyproject.toml` declares,
230
+ publishes them to PyPI through OIDC trusted publishing, and tags the merge—for example, `v1.0.0`.
231
+ No PyPI API token or container registry is involved.
231
232
 
232
233
  ## Built on Tau
233
234
 
@@ -195,9 +195,10 @@ uv run ruff check .
195
195
  uv run mypy
196
196
  ```
197
197
 
198
- Releases are immutable and tag-driven. Pushing a tag that exactly matches the package version—for
199
- example, `v1.0.0`—builds and verifies the wheel and source distribution, then publishes them to
200
- PyPI through OIDC trusted publishing. No PyPI API token or container registry is involved.
198
+ Releases are immutable, and a merge to `main` is the release. Merging `development` into `main`
199
+ builds and verifies the wheel and source distribution of the version `pyproject.toml` declares,
200
+ publishes them to PyPI through OIDC trusted publishing, and tags the merge—for example, `v1.0.0`.
201
+ No PyPI API token or container registry is involved.
201
202
 
202
203
  ## Built on Tau
203
204
 
@@ -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.dev9"
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,