ms-tau-sdk 2.0.26.dev79__tar.gz → 2.0.27.dev80__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 (91) hide show
  1. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/CHANGELOG.md +14 -0
  2. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/PKG-INFO +1 -1
  3. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/pyproject.toml +1 -1
  4. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/backend/client.py +2 -0
  5. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/backend/local.py +5 -0
  6. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/backend/models.py +12 -0
  7. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/providers/factory.py +74 -38
  8. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/.gitignore +0 -0
  9. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/README.md +0 -0
  10. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/packages/tau-board/src/ms_tau_board/__init__.py +0 -0
  11. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/packages/tau-board/src/ms_tau_board/app.py +0 -0
  12. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/packages/tau-board/src/ms_tau_board/cli.py +0 -0
  13. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/packages/tau-board/src/ms_tau_board/config.py +0 -0
  14. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/packages/tau-board/src/ms_tau_board/env_file.py +0 -0
  15. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/packages/tau-board/src/ms_tau_board/logs.py +0 -0
  16. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/packages/tau-board/src/ms_tau_board/proxy.py +0 -0
  17. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/packages/tau-board/src/ms_tau_board/state.py +0 -0
  18. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/packages/tau-board/src/ms_tau_board/static/BULMA-LICENSE.txt +0 -0
  19. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/packages/tau-board/src/ms_tau_board/static/app.js +0 -0
  20. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/packages/tau-board/src/ms_tau_board/static/board.css +0 -0
  21. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/packages/tau-board/src/ms_tau_board/static/bulma.min.css +0 -0
  22. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/packages/tau-board/src/ms_tau_board/static/index.html +0 -0
  23. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/__init__.py +0 -0
  24. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/agent_skills/design-agent-icons-and-motion/SKILL.md +0 -0
  25. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/agent_skills/design-agent-icons-and-motion/agents/openai.yaml +0 -0
  26. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/agent_skills/design-agent-icons-and-motion/references/metatables-example.html +0 -0
  27. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/agent_skills/tau_a2a_runtime_adapter/SKILL.md +0 -0
  28. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/agent_skills/tau_local_development/SKILL.md +0 -0
  29. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/agent_skills/tau_project_customization/SKILL.md +0 -0
  30. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/agent_skills/tau_repository_integration/SKILL.md +0 -0
  31. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/agent_skills/tau_security_and_access/SKILL.md +0 -0
  32. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/api/__init__.py +0 -0
  33. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/api/a2a.py +0 -0
  34. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/api/chat.py +0 -0
  35. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/api/conversations.py +0 -0
  36. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/api/dependencies.py +0 -0
  37. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/api/health.py +0 -0
  38. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/api/inspection.py +0 -0
  39. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/api/local_chat.py +0 -0
  40. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/api/models.py +0 -0
  41. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/api/request_identity.py +0 -0
  42. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/api/sessions.py +0 -0
  43. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/app.py +0 -0
  44. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/application.py +0 -0
  45. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/backend/__init__.py +0 -0
  46. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/backend/assertions.py +0 -0
  47. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/backend/auth.py +0 -0
  48. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/backend/mcp.py +0 -0
  49. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/backend/routes.py +0 -0
  50. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/backend/team_memberships.py +0 -0
  51. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/cli.py +0 -0
  52. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/errors.py +0 -0
  53. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/logging.py +0 -0
  54. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/protocols/__init__.py +0 -0
  55. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/protocols/a2a_failure.py +0 -0
  56. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/protocols/a2a_message.py +0 -0
  57. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/protocols/a2a_roles.py +0 -0
  58. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/protocols/assistant_ui.py +0 -0
  59. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/protocols/chat_history.py +0 -0
  60. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/protocols/strict_json.py +0 -0
  61. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/providers/__init__.py +0 -0
  62. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/providers/definitions.py +0 -0
  63. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/providers/tau_compat.py +0 -0
  64. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/resources/SYSTEM.md +0 -0
  65. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/resources/__init__.py +0 -0
  66. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/resources/context_compat.py +0 -0
  67. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/resources/loader.py +0 -0
  68. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/resources/prompts/review-code-repository.md +0 -0
  69. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/runtime/__init__.py +0 -0
  70. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/runtime/deployment_health.py +0 -0
  71. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/runtime/events.py +0 -0
  72. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/runtime/extensions.py +0 -0
  73. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/runtime/failures.py +0 -0
  74. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/runtime/live_turns.py +0 -0
  75. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/runtime/manager.py +0 -0
  76. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/runtime/observability.py +0 -0
  77. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/runtime/provenance.py +0 -0
  78. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/runtime/requester.py +0 -0
  79. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/runtime/session.py +0 -0
  80. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/runtime/snapshots.py +0 -0
  81. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/runtime/task_context.py +0 -0
  82. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/sessions/__init__.py +0 -0
  83. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/sessions/storage.py +0 -0
  84. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/settings.py +0 -0
  85. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/skills.py +0 -0
  86. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/tools/__init__.py +0 -0
  87. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/tools/mainsequence_mcp.py +0 -0
  88. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/tools/mcp_applications.py +0 -0
  89. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/tools/mcp_connection.py +0 -0
  90. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/tools/skill_read.py +0 -0
  91. {ms_tau_sdk-2.0.26.dev79 → ms_tau_sdk-2.0.27.dev80}/src/ms_tau_sdk/tools/task_control.py +0 -0
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ - **Platform providers that this Tau release does not know run from the platform's catalog.**
6
+ Beside each built-in provider credential, the platform now sends `catalog_definition`: the
7
+ address and Tau compatibility settings its provider catalog publishes for the selected model.
8
+ When the pinned `tau-ai==0.4.2` does not know the provider, the runtime builds it from that
9
+ definition through Tau's OpenAI-compatible constructor, `openai-completions` or
10
+ `openai-responses` only, and never from an address or headers saved with the credential. The
11
+ address must pass the checks a custom endpoint's address passes. Providers Tau knows ignore the
12
+ definition and keep Tau's address and settings. Meta (`meta`, one Meta Model API key per
13
+ person) is the first such provider; earlier runtimes refuse it. Errors about a provider outside
14
+ Tau's catalog now say "not in this runtime's Tau catalog" instead of "custom provider". The
15
+ `tau-ai` pin and the ADR 0016 provider patch are unchanged.
16
+
3
17
  ## 2.0.26 — 2026-10-24
4
18
 
5
19
  - **The deployed Agent reads its resources only from `.tau/` by default (ADR 0025).** Tau, a
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ms-tau-sdk
3
- Version: 2.0.26.dev79
3
+ Version: 2.0.27.dev80
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 = "2.0.26.dev79"
7
+ version = "2.0.27.dev80"
8
8
  description = "Workspace-bound Tau application primitives for Main Sequence projects"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13"
@@ -966,6 +966,8 @@ class MainSequenceClient:
966
966
  "base_url": raw.get("base_url"),
967
967
  "headers": raw.get("headers", {}),
968
968
  "api": raw.get("api"),
969
+ # Server-owned, beside the saved credential and never inside it.
970
+ "catalog_definition": hydrated.get("catalog_definition"),
969
971
  "metadata": {
970
972
  "version": hydrated.get("version"),
971
973
  "credential_hash": hydrated.get("credential_hash"),
@@ -610,6 +610,11 @@ class LocalDevelopmentBackend(MainSequenceClient):
610
610
  "user_uid": credential.owner_user_uid,
611
611
  "credential_kind": credential.credential_kind,
612
612
  "credential": {key: value for key, value in raw.items() if value is not None},
613
+ "catalog_definition": (
614
+ credential.catalog_definition.model_dump(mode="json")
615
+ if credential.catalog_definition is not None
616
+ else None
617
+ ),
613
618
  "version": credential.metadata.get("version"),
614
619
  "credential_hash": credential.metadata.get("credential_hash"),
615
620
  }
@@ -422,6 +422,17 @@ class TauResumeSnapshotUploadResponse(BackendModel):
422
422
  canonical_size: int = Field(ge=0)
423
423
 
424
424
 
425
+ class ProviderCatalogDefinition(BackendModel):
426
+ """The platform catalog's address and Tau compatibility settings for the selected model.
427
+
428
+ The platform sends it beside every built-in credential. The runtime reads it only for a
429
+ provider its pinned Tau does not know.
430
+ """
431
+
432
+ base_url: str
433
+ compat: dict[str, Any] = Field(default_factory=dict)
434
+
435
+
425
436
  class ProviderCredential(BackendModel):
426
437
  provider: str
427
438
  custom_id: str | None = None
@@ -438,6 +449,7 @@ class ProviderCredential(BackendModel):
438
449
  # An organization_custom credential carries its key in a header, so the headers never appear
439
450
  # in repr() or str().
440
451
  headers: dict[str, str] = Field(default_factory=dict, repr=False)
452
+ catalog_definition: ProviderCatalogDefinition | None = None
441
453
  metadata: dict[str, Any] = Field(default_factory=dict)
442
454
 
443
455
  def secret(self) -> str:
@@ -116,7 +116,11 @@ class ProviderFactory:
116
116
  self.provider_timeout_seconds = provider_timeout_seconds
117
117
 
118
118
  @staticmethod
119
- def _validate_custom_base_url(base_url: str) -> None:
119
+ def _validate_custom_base_url(
120
+ base_url: str,
121
+ *,
122
+ source: str = "Custom provider credential",
123
+ ) -> None:
120
124
  parsed = urlsplit(base_url)
121
125
  hostname = str(parsed.hostname or "").casefold()
122
126
  if (
@@ -127,13 +131,13 @@ class ProviderFactory:
127
131
  or parsed.query
128
132
  or parsed.fragment
129
133
  ):
130
- raise ConfigurationError("Custom provider credential contains an invalid base_url")
134
+ raise ConfigurationError(f"{source} contains an invalid base_url")
131
135
  if (
132
136
  hostname in BLOCKED_CUSTOM_PROVIDER_HOSTS
133
137
  or hostname == "localhost"
134
138
  or hostname.endswith(".localhost")
135
139
  ):
136
- raise ConfigurationError("Custom provider base_url targets a prohibited host")
140
+ raise ConfigurationError(f"{source} base_url targets a prohibited host")
137
141
  try:
138
142
  address = ipaddress.ip_address(hostname)
139
143
  except ValueError:
@@ -145,7 +149,7 @@ class ProviderFactory:
145
149
  or address.is_reserved
146
150
  or address.is_unspecified
147
151
  ):
148
- raise ConfigurationError("Custom provider base_url targets a prohibited address")
152
+ raise ConfigurationError(f"{source} base_url targets a prohibited address")
149
153
 
150
154
  @staticmethod
151
155
  def _thinking_levels(
@@ -193,19 +197,20 @@ class ProviderFactory:
193
197
  projected_api = provider_control.model.api
194
198
  if projected_api not in CUSTOM_API_TRANSPORTS:
195
199
  raise ConfigurationError(
196
- f"Custom provider {provider_name} uses unsupported Tau API "
197
- f"transport {projected_api}"
200
+ f"Provider {provider_name} is not in this runtime's Tau catalog and uses "
201
+ f"unsupported Tau API transport {projected_api}"
198
202
  )
199
203
  projected_inputs = set(provider_control.model.input)
200
204
  if not projected_inputs or not projected_inputs.issubset(CUSTOM_INPUT_KINDS):
201
205
  raise ConfigurationError(
202
- "Platform provider-control input capabilities are invalid for a custom provider"
206
+ "Platform provider-control input capabilities are invalid for a provider "
207
+ "not in this runtime's Tau catalog"
203
208
  )
204
209
  projected_thinking = set(provider_control.model.thinking_levels)
205
210
  if not provider_control.model.reasoning and projected_thinking:
206
211
  raise ConfigurationError(
207
- "Platform provider-control thinking levels contradict the "
208
- "custom model reasoning capability"
212
+ "Platform provider-control thinking levels contradict the reasoning "
213
+ "capability of a model not in this runtime's Tau catalog"
209
214
  )
210
215
  self.validate_input_media(
211
216
  provider_control,
@@ -405,7 +410,17 @@ class ProviderFactory:
405
410
  timeout_seconds = self.provider_timeout_seconds
406
411
  definition = PROVIDER_DEFINITIONS.get(credential.provider)
407
412
  catalog_provider = CATALOG_BY_NAME.get(credential.provider)
408
- is_custom_provider = catalog_provider is None
413
+ # A provider this Tau does not know runs only through Tau's OpenAI-compatible constructor,
414
+ # as an Organization custom provider or as the platform's catalog defines it.
415
+ outside_tau_catalog = catalog_provider is None
416
+ is_custom_provider = (
417
+ outside_tau_catalog and credential.credential_kind == "organization_custom"
418
+ )
419
+ catalog_definition = (
420
+ credential.catalog_definition
421
+ if outside_tau_catalog and not is_custom_provider
422
+ else None
423
+ )
409
424
  model_metadata = (
410
425
  catalog_provider.model_metadata.get(model)
411
426
  if catalog_provider is not None and model is not None
@@ -415,7 +430,8 @@ class ProviderFactory:
415
430
  normalize_thinking_level(thinking_level) if thinking_level is not None else None
416
431
  )
417
432
  if not provider_control.model.reasoning or (
418
- is_custom_provider and normalized_thinking not in provider_control.model.thinking_levels
433
+ outside_tau_catalog
434
+ and normalized_thinking not in provider_control.model.thinking_levels
419
435
  ):
420
436
  normalized_thinking = None
421
437
  mapped_thinking: str | None = None
@@ -427,37 +443,51 @@ class ProviderFactory:
427
443
  ):
428
444
  mapped_thinking = model_metadata.thinking_level_map[normalized_thinking]
429
445
  api = provider_control.model.api
430
- if is_custom_provider:
431
- if credential.credential_kind != "organization_custom":
432
- raise ConfigurationError(
433
- "A provider absent from Tau's built-in catalog requires an "
434
- "organization_custom credential"
435
- )
446
+ if outside_tau_catalog:
436
447
  if api not in CUSTOM_API_TRANSPORTS:
437
448
  raise ConfigurationError(
438
- f"Custom provider {credential.provider} uses unsupported Tau "
439
- f"API transport {api}"
449
+ f"Provider {credential.provider} is not in this runtime's Tau catalog and "
450
+ f"uses unsupported Tau API transport {api}"
440
451
  )
441
- if credential.api != api:
452
+ if is_custom_provider:
453
+ if credential.api != api:
454
+ raise ConfigurationError(
455
+ "Custom provider credential transport does not match "
456
+ "Platform provider-control evidence"
457
+ )
458
+ if not credential.base_url:
459
+ raise ConfigurationError(
460
+ "Custom provider credential requires an explicit base_url"
461
+ )
462
+ self._validate_custom_base_url(credential.base_url)
463
+ elif catalog_definition is not None:
464
+ self._validate_custom_base_url(
465
+ catalog_definition.base_url,
466
+ source="Platform catalog definition",
467
+ )
468
+ else:
442
469
  raise ConfigurationError(
443
- "Custom provider credential transport does not match "
444
- "Platform provider-control evidence"
470
+ f"Provider {credential.provider} is not in this runtime's Tau catalog, and "
471
+ "the platform sent neither an organization_custom credential nor its "
472
+ "catalog definition"
445
473
  )
446
- if not credential.base_url:
447
- raise ConfigurationError("Custom provider credential requires an explicit base_url")
448
- self._validate_custom_base_url(credential.base_url)
449
- base_url = credential.base_url or (
450
- model_metadata.base_url
451
- if model_metadata is not None and model_metadata.base_url
452
- else definition.base_url
453
- if definition
454
- else "https://api.openai.com/v1"
455
- )
456
- headers = {
457
- **(catalog_provider.headers if catalog_provider is not None else {}),
458
- **(model_metadata.headers if model_metadata is not None else {}),
459
- **credential.headers,
460
- }
474
+ if catalog_definition is not None:
475
+ # The address comes from the platform's catalog, never from the saved credential.
476
+ base_url = catalog_definition.base_url
477
+ headers: dict[str, str] = {}
478
+ else:
479
+ base_url = credential.base_url or (
480
+ model_metadata.base_url
481
+ if model_metadata is not None and model_metadata.base_url
482
+ else definition.base_url
483
+ if definition
484
+ else "https://api.openai.com/v1"
485
+ )
486
+ headers = {
487
+ **(catalog_provider.headers if catalog_provider is not None else {}),
488
+ **(model_metadata.headers if model_metadata is not None else {}),
489
+ **credential.headers,
490
+ }
461
491
  secret = credential.secret()
462
492
  if not secret and not is_custom_provider:
463
493
  raise ConfigurationError(
@@ -527,7 +557,13 @@ class ProviderFactory:
527
557
  and catalog_provider.thinking_parameter in {"reasoning_effort", "reasoning.effort"}
528
558
  else "reasoning_effort"
529
559
  ),
530
- compat=model_metadata.compat if model_metadata is not None else {},
560
+ compat=(
561
+ catalog_definition.compat
562
+ if catalog_definition is not None
563
+ else model_metadata.compat
564
+ if model_metadata is not None
565
+ else {}
566
+ ),
531
567
  )
532
568
  if api == "google-generative-ai":
533
569
  return GoogleGenerativeAIProvider(config)