agentshim 0.17.0__tar.gz → 0.18.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.
Files changed (114) hide show
  1. {agentshim-0.17.0 → agentshim-0.18.0}/CHANGELOG.md +13 -0
  2. {agentshim-0.17.0 → agentshim-0.18.0}/PKG-INFO +1 -1
  3. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/__init__.py +11 -1
  4. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/core/__init__.py +6 -0
  5. agentshim-0.18.0/agentshim/core/models.py +247 -0
  6. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/core/profile.py +10 -0
  7. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/claude/provider.py +12 -0
  8. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/codex/provider.py +11 -0
  9. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/opencode/provider.py +3 -0
  10. {agentshim-0.17.0 → agentshim-0.18.0}/pyproject.toml +1 -1
  11. {agentshim-0.17.0 → agentshim-0.18.0}/.gitignore +0 -0
  12. {agentshim-0.17.0 → agentshim-0.18.0}/LICENSE +0 -0
  13. {agentshim-0.17.0 → agentshim-0.18.0}/README.md +0 -0
  14. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/agent.py +0 -0
  15. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/core/_files.py +0 -0
  16. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/core/auth.py +0 -0
  17. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/core/checkpoints.py +0 -0
  18. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/core/clock.py +0 -0
  19. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/core/conversation.py +0 -0
  20. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/core/env.py +0 -0
  21. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/core/errors.py +0 -0
  22. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/core/events.py +0 -0
  23. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/core/home.py +0 -0
  24. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/core/ids.py +0 -0
  25. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/core/mcp.py +0 -0
  26. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/core/permissions.py +0 -0
  27. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/core/pricing.py +0 -0
  28. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/core/provider.py +0 -0
  29. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/core/schema.py +0 -0
  30. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/core/session_policy.py +0 -0
  31. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/core/skills.py +0 -0
  32. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/core/status.py +0 -0
  33. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/core/stream.py +0 -0
  34. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/core/turn.py +0 -0
  35. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/core/usage.py +0 -0
  36. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/execution/__init__.py +0 -0
  37. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/execution/confinement.py +0 -0
  38. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/execution/docker.py +0 -0
  39. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/execution/executor.py +0 -0
  40. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/execution/host.py +0 -0
  41. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/execution/process.py +0 -0
  42. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/execution/transform.py +0 -0
  43. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/oneshot.py +0 -0
  44. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/probe.py +0 -0
  45. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/__init__.py +0 -0
  46. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/claude/__init__.py +0 -0
  47. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/claude/events.py +0 -0
  48. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/claude/failures.py +0 -0
  49. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/claude/hooks/__init__.py +0 -0
  50. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/claude/hooks/confine_reads.py +0 -0
  51. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/claude/parser.py +0 -0
  52. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/claude/probe.py +0 -0
  53. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/claude/rate_limits.py +0 -0
  54. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/claude/sandbox.py +0 -0
  55. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/claude/scripted.py +0 -0
  56. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/claude/stream_argv.py +0 -0
  57. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/claude/stream_transport.py +0 -0
  58. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/claude/user_hooks.py +0 -0
  59. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/codex/__init__.py +0 -0
  60. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/codex/_toml.py +0 -0
  61. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/codex/app_server/__init__.py +0 -0
  62. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/codex/app_server/_wire.py +0 -0
  63. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/codex/app_server/approvals.py +0 -0
  64. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/codex/app_server/channel.py +0 -0
  65. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/codex/app_server/errors.py +0 -0
  66. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/codex/app_server/items.py +0 -0
  67. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/codex/app_server/mcp.py +0 -0
  68. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/codex/app_server/permissions.py +0 -0
  69. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/codex/app_server/profile.py +0 -0
  70. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/codex/app_server/protocol.py +0 -0
  71. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/codex/app_server/rate_limits.py +0 -0
  72. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/codex/app_server/transport.py +0 -0
  73. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/codex/app_server/usage.py +0 -0
  74. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/codex/events.py +0 -0
  75. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/codex/failures.py +0 -0
  76. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/codex/mcp_scope.py +0 -0
  77. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/codex/parser.py +0 -0
  78. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/codex/probe.py +0 -0
  79. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/codex/rules.py +0 -0
  80. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/codex/sandbox.py +0 -0
  81. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/codex/scripted.py +0 -0
  82. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/codex/skills.py +0 -0
  83. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/copilot/__init__.py +0 -0
  84. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/copilot/events.py +0 -0
  85. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/copilot/parser.py +0 -0
  86. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/copilot/probe.py +0 -0
  87. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/copilot/provider.py +0 -0
  88. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/copilot/scripted.py +0 -0
  89. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/gemini/__init__.py +0 -0
  90. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/gemini/events.py +0 -0
  91. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/gemini/parser.py +0 -0
  92. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/gemini/probe.py +0 -0
  93. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/gemini/provider.py +0 -0
  94. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/gemini/scripted.py +0 -0
  95. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/opencode/__init__.py +0 -0
  96. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/opencode/events.py +0 -0
  97. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/opencode/parser.py +0 -0
  98. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/opencode/probe.py +0 -0
  99. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/providers/opencode/scripted.py +0 -0
  100. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/py.typed +0 -0
  101. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/runtime.py +0 -0
  102. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/session.py +0 -0
  103. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/testing/__init__.py +0 -0
  104. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/testing/claude_stream.py +0 -0
  105. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/testing/clock.py +0 -0
  106. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/testing/codex_app_server.py +0 -0
  107. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/testing/confinement.py +0 -0
  108. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/testing/contracts.py +0 -0
  109. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/testing/conversation.py +0 -0
  110. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/testing/probe.py +0 -0
  111. {agentshim-0.17.0 → agentshim-0.18.0}/agentshim/testing/process.py +0 -0
  112. {agentshim-0.17.0 → agentshim-0.18.0}/tests/fixtures/claude_stream/README.md +0 -0
  113. {agentshim-0.17.0 → agentshim-0.18.0}/tests/fixtures/codex/README.md +0 -0
  114. {agentshim-0.17.0 → agentshim-0.18.0}/tests/fixtures/codex_app_server/README.md +0 -0
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.18.0 (2026-10-16)
4
+
5
+ ### Added
6
+
7
+ - `context_window(provider, model)` and `model_spec(provider, model)`: a model's context
8
+ window in tokens from a static, versioned table (`ModelSpec`, `ModelTable`,
9
+ `default_models()`), each entry citing the vendor page it was read from. Covers the
10
+ Claude 5 family (`claude-fable-5-1`, `claude-opus-5-5`, `claude-sonnet-5-5`,
11
+ `claude-haiku-5-5`, all 1M) and current OpenAI models. An unknown model, or a
12
+ bracketed variant, returns `None`; the lookup never matches by prefix.
13
+ - `ProviderProfile.models`: the model ids a provider's CLI is known to run, the default
14
+ first, and `ProviderProfile.default_model`. Claude, Codex and opencode declare theirs.
15
+
3
16
  ## 0.17.0 (2026-10-15)
4
17
 
5
18
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: agentshim
3
- Version: 0.17.0
3
+ Version: 0.18.0
4
4
  Summary: Provider-agnostic coding-agent CLI shims
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -57,6 +57,8 @@ from .core import (
57
57
  McpServer,
58
58
  McpTransport,
59
59
  ModelPricing,
60
+ ModelSpec,
61
+ ModelTable,
60
62
  NativeMode,
61
63
  NativePermissions,
62
64
  NoopInstallation,
@@ -124,13 +126,16 @@ from .core import (
124
126
  UsageReport,
125
127
  compact_json,
126
128
  compose_event_handlers,
129
+ context_window,
127
130
  cost_usd,
131
+ default_models,
128
132
  default_pricing,
129
133
  dialect_problems,
130
134
  host_auth_plan,
131
135
  install_config_file,
132
136
  interactive_env,
133
137
  materialize,
138
+ model_spec,
134
139
  normalize,
135
140
  normalized_usage,
136
141
  parse_json_object,
@@ -171,7 +176,7 @@ from .providers.opencode import OpencodeProvider
171
176
  from .runtime import Agent
172
177
  from .session import Session, Turn, TurnTicket, map_mcp_servers
173
178
 
174
- __version__ = "0.17.0"
179
+ __version__ = "0.18.0"
175
180
 
176
181
  __all__ = [
177
182
  "Agent",
@@ -244,6 +249,8 @@ __all__ = [
244
249
  "McpServer",
245
250
  "McpTransport",
246
251
  "ModelPricing",
252
+ "ModelSpec",
253
+ "ModelTable",
247
254
  "NativeMode",
248
255
  "NativePermissions",
249
256
  "NoRunningTurnError",
@@ -327,7 +334,9 @@ __all__ = [
327
334
  "compact_json",
328
335
  "compose_event_handlers",
329
336
  "confine",
337
+ "context_window",
330
338
  "cost_usd",
339
+ "default_models",
331
340
  "default_pricing",
332
341
  "dialect_problems",
333
342
  "get_provider",
@@ -336,6 +345,7 @@ __all__ = [
336
345
  "interactive_env",
337
346
  "map_mcp_servers",
338
347
  "materialize",
348
+ "model_spec",
339
349
  "normalize",
340
350
  "normalized_usage",
341
351
  "parse_json_object",
@@ -95,6 +95,7 @@ from .mcp import (
95
95
  StdioMcpServer,
96
96
  install_config_file,
97
97
  )
98
+ from .models import ModelSpec, ModelTable, context_window, default_models, model_spec
98
99
  from .permissions import ApprovalPolicy, NativeMode, NativePermissions
99
100
  from .pricing import ModelPricing, PricingTable, cost_usd, default_pricing, price_for
100
101
  from .profile import (
@@ -175,6 +176,8 @@ __all__ = [
175
176
  "McpServer",
176
177
  "McpTransport",
177
178
  "ModelPricing",
179
+ "ModelSpec",
180
+ "ModelTable",
178
181
  "NativeMode",
179
182
  "NativePermissions",
180
183
  "NoRunningTurnError",
@@ -242,13 +245,16 @@ __all__ = [
242
245
  "UsageReport",
243
246
  "compact_json",
244
247
  "compose_event_handlers",
248
+ "context_window",
245
249
  "cost_usd",
250
+ "default_models",
246
251
  "default_pricing",
247
252
  "dialect_problems",
248
253
  "host_auth_plan",
249
254
  "install_config_file",
250
255
  "interactive_env",
251
256
  "materialize",
257
+ "model_spec",
252
258
  "normalize",
253
259
  "normalized_usage",
254
260
  "parse_json_object",
@@ -0,0 +1,247 @@
1
+ """Static, versioned per-model facts: each model's context window and where it was read.
2
+
3
+ The table is data: one :class:`ModelSpec` per ``(vendor, model)``, each with
4
+ the official page its numbers were read from and the date they were checked,
5
+ and a table-level ``version`` and ``last_updated``. It is keyed by vendor, the
6
+ same way as the pricing table, because a context window belongs to the model,
7
+ whichever CLI runs it.
8
+
9
+ A lookup of a model the table does not know returns ``None``: an unknown
10
+ window is never guessed from a similar name, a prefix or a model family. A
11
+ bracketed variant (``claude-opus-5-5[1m]``) also returns ``None``, because a
12
+ variant may change the window. Callers pin or extend assumptions by passing
13
+ their own :class:`ModelTable` (see :meth:`ModelTable.with_entries`).
14
+
15
+ Which models a provider CLI is known to run, and which of them is its
16
+ default, is per-provider data on ``ProviderProfile.models``.
17
+
18
+ Updating the table is described in ``docs/models.md``.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import datetime as dt
24
+ import re
25
+ from dataclasses import dataclass, field, replace
26
+ from typing import TYPE_CHECKING, cast
27
+
28
+ from agentshim.core.pricing import PROVIDER_VENDORS
29
+
30
+ if TYPE_CHECKING:
31
+ from collections.abc import Iterable
32
+
33
+ _DATE_SUFFIX = re.compile(r"-\d{8}$")
34
+
35
+ OPENAI_MODELS_URL = "https://developers.openai.com/api/docs/models"
36
+ ANTHROPIC_CONTEXT_WINDOWS_URL = (
37
+ "https://platform.claude.com/docs/en/build-with-claude/context-windows"
38
+ )
39
+
40
+
41
+ @dataclass(frozen=True)
42
+ class ModelSpec:
43
+ """One model's context window, with where and when it was read.
44
+
45
+ Attributes:
46
+ vendor: Who serves the model (``"openai"``, ``"anthropic"``).
47
+ model: The model id as the vendor names it.
48
+ context_window: The model's context window in tokens (input and
49
+ output together), at the vendor's default for that id.
50
+ source: URL of the official page the number was read from.
51
+ checked: Date the number was last checked against ``source``.
52
+ """
53
+
54
+ vendor: str
55
+ model: str
56
+ context_window: int
57
+ source: str
58
+ checked: dt.date
59
+
60
+ def __post_init__(self) -> None:
61
+ """Reject a missing name, source or date, and a window that is not a positive int."""
62
+ for name in ("vendor", "model"):
63
+ value: object = getattr(self, name)
64
+ if not isinstance(value, str) or not value.strip():
65
+ msg = f"{name} must be a non-empty string"
66
+ raise ValueError(msg)
67
+ source = cast("object", self.source)
68
+ if not isinstance(source, str) or not source.startswith("https://"):
69
+ msg = f"{self.vendor}/{self.model}: source must be an https:// URL"
70
+ raise ValueError(msg)
71
+ checked = cast("object", self.checked)
72
+ if not isinstance(checked, dt.date) or isinstance(checked, dt.datetime):
73
+ msg = f"{self.vendor}/{self.model}: checked must be a datetime.date"
74
+ raise TypeError(msg)
75
+ window = cast("object", self.context_window)
76
+ if isinstance(window, bool) or not isinstance(window, int) or window <= 0:
77
+ msg = f"{self.vendor}/{self.model}: context_window must be a positive int"
78
+ raise ValueError(msg)
79
+
80
+ @property
81
+ def key(self) -> tuple[str, str]:
82
+ """The ``(vendor, model)`` this entry describes."""
83
+ return (self.vendor, self.model)
84
+
85
+
86
+ @dataclass(frozen=True)
87
+ class ModelTable:
88
+ """A versioned set of :class:`ModelSpec` entries.
89
+
90
+ Attributes:
91
+ version: Names this set of facts; change it whenever a fact changes.
92
+ last_updated: Date the table as a whole was last updated. No entry
93
+ may have been checked later.
94
+ entries: One entry per ``(vendor, model)``.
95
+ """
96
+
97
+ version: str
98
+ last_updated: dt.date
99
+ entries: tuple[ModelSpec, ...] = field(default=())
100
+
101
+ def __post_init__(self) -> None:
102
+ """Reject a missing version, duplicate keys, and entries checked after ``last_updated``."""
103
+ version = cast("object", self.version)
104
+ if not isinstance(version, str) or not version.strip():
105
+ msg = "version must be a non-empty string"
106
+ raise ValueError(msg)
107
+ object.__setattr__(self, "entries", tuple(self.entries))
108
+ seen: set[tuple[str, str]] = set()
109
+ for entry in self.entries:
110
+ if not isinstance(cast("object", entry), ModelSpec):
111
+ msg = f"entries must be ModelSpec, got {entry!r}"
112
+ raise TypeError(msg)
113
+ if entry.key in seen:
114
+ msg = f"duplicate model entry for {entry.vendor}/{entry.model}"
115
+ raise ValueError(msg)
116
+ seen.add(entry.key)
117
+ if entry.checked > self.last_updated:
118
+ msg = (
119
+ f"{entry.vendor}/{entry.model} was checked {entry.checked}, "
120
+ f"after the table's last_updated {self.last_updated}"
121
+ )
122
+ raise ValueError(msg)
123
+
124
+ def get(self, vendor: str, model: str) -> ModelSpec | None:
125
+ """The entry for exactly ``(vendor, model)``, or ``None``."""
126
+ for entry in self.entries:
127
+ if entry.key == (vendor, model):
128
+ return entry
129
+ return None
130
+
131
+ def with_entries(
132
+ self,
133
+ entries: Iterable[ModelSpec],
134
+ *,
135
+ version: str,
136
+ last_updated: dt.date | None = None,
137
+ ) -> ModelTable:
138
+ """A new table with *entries* added, replacing any with the same key."""
139
+ added = tuple(entries)
140
+ by_key = {entry.key: entry for entry in self.entries}
141
+ by_key.update({entry.key: entry for entry in added})
142
+ latest = max([self.last_updated, *(entry.checked for entry in added)])
143
+ return replace(
144
+ self,
145
+ version=version,
146
+ last_updated=last_updated or latest,
147
+ entries=tuple(by_key.values()),
148
+ )
149
+
150
+
151
+ def model_spec(
152
+ provider: str, model: str | None, table: ModelTable | None = None
153
+ ) -> ModelSpec | None:
154
+ """The facts about *model* run through *provider*, or ``None`` when they are not known.
155
+
156
+ Accepts a provider name (``codex``, ``claude``) or a vendor name, and a
157
+ model with a ``vendor/`` prefix (opencode's ``provider/model``). Drops a
158
+ trailing date snapshot (``claude-haiku-4-5-20251001``). Returns ``None``
159
+ for a bracketed variant (``claude-opus-5-5[1m]``), for a provider that
160
+ runs no known vendor's models, and for any model the table lacks; it
161
+ never matches by prefix or family.
162
+ """
163
+ if not model:
164
+ return None
165
+ name = model.strip().lower()
166
+ if "[" in name:
167
+ return None
168
+ vendor = PROVIDER_VENDORS.get(provider.strip().lower())
169
+ if "/" in name:
170
+ prefix, _, rest = name.partition("/")
171
+ vendor = PROVIDER_VENDORS.get(prefix)
172
+ name = rest
173
+ if vendor is None or not name:
174
+ return None
175
+ return (table or DEFAULT_MODELS).get(vendor, _DATE_SUFFIX.sub("", name))
176
+
177
+
178
+ def context_window(provider: str, model: str | None, table: ModelTable | None = None) -> int | None:
179
+ """*model*'s context window in tokens when run through *provider*, or ``None`` when not known."""
180
+ spec = model_spec(provider, model, table)
181
+ return None if spec is None else spec.context_window
182
+
183
+
184
+ def default_models() -> ModelTable:
185
+ """The table agentshim ships (:data:`DEFAULT_MODELS`)."""
186
+ return DEFAULT_MODELS
187
+
188
+
189
+ _CHECKED = dt.date(2026, 10, 16)
190
+
191
+
192
+ def _openai(model: str, context_window: int) -> ModelSpec:
193
+ # Each model's page under OPENAI_MODELS_URL ("<n> context window").
194
+ return ModelSpec(
195
+ vendor="openai",
196
+ model=model,
197
+ context_window=context_window,
198
+ source=f"{OPENAI_MODELS_URL}/{model}",
199
+ checked=_CHECKED,
200
+ )
201
+
202
+
203
+ def _anthropic(model: str, context_window: int) -> ModelSpec:
204
+ # "Context window sizes by model": the 1M models list 1M as the default
205
+ # with no beta header; "other Claude models ... have a 200k-token" window.
206
+ return ModelSpec(
207
+ vendor="anthropic",
208
+ model=model,
209
+ context_window=context_window,
210
+ source=ANTHROPIC_CONTEXT_WINDOWS_URL,
211
+ checked=_CHECKED,
212
+ )
213
+
214
+
215
+ _ONE_MILLION = 1_000_000
216
+ _OPENAI_1_05M = 1_050_000
217
+
218
+ #: The table agentshim ships. Update it as described in ``docs/models.md``.
219
+ #: A model whose window could not be read from its vendor's page is left out.
220
+ DEFAULT_MODELS = ModelTable(
221
+ version="2026-10-16",
222
+ last_updated=_CHECKED,
223
+ entries=(
224
+ # OpenAI (Codex): each model page reads "1,050,000 context window".
225
+ _openai("gpt-6-astra", _OPENAI_1_05M),
226
+ _openai("gpt-6.1-sol", _OPENAI_1_05M),
227
+ _openai("gpt-6-sol", _OPENAI_1_05M),
228
+ _openai("gpt-6-luna", _OPENAI_1_05M),
229
+ _openai("gpt-5.6-sol", _OPENAI_1_05M),
230
+ _openai("gpt-5.6-terra", _OPENAI_1_05M),
231
+ _openai("gpt-5.6-luna", _OPENAI_1_05M),
232
+ _openai("gpt-5.5", _OPENAI_1_05M),
233
+ # Anthropic (Claude).
234
+ _anthropic("claude-fable-5-1", _ONE_MILLION),
235
+ _anthropic("claude-fable-5", _ONE_MILLION),
236
+ _anthropic("claude-opus-5-5", _ONE_MILLION),
237
+ _anthropic("claude-opus-5", _ONE_MILLION),
238
+ _anthropic("claude-opus-4-8", _ONE_MILLION),
239
+ _anthropic("claude-opus-4-7", _ONE_MILLION),
240
+ _anthropic("claude-opus-4-6", _ONE_MILLION),
241
+ _anthropic("claude-sonnet-5-5", _ONE_MILLION),
242
+ _anthropic("claude-sonnet-5", _ONE_MILLION),
243
+ _anthropic("claude-sonnet-4-6", _ONE_MILLION),
244
+ _anthropic("claude-haiku-5-5", _ONE_MILLION),
245
+ _anthropic("claude-haiku-4-5", 200_000),
246
+ ),
247
+ )
@@ -237,3 +237,13 @@ class ProviderProfile:
237
237
  #: The settings files the CLI reads at start that can change how it logs
238
238
  #: in, or ``None`` if it has none (0.17.0+).
239
239
  settings: SettingsSpec | None = None
240
+ #: The model ids agentshim knows this CLI runs, as its ``--model`` flag
241
+ #: takes them, the provider's default first (0.18.0+). Empty means
242
+ #: agentshim knows of none: a caller may still pass any model, at its own
243
+ #: risk. A model's facts (its context window) are in ``model_spec``.
244
+ models: tuple[str, ...] = ()
245
+
246
+ @property
247
+ def default_model(self) -> str | None:
248
+ """The model to run when a caller must name one and has none: ``models[0]``, or ``None``."""
249
+ return self.models[0] if self.models else None
@@ -69,6 +69,18 @@ STRICT_MCP_FLAG = "--strict-mcp-config"
69
69
 
70
70
  PROFILE = ProviderProfile(
71
71
  name="claude",
72
+ # Model ids Claude Code's ``--model`` takes and agentshim has run, the
73
+ # default first. Context windows are in ``agentshim.core.models``.
74
+ models=(
75
+ "claude-opus-5",
76
+ "claude-opus-5-5",
77
+ "claude-fable-5-1",
78
+ "claude-fable-5",
79
+ "claude-sonnet-5-5",
80
+ "claude-sonnet-5",
81
+ "claude-haiku-5-5",
82
+ "claude-haiku-4-5",
83
+ ),
72
84
  display_name="Claude Code",
73
85
  binary="claude",
74
86
  supports_resume=True,
@@ -73,6 +73,17 @@ _HTTP_HEADERS_UNSUPPORTED = (
73
73
 
74
74
  PROFILE = ProviderProfile(
75
75
  name="codex",
76
+ # Model ids Codex's ``--model`` takes and agentshim has run, the default
77
+ # first. Context windows are in ``agentshim.core.models``.
78
+ models=(
79
+ "gpt-5.6-sol",
80
+ "gpt-5.6-luna",
81
+ "gpt-5.6-terra",
82
+ "gpt-5.5",
83
+ "gpt-6-astra",
84
+ "gpt-6-sol",
85
+ "gpt-6-luna",
86
+ ),
76
87
  display_name="Codex",
77
88
  binary="codex",
78
89
  supports_resume=True,
@@ -47,6 +47,9 @@ _NO_WORKSPACE = "opencode installs MCP servers into <cwd>/opencode.json; the tur
47
47
 
48
48
  PROFILE = ProviderProfile(
49
49
  name="opencode",
50
+ # ``provider/model`` ids opencode's ``--model`` takes and agentshim has
51
+ # run, the default first.
52
+ models=("google-vertex/gemini-3-pro-preview",),
50
53
  display_name="opencode",
51
54
  binary="opencode",
52
55
  supports_resume=True,
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agentshim"
3
- version = "0.17.0"
3
+ version = "0.18.0"
4
4
  description = "Provider-agnostic coding-agent CLI shims"
5
5
  readme = "README.md"
6
6
  license = "MIT"
File without changes
File without changes
File without changes