agentshim 0.20.0__tar.gz → 0.21.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.
- {agentshim-0.20.0 → agentshim-0.21.0}/CHANGELOG.md +17 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/PKG-INFO +1 -1
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/__init__.py +3 -1
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/core/__init__.py +2 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/core/auth.py +38 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/core/models.py +56 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/core/profile.py +5 -1
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/models.py +1 -1
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/claude/model_data.py +10 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/claude/provider.py +4 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/codex/model_data.py +18 -8
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/codex/provider.py +4 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/copilot/provider.py +3 -1
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/opencode/model_data.py +3 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/opencode/provider.py +3 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/pyproject.toml +1 -1
- {agentshim-0.20.0 → agentshim-0.21.0}/.gitignore +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/LICENSE +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/README.md +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/agent.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/core/_files.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/core/checkpoints.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/core/clock.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/core/conversation.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/core/env.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/core/errors.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/core/events.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/core/home.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/core/ids.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/core/mcp.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/core/permissions.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/core/plugin.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/core/pricing.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/core/provider.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/core/schema.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/core/session_policy.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/core/skills.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/core/status.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/core/stream.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/core/turn.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/core/usage.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/execution/__init__.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/execution/confinement.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/execution/docker.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/execution/executor.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/execution/host.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/execution/process.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/execution/readiness.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/execution/transform.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/oneshot.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/pricing.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/probe.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/__init__.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/_testing.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/catalog.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/claude/__init__.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/claude/events.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/claude/failures.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/claude/hooks/__init__.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/claude/hooks/confine_reads.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/claude/parser.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/claude/plugin.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/claude/probe.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/claude/rate_limits.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/claude/sandbox.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/claude/scripted.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/claude/stream_argv.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/claude/stream_transport.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/claude/testing.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/claude/tools.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/claude/user_hooks.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/codex/__init__.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/codex/_toml.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/codex/app_server/__init__.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/codex/app_server/_wire.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/codex/app_server/approvals.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/codex/app_server/channel.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/codex/app_server/errors.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/codex/app_server/items.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/codex/app_server/mcp.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/codex/app_server/permissions.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/codex/app_server/profile.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/codex/app_server/protocol.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/codex/app_server/rate_limits.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/codex/app_server/transport.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/codex/app_server/usage.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/codex/events.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/codex/failures.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/codex/mcp_scope.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/codex/parser.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/codex/plugin.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/codex/probe.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/codex/rules.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/codex/sandbox.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/codex/scripted.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/codex/skills.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/codex/testing.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/copilot/__init__.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/copilot/events.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/copilot/parser.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/copilot/plugin.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/copilot/probe.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/copilot/scripted.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/copilot/testing.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/gemini/__init__.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/gemini/events.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/gemini/parser.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/gemini/plugin.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/gemini/probe.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/gemini/provider.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/gemini/scripted.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/gemini/testing.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/opencode/__init__.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/opencode/events.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/opencode/parser.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/opencode/plugin.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/opencode/probe.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/opencode/scripted.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/opencode/testing.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/providers/static_plugin.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/py.typed +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/runtime.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/session.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/testing/__init__.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/testing/claude_stream.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/testing/clock.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/testing/codex_app_server.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/testing/confinement.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/testing/contracts.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/testing/conversation.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/testing/probe.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/agentshim/testing/process.py +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/tests/fixtures/claude_stream/README.md +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/tests/fixtures/codex/README.md +0 -0
- {agentshim-0.20.0 → agentshim-0.21.0}/tests/fixtures/codex_app_server/README.md +0 -0
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.21.0 (2026-10-18)
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `ModelSpec` ranks a model's generation among its vendor's: `generation_id` (`"5.5"`, shared
|
|
8
|
+
by every model of that generation), `generation` (`"Claude 5.5"`, `"GPT-6"`) and
|
|
9
|
+
`generation_rank` (`1` the newest). The three are set together, default to `None`, and are
|
|
10
|
+
filled for every shipped model. `ModelTable` rejects a vendor whose generations disagree on a
|
|
11
|
+
name or rank, or share a rank. Newest first: Claude 5.5, 5.1, 5, 4.8, 4.7, 4.6, 4.5; GPT-6.1,
|
|
12
|
+
6, 5.6, 5.5; Gemini 3.
|
|
13
|
+
- `ProviderProfile.login` is a `LoginSpec`: the CLI's own command for a person to sign in
|
|
14
|
+
(`claude auth login`, `codex login` with `--device-auth` as its device variant,
|
|
15
|
+
`copilot login`, `opencode auth login`), and whether it needs a terminal.
|
|
16
|
+
`LoginSpec.command(binary, device=False)` is the full command line. Gemini CLI has no
|
|
17
|
+
sign-in command (`login` is `None`). Signed-in state stays in `ProbeSpec.auth` and
|
|
18
|
+
`probe_provider`.
|
|
19
|
+
|
|
3
20
|
## 0.20.0 (2026-10-18)
|
|
4
21
|
|
|
5
22
|
### Added
|
|
@@ -51,6 +51,7 @@ from .core import (
|
|
|
51
51
|
IdAllocator,
|
|
52
52
|
InMemoryCheckpointStore,
|
|
53
53
|
Lifecycle,
|
|
54
|
+
LoginSpec,
|
|
54
55
|
McpConfigError,
|
|
55
56
|
McpInstallation,
|
|
56
57
|
McpMechanism,
|
|
@@ -184,7 +185,7 @@ from .providers.opencode import OpencodeProvider
|
|
|
184
185
|
from .runtime import Agent
|
|
185
186
|
from .session import Session, Turn, TurnTicket, map_mcp_servers
|
|
186
187
|
|
|
187
|
-
__version__ = "0.
|
|
188
|
+
__version__ = "0.21.0"
|
|
188
189
|
|
|
189
190
|
__all__ = [ # noqa: RUF022 - public API test uses Python's case-sensitive order
|
|
190
191
|
"Agent",
|
|
@@ -253,6 +254,7 @@ __all__ = [ # noqa: RUF022 - public API test uses Python's case-sensitive order
|
|
|
253
254
|
"IdAllocator",
|
|
254
255
|
"InMemoryCheckpointStore",
|
|
255
256
|
"Lifecycle",
|
|
257
|
+
"LoginSpec",
|
|
256
258
|
"McpConfigError",
|
|
257
259
|
"McpInstallation",
|
|
258
260
|
"McpMechanism",
|
|
@@ -19,6 +19,7 @@ from .auth import (
|
|
|
19
19
|
FileCredential,
|
|
20
20
|
HostAuthGap,
|
|
21
21
|
HostAuthUnplannableError,
|
|
22
|
+
LoginSpec,
|
|
22
23
|
SettingsSpec,
|
|
23
24
|
StagedCredentialFile,
|
|
24
25
|
host_auth_plan,
|
|
@@ -171,6 +172,7 @@ __all__ = [
|
|
|
171
172
|
"IdAllocator",
|
|
172
173
|
"InMemoryCheckpointStore",
|
|
173
174
|
"Lifecycle",
|
|
175
|
+
"LoginSpec",
|
|
174
176
|
"McpConfigError",
|
|
175
177
|
"McpInstallation",
|
|
176
178
|
"McpMechanism",
|
|
@@ -151,6 +151,44 @@ class EndpointSpec:
|
|
|
151
151
|
credentials: frozenset[CredentialKind] = frozenset({CredentialKind.API_KEY})
|
|
152
152
|
|
|
153
153
|
|
|
154
|
+
@dataclass(frozen=True)
|
|
155
|
+
class LoginSpec:
|
|
156
|
+
"""How a person signs a CLI in, as the CLI itself does it.
|
|
157
|
+
|
|
158
|
+
``argv`` follows the binary and starts the CLI's own subscription login
|
|
159
|
+
(a browser page, or a URL to open). ``device_argv`` is the variant for a
|
|
160
|
+
machine without a usable browser, where the CLI prints a code to enter on
|
|
161
|
+
another device, or ``None`` when the CLI has none. ``needs_terminal`` is
|
|
162
|
+
true when the command prompts the person (a menu, a pasted code), so it
|
|
163
|
+
must run attached to a terminal; false when it runs unattended and only
|
|
164
|
+
prints a URL. Whether a CLI is signed in is not here: the provider's
|
|
165
|
+
``ProbeSpec.auth`` reads that, where the CLI has a status command.
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
argv: tuple[str, ...]
|
|
169
|
+
device_argv: tuple[str, ...] | None = None
|
|
170
|
+
needs_terminal: bool = False
|
|
171
|
+
|
|
172
|
+
def __post_init__(self) -> None:
|
|
173
|
+
"""Reject an empty command."""
|
|
174
|
+
if not self.argv or (self.device_argv is not None and not self.device_argv):
|
|
175
|
+
msg = "LoginSpec commands need at least one argument"
|
|
176
|
+
raise ValueError(msg)
|
|
177
|
+
|
|
178
|
+
def command(self, binary: str, *, device: bool = False) -> tuple[str, ...]:
|
|
179
|
+
"""The full command line, with *binary* first.
|
|
180
|
+
|
|
181
|
+
Raises:
|
|
182
|
+
ValueError: *device* is asked for and the CLI has no device login.
|
|
183
|
+
"""
|
|
184
|
+
if not device:
|
|
185
|
+
return (binary, *self.argv)
|
|
186
|
+
if self.device_argv is None:
|
|
187
|
+
msg = f"{binary} has no device login"
|
|
188
|
+
raise ValueError(msg)
|
|
189
|
+
return (binary, *self.device_argv)
|
|
190
|
+
|
|
191
|
+
|
|
154
192
|
@dataclass(frozen=True)
|
|
155
193
|
class AuthPolicy:
|
|
156
194
|
"""Which credentials a caller allows, and the endpoint to use.
|
|
@@ -30,6 +30,14 @@ class ModelSpec:
|
|
|
30
30
|
that sits on no tier ladder.
|
|
31
31
|
tier_name: The tier's readable name (``"Opus"``); set together with
|
|
32
32
|
``tier``.
|
|
33
|
+
generation_id: A short stable token naming the model's generation
|
|
34
|
+
within its vendor (``"5.5"``); every model of one generation
|
|
35
|
+
shares it, and no two generations of a vendor do.
|
|
36
|
+
generation: The generation as people call it (``"Claude 5.5"``,
|
|
37
|
+
``"GPT-6"``); set together with ``generation_id``.
|
|
38
|
+
generation_rank: The generation's rank among its vendor's, ``1`` the
|
|
39
|
+
newest; set together with ``generation_id``. ``ModelTable``
|
|
40
|
+
checks that one vendor's generations have distinct ranks.
|
|
33
41
|
"""
|
|
34
42
|
|
|
35
43
|
vendor: str
|
|
@@ -41,6 +49,9 @@ class ModelSpec:
|
|
|
41
49
|
family: str | None = None
|
|
42
50
|
tier: int | None = None
|
|
43
51
|
tier_name: str | None = None
|
|
52
|
+
generation_id: str | None = None
|
|
53
|
+
generation: str | None = None
|
|
54
|
+
generation_rank: int | None = None
|
|
44
55
|
|
|
45
56
|
def __post_init__(self) -> None:
|
|
46
57
|
"""Reject a missing name, source or date, and a window that is not a positive int."""
|
|
@@ -62,6 +73,26 @@ class ModelSpec:
|
|
|
62
73
|
msg = f"{self.vendor}/{self.model}: context_window must be a positive int"
|
|
63
74
|
raise ValueError(msg)
|
|
64
75
|
self._check_presentation()
|
|
76
|
+
self._check_generation()
|
|
77
|
+
|
|
78
|
+
def _check_generation(self) -> None:
|
|
79
|
+
"""Reject a half-set generation triple, a blank name, and a rank that is not a positive int."""
|
|
80
|
+
parts = (self.generation_id, self.generation, self.generation_rank)
|
|
81
|
+
if any(part is None for part in parts) and any(part is not None for part in parts):
|
|
82
|
+
msg = (
|
|
83
|
+
f"{self.vendor}/{self.model}: "
|
|
84
|
+
"generation_id, generation and generation_rank are set together"
|
|
85
|
+
)
|
|
86
|
+
raise ValueError(msg)
|
|
87
|
+
for name in ("generation_id", "generation"):
|
|
88
|
+
value: object = getattr(self, name)
|
|
89
|
+
if value is not None and (not isinstance(value, str) or not value.strip()):
|
|
90
|
+
msg = f"{self.vendor}/{self.model}: {name} must be a non-empty string"
|
|
91
|
+
raise ValueError(msg)
|
|
92
|
+
rank = cast("object", self.generation_rank)
|
|
93
|
+
if rank is not None and (isinstance(rank, bool) or not isinstance(rank, int) or rank < 1):
|
|
94
|
+
msg = f"{self.vendor}/{self.model}: generation_rank must be a positive int"
|
|
95
|
+
raise ValueError(msg)
|
|
65
96
|
|
|
66
97
|
def _check_presentation(self) -> None:
|
|
67
98
|
"""Reject a half-set name pair or tier pair, and a tier that is not a positive int."""
|
|
@@ -125,6 +156,31 @@ class ModelTable:
|
|
|
125
156
|
f"after the table's last_updated {self.last_updated}"
|
|
126
157
|
)
|
|
127
158
|
raise ValueError(msg)
|
|
159
|
+
self._check_generations()
|
|
160
|
+
|
|
161
|
+
def _check_generations(self) -> None:
|
|
162
|
+
"""Reject a vendor whose generations disagree on a name or rank, or share a rank."""
|
|
163
|
+
by_id: dict[tuple[str, str], tuple[str | None, int | None]] = {}
|
|
164
|
+
by_rank: dict[tuple[str, int], str] = {}
|
|
165
|
+
for entry in self.entries:
|
|
166
|
+
if entry.generation_id is None or entry.generation_rank is None:
|
|
167
|
+
continue
|
|
168
|
+
known = by_id.setdefault(
|
|
169
|
+
(entry.vendor, entry.generation_id), (entry.generation, entry.generation_rank)
|
|
170
|
+
)
|
|
171
|
+
if known != (entry.generation, entry.generation_rank):
|
|
172
|
+
msg = (
|
|
173
|
+
f"{entry.vendor}/{entry.model}: generation {entry.generation_id!r} "
|
|
174
|
+
"has another name or rank on another model"
|
|
175
|
+
)
|
|
176
|
+
raise ValueError(msg)
|
|
177
|
+
holder = by_rank.setdefault((entry.vendor, entry.generation_rank), entry.generation_id)
|
|
178
|
+
if holder != entry.generation_id:
|
|
179
|
+
msg = (
|
|
180
|
+
f"{entry.vendor}: generations {holder!r} and {entry.generation_id!r} "
|
|
181
|
+
f"share rank {entry.generation_rank}"
|
|
182
|
+
)
|
|
183
|
+
raise ValueError(msg)
|
|
128
184
|
|
|
129
185
|
def get(self, vendor: str, model: str) -> ModelSpec | None:
|
|
130
186
|
"""The entry for exactly ``(vendor, model)``, or ``None``."""
|
|
@@ -16,7 +16,7 @@ from .permissions import NativeMode
|
|
|
16
16
|
if TYPE_CHECKING:
|
|
17
17
|
from collections.abc import Mapping
|
|
18
18
|
|
|
19
|
-
from .auth import CredentialSource, EndpointSpec, SettingsSpec
|
|
19
|
+
from .auth import CredentialSource, EndpointSpec, LoginSpec, SettingsSpec
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
class McpMechanism(Enum):
|
|
@@ -242,6 +242,10 @@ class ProviderProfile:
|
|
|
242
242
|
#: agentshim knows of none: a caller may still pass any model, at its own
|
|
243
243
|
#: risk. A model's facts (its context window) are in ``model_spec``.
|
|
244
244
|
models: tuple[str, ...] = ()
|
|
245
|
+
#: The CLI's own command for a person to sign in (0.21.0+), or ``None``
|
|
246
|
+
#: when it has none (Gemini CLI signs in on its first interactive run).
|
|
247
|
+
#: Signed-in state is read by the provider's ``ProbeSpec``.
|
|
248
|
+
login: LoginSpec | None = None
|
|
245
249
|
|
|
246
250
|
@property
|
|
247
251
|
def default_model(self) -> str | None:
|
|
@@ -88,4 +88,4 @@ def default_models(*, catalog: ProviderCatalog | None = None) -> ModelTable:
|
|
|
88
88
|
by_key[entry.key] = entry
|
|
89
89
|
entries = tuple(by_key.values())
|
|
90
90
|
updated = max((entry.checked for entry in entries), default=dt.date(2026, 10, 17))
|
|
91
|
-
return ModelTable(version="2026-10-
|
|
91
|
+
return ModelTable(version="2026-10-18.1", last_updated=updated, entries=entries)
|
|
@@ -16,6 +16,7 @@ _PRICE_CHECKED = dt.date(2026, 9, 27)
|
|
|
16
16
|
def _anthropic_model(
|
|
17
17
|
model: str, context_window: int, tier: tuple[int, str], version: str
|
|
18
18
|
) -> ModelSpec:
|
|
19
|
+
generation_id, generation_rank = _GENERATIONS[version]
|
|
19
20
|
return ModelSpec(
|
|
20
21
|
vendor="anthropic",
|
|
21
22
|
model=model,
|
|
@@ -26,9 +27,18 @@ def _anthropic_model(
|
|
|
26
27
|
family="Claude",
|
|
27
28
|
tier=tier[0],
|
|
28
29
|
tier_name=tier[1],
|
|
30
|
+
generation_id=generation_id,
|
|
31
|
+
generation=f"Claude {version}",
|
|
32
|
+
generation_rank=generation_rank,
|
|
29
33
|
)
|
|
30
34
|
|
|
31
35
|
|
|
36
|
+
#: Claude's generations by version, newest first: ``(generation_id, rank)``.
|
|
37
|
+
_GENERATIONS = {
|
|
38
|
+
version: (version, rank)
|
|
39
|
+
for rank, version in enumerate(("5.5", "5.1", "5", "4.8", "4.7", "4.6", "4.5"), start=1)
|
|
40
|
+
}
|
|
41
|
+
|
|
32
42
|
_HAIKU = (1, "Haiku")
|
|
33
43
|
_SONNET = (2, "Sonnet")
|
|
34
44
|
_OPUS = (3, "Opus")
|
|
@@ -11,6 +11,7 @@ from agentshim.core.auth import (
|
|
|
11
11
|
EnvCredential,
|
|
12
12
|
ExternalCredential,
|
|
13
13
|
FileCredential,
|
|
14
|
+
LoginSpec,
|
|
14
15
|
SettingsSpec,
|
|
15
16
|
)
|
|
16
17
|
from agentshim.core.errors import FailureKind, ProviderCapabilityError, SessionResumeError
|
|
@@ -83,6 +84,9 @@ PROFILE = ProviderProfile(
|
|
|
83
84
|
"claude-haiku-4-5",
|
|
84
85
|
),
|
|
85
86
|
display_name="Claude Code",
|
|
87
|
+
# ``claude auth login`` opens a browser page and, when none opens, asks for a
|
|
88
|
+
# pasted code, so it needs a terminal. It has no device-code variant.
|
|
89
|
+
login=LoginSpec(argv=("auth", "login"), needs_terminal=True),
|
|
86
90
|
binary="claude",
|
|
87
91
|
supports_resume=True,
|
|
88
92
|
supports_reasoning_effort=True,
|
|
@@ -15,8 +15,10 @@ def _openai_model(
|
|
|
15
15
|
model: str,
|
|
16
16
|
context_window: int,
|
|
17
17
|
display_name: str,
|
|
18
|
+
version: str,
|
|
18
19
|
tier: tuple[int, str] | None = None,
|
|
19
20
|
) -> ModelSpec:
|
|
21
|
+
generation_id, generation_rank = _GENERATIONS[version]
|
|
20
22
|
return ModelSpec(
|
|
21
23
|
vendor="openai",
|
|
22
24
|
model=model,
|
|
@@ -27,9 +29,17 @@ def _openai_model(
|
|
|
27
29
|
family="GPT",
|
|
28
30
|
tier=None if tier is None else tier[0],
|
|
29
31
|
tier_name=None if tier is None else tier[1],
|
|
32
|
+
generation_id=generation_id,
|
|
33
|
+
generation=f"GPT-{version}",
|
|
34
|
+
generation_rank=generation_rank,
|
|
30
35
|
)
|
|
31
36
|
|
|
32
37
|
|
|
38
|
+
#: GPT's generations by version, newest first: ``(generation_id, rank)``.
|
|
39
|
+
_GENERATIONS = {
|
|
40
|
+
version: (version, rank) for rank, version in enumerate(("6.1", "6", "5.6", "5.5"), start=1)
|
|
41
|
+
}
|
|
42
|
+
|
|
33
43
|
_LUNA = (1, "Luna")
|
|
34
44
|
_TERRA = (2, "Terra")
|
|
35
45
|
_SOL = (3, "Sol")
|
|
@@ -37,14 +47,14 @@ _ASTRA = (4, "Astra")
|
|
|
37
47
|
_OPENAI_1_05M = 1_050_000
|
|
38
48
|
|
|
39
49
|
MODEL_ENTRIES = (
|
|
40
|
-
_openai_model("gpt-6-astra", _OPENAI_1_05M, "GPT-6 Astra", _ASTRA),
|
|
41
|
-
_openai_model("gpt-6.1-sol", _OPENAI_1_05M, "GPT-6.1 Sol", _SOL),
|
|
42
|
-
_openai_model("gpt-6-sol", _OPENAI_1_05M, "GPT-6 Sol", _SOL),
|
|
43
|
-
_openai_model("gpt-6-luna", _OPENAI_1_05M, "GPT-6 Luna", _LUNA),
|
|
44
|
-
_openai_model("gpt-5.6-sol", _OPENAI_1_05M, "GPT-5.6 Sol", _SOL),
|
|
45
|
-
_openai_model("gpt-5.6-terra", _OPENAI_1_05M, "GPT-5.6 Terra", _TERRA),
|
|
46
|
-
_openai_model("gpt-5.6-luna", _OPENAI_1_05M, "GPT-5.6 Luna", _LUNA),
|
|
47
|
-
_openai_model("gpt-5.5", _OPENAI_1_05M, "GPT-5.5"),
|
|
50
|
+
_openai_model("gpt-6-astra", _OPENAI_1_05M, "GPT-6 Astra", "6", _ASTRA),
|
|
51
|
+
_openai_model("gpt-6.1-sol", _OPENAI_1_05M, "GPT-6.1 Sol", "6.1", _SOL),
|
|
52
|
+
_openai_model("gpt-6-sol", _OPENAI_1_05M, "GPT-6 Sol", "6", _SOL),
|
|
53
|
+
_openai_model("gpt-6-luna", _OPENAI_1_05M, "GPT-6 Luna", "6", _LUNA),
|
|
54
|
+
_openai_model("gpt-5.6-sol", _OPENAI_1_05M, "GPT-5.6 Sol", "5.6", _SOL),
|
|
55
|
+
_openai_model("gpt-5.6-terra", _OPENAI_1_05M, "GPT-5.6 Terra", "5.6", _TERRA),
|
|
56
|
+
_openai_model("gpt-5.6-luna", _OPENAI_1_05M, "GPT-5.6 Luna", "5.6", _LUNA),
|
|
57
|
+
_openai_model("gpt-5.5", _OPENAI_1_05M, "GPT-5.5", "5.5"),
|
|
48
58
|
)
|
|
49
59
|
|
|
50
60
|
_OPENAI_NOTE = (
|
|
@@ -11,6 +11,7 @@ from agentshim.core.auth import (
|
|
|
11
11
|
EndpointSpec,
|
|
12
12
|
EnvCredential,
|
|
13
13
|
FileCredential,
|
|
14
|
+
LoginSpec,
|
|
14
15
|
classify_json_field,
|
|
15
16
|
)
|
|
16
17
|
from agentshim.core.errors import ProviderCapabilityError, SessionResumeError
|
|
@@ -87,6 +88,9 @@ PROFILE = ProviderProfile(
|
|
|
87
88
|
),
|
|
88
89
|
display_name="Codex",
|
|
89
90
|
binary="codex",
|
|
91
|
+
# ``codex login`` prints a URL and waits for the browser redirect on a local
|
|
92
|
+
# port; ``--device-auth`` prints a code to enter on another device instead.
|
|
93
|
+
login=LoginSpec(argv=("login",), device_argv=("login", "--device-auth")),
|
|
90
94
|
supports_resume=True,
|
|
91
95
|
supports_reasoning_effort=True,
|
|
92
96
|
mcp=McpMechanism.CLI_FLAGS,
|
|
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import json
|
|
6
6
|
from typing import TYPE_CHECKING, Any
|
|
7
7
|
|
|
8
|
-
from agentshim.core.auth import CredentialKind, EnvCredential, FileCredential
|
|
8
|
+
from agentshim.core.auth import CredentialKind, EnvCredential, FileCredential, LoginSpec
|
|
9
9
|
from agentshim.core.errors import ProviderCapabilityError
|
|
10
10
|
from agentshim.core.mcp import FlagsInstallation, HttpMcpServer, NoopInstallation
|
|
11
11
|
from agentshim.core.profile import McpMechanism, OutputSchemaStyle, ProviderProfile
|
|
@@ -28,6 +28,8 @@ PROFILE = ProviderProfile(
|
|
|
28
28
|
name="copilot",
|
|
29
29
|
display_name="Copilot CLI",
|
|
30
30
|
binary="copilot",
|
|
31
|
+
# ``copilot login`` runs GitHub's device flow: a code to enter at a URL.
|
|
32
|
+
login=LoginSpec(argv=("login",)),
|
|
31
33
|
supports_resume=True,
|
|
32
34
|
# ``--effort`` exists on the CLI but the provider does not expose it: the
|
|
33
35
|
# levels are model-specific and unvalidated, so a request naming one is
|
|
@@ -17,6 +17,7 @@ from __future__ import annotations
|
|
|
17
17
|
|
|
18
18
|
from typing import TYPE_CHECKING, Any
|
|
19
19
|
|
|
20
|
+
from agentshim.core.auth import LoginSpec
|
|
20
21
|
from agentshim.core.errors import ProviderCapabilityError, SessionResumeError
|
|
21
22
|
from agentshim.core.mcp import (
|
|
22
23
|
HttpMcpServer,
|
|
@@ -52,6 +53,8 @@ PROFILE = ProviderProfile(
|
|
|
52
53
|
models=("google-vertex/gemini-3-pro-preview",),
|
|
53
54
|
display_name="opencode",
|
|
54
55
|
binary="opencode",
|
|
56
|
+
# ``opencode auth login`` prompts for a provider and its credential.
|
|
57
|
+
login=LoginSpec(argv=("auth", "login"), needs_terminal=True),
|
|
55
58
|
supports_resume=True,
|
|
56
59
|
# ``run --session <id>`` continues a stored session.
|
|
57
60
|
supports_reasoning_effort=False,
|
|
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
|
|
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
|
|
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
|