agentshim 0.18.3__tar.gz → 0.20.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.18.3 → agentshim-0.20.0}/CHANGELOG.md +29 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/PKG-INFO +1 -1
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/__init__.py +29 -14
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/agent.py +6 -4
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/core/__init__.py +7 -7
- agentshim-0.20.0/agentshim/core/models.py +153 -0
- agentshim-0.20.0/agentshim/core/plugin.py +172 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/core/pricing.py +2 -159
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/core/usage.py +1 -1
- agentshim-0.20.0/agentshim/execution/readiness.py +76 -0
- agentshim-0.20.0/agentshim/models.py +91 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/oneshot.py +9 -2
- agentshim-0.20.0/agentshim/pricing.py +109 -0
- agentshim-0.20.0/agentshim/probe.py +73 -0
- agentshim-0.20.0/agentshim/providers/__init__.py +139 -0
- agentshim-0.20.0/agentshim/providers/_testing.py +25 -0
- agentshim-0.20.0/agentshim/providers/catalog.py +150 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/claude/__init__.py +2 -0
- agentshim-0.20.0/agentshim/providers/claude/model_data.py +85 -0
- agentshim-0.20.0/agentshim/providers/claude/plugin.py +32 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/claude/provider.py +16 -10
- agentshim-0.20.0/agentshim/providers/claude/testing.py +48 -0
- agentshim-0.20.0/agentshim/providers/claude/tools.py +113 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/codex/app_server/transport.py +2 -2
- agentshim-0.20.0/agentshim/providers/codex/model_data.py +96 -0
- agentshim-0.20.0/agentshim/providers/codex/plugin.py +32 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/codex/provider.py +1 -1
- agentshim-0.20.0/agentshim/providers/codex/testing.py +37 -0
- agentshim-0.20.0/agentshim/providers/copilot/plugin.py +24 -0
- agentshim-0.20.0/agentshim/providers/copilot/testing.py +28 -0
- agentshim-0.20.0/agentshim/providers/gemini/plugin.py +24 -0
- agentshim-0.20.0/agentshim/providers/gemini/testing.py +42 -0
- agentshim-0.20.0/agentshim/providers/opencode/model_data.py +28 -0
- agentshim-0.20.0/agentshim/providers/opencode/plugin.py +27 -0
- agentshim-0.20.0/agentshim/providers/opencode/testing.py +41 -0
- agentshim-0.20.0/agentshim/providers/static_plugin.py +162 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/runtime.py +17 -3
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/testing/__init__.py +39 -89
- agentshim-0.20.0/agentshim/testing/probe.py +34 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/pyproject.toml +1 -1
- agentshim-0.18.3/agentshim/core/models.py +0 -273
- agentshim-0.18.3/agentshim/probe.py +0 -117
- agentshim-0.18.3/agentshim/providers/__init__.py +0 -273
- agentshim-0.18.3/agentshim/testing/probe.py +0 -63
- {agentshim-0.18.3 → agentshim-0.20.0}/.gitignore +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/LICENSE +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/README.md +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/core/_files.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/core/auth.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/core/checkpoints.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/core/clock.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/core/conversation.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/core/env.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/core/errors.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/core/events.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/core/home.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/core/ids.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/core/mcp.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/core/permissions.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/core/profile.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/core/provider.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/core/schema.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/core/session_policy.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/core/skills.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/core/status.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/core/stream.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/core/turn.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/execution/__init__.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/execution/confinement.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/execution/docker.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/execution/executor.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/execution/host.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/execution/process.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/execution/transform.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/claude/events.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/claude/failures.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/claude/hooks/__init__.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/claude/hooks/confine_reads.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/claude/parser.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/claude/probe.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/claude/rate_limits.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/claude/sandbox.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/claude/scripted.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/claude/stream_argv.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/claude/stream_transport.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/claude/user_hooks.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/codex/__init__.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/codex/_toml.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/codex/app_server/__init__.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/codex/app_server/_wire.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/codex/app_server/approvals.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/codex/app_server/channel.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/codex/app_server/errors.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/codex/app_server/items.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/codex/app_server/mcp.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/codex/app_server/permissions.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/codex/app_server/profile.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/codex/app_server/protocol.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/codex/app_server/rate_limits.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/codex/app_server/usage.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/codex/events.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/codex/failures.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/codex/mcp_scope.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/codex/parser.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/codex/probe.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/codex/rules.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/codex/sandbox.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/codex/scripted.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/codex/skills.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/copilot/__init__.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/copilot/events.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/copilot/parser.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/copilot/probe.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/copilot/provider.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/copilot/scripted.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/gemini/__init__.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/gemini/events.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/gemini/parser.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/gemini/probe.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/gemini/provider.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/gemini/scripted.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/opencode/__init__.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/opencode/events.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/opencode/parser.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/opencode/probe.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/opencode/provider.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/providers/opencode/scripted.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/py.typed +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/session.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/testing/claude_stream.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/testing/clock.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/testing/codex_app_server.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/testing/confinement.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/testing/contracts.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/testing/conversation.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/agentshim/testing/process.py +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/tests/fixtures/claude_stream/README.md +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/tests/fixtures/codex/README.md +0 -0
- {agentshim-0.18.3 → agentshim-0.20.0}/tests/fixtures/codex_app_server/README.md +0 -0
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.20.0 (2026-10-18)
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `ClaudeProvider(tools=ClaudeToolPolicy(...))`: run Claude Code with only the built-in tools
|
|
8
|
+
and allow rules the policy names, under `--permission-mode dontAsk`, instead of the
|
|
9
|
+
always-on `--dangerously-skip-permissions`. `ClaudeToolPolicy.only(["mcp__rs"])` gives a
|
|
10
|
+
turn no built-in tools and lets it call only that MCP server; `builtin=()` renders
|
|
11
|
+
`--tools ""`, and each allow rule is its own `--allowedTools` argument so a scoped rule
|
|
12
|
+
such as `Bash(git log:*)` arrives whole. A turn with an output schema also allows
|
|
13
|
+
`StructuredOutput`. Without a policy the argv is unchanged.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- Provider integrations are resolved through an extensible `ProviderCatalog`; `CliAgent`,
|
|
18
|
+
`Agent`, `probe_provider` and the consumer test helpers take `catalog=` (#70).
|
|
19
|
+
|
|
20
|
+
## 0.19.0 (2026-10-17)
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- `ModelSpec` carries what a model picker needs: `display_name` (`"Opus 5.5"`), `family`
|
|
25
|
+
(`"Claude"`, `"GPT"`, `"Gemini"`), and a within-family ladder, `tier` (a rank, `1` the
|
|
26
|
+
smallest) with `tier_name` (`"Opus"`). Haiku < Sonnet < Opus < Fable; Luna < Terra < Sol <
|
|
27
|
+
Astra. All four fields default to `None`, so a caller-built `ModelSpec` keeps working;
|
|
28
|
+
every shipped entry has a name and family, and every one but `gpt-5.5` a tier.
|
|
29
|
+
Read them with `model_spec(provider, model)`. A provider's readable name was already
|
|
30
|
+
`ProviderProfile.display_name`.
|
|
31
|
+
|
|
3
32
|
## 0.18.3 (2026-10-16)
|
|
4
33
|
|
|
5
34
|
### Fixed
|
|
@@ -29,6 +29,7 @@ from .core import (
|
|
|
29
29
|
CompositeEventHandler,
|
|
30
30
|
ConfigFileInstallation,
|
|
31
31
|
ConfigScope,
|
|
32
|
+
ConnectionOptions,
|
|
32
33
|
ConsoleEventHandler,
|
|
33
34
|
ContextualStreamParser,
|
|
34
35
|
Continuity,
|
|
@@ -56,9 +57,6 @@ from .core import (
|
|
|
56
57
|
McpScope,
|
|
57
58
|
McpServer,
|
|
58
59
|
McpTransport,
|
|
59
|
-
ModelPricing,
|
|
60
|
-
ModelSpec,
|
|
61
|
-
ModelTable,
|
|
62
60
|
NativeMode,
|
|
63
61
|
NativePermissions,
|
|
64
62
|
NoopInstallation,
|
|
@@ -69,7 +67,7 @@ from .core import (
|
|
|
69
67
|
ParsedTurn,
|
|
70
68
|
ParserContext,
|
|
71
69
|
PolicyConfig,
|
|
72
|
-
|
|
70
|
+
PricingContext,
|
|
73
71
|
ProcessClosedError,
|
|
74
72
|
Provider,
|
|
75
73
|
ProviderCapabilityError,
|
|
@@ -126,22 +124,16 @@ from .core import (
|
|
|
126
124
|
UsageReport,
|
|
127
125
|
compact_json,
|
|
128
126
|
compose_event_handlers,
|
|
129
|
-
context_window,
|
|
130
|
-
cost_usd,
|
|
131
|
-
default_models,
|
|
132
|
-
default_pricing,
|
|
133
127
|
dialect_problems,
|
|
134
128
|
host_auth_plan,
|
|
135
129
|
install_config_file,
|
|
136
130
|
interactive_env,
|
|
137
131
|
materialize,
|
|
138
|
-
model_spec,
|
|
139
132
|
normalize,
|
|
140
133
|
normalized_usage,
|
|
141
134
|
parse_json_object,
|
|
142
135
|
plan_auth,
|
|
143
136
|
prepare_config_home,
|
|
144
|
-
price_for,
|
|
145
137
|
)
|
|
146
138
|
from .execution import (
|
|
147
139
|
CallbackCommandStreamSink,
|
|
@@ -164,10 +156,26 @@ from .execution import (
|
|
|
164
156
|
TransformingExecutor,
|
|
165
157
|
confine,
|
|
166
158
|
)
|
|
159
|
+
from .models import ModelSpec, ModelTable, context_window, default_models, model_spec
|
|
167
160
|
from .oneshot import OneShotTransport
|
|
161
|
+
from .pricing import ModelPricing, PricingTable, cost_usd, default_pricing, price_for
|
|
168
162
|
from .probe import probe_provider
|
|
169
|
-
from .providers import
|
|
170
|
-
|
|
163
|
+
from .providers import (
|
|
164
|
+
DEFAULT_CATALOG,
|
|
165
|
+
ProviderCatalog,
|
|
166
|
+
ProviderPlugin,
|
|
167
|
+
ProviderTestSupport,
|
|
168
|
+
get_provider,
|
|
169
|
+
provider_names,
|
|
170
|
+
stream_provider_names,
|
|
171
|
+
)
|
|
172
|
+
from .providers.claude import (
|
|
173
|
+
ClaudeHook,
|
|
174
|
+
ClaudeProvider,
|
|
175
|
+
ClaudeStreamTransport,
|
|
176
|
+
ClaudeToolPolicy,
|
|
177
|
+
SandboxConfig,
|
|
178
|
+
)
|
|
171
179
|
from .providers.codex import CodexProvider, CodexSandboxConfig
|
|
172
180
|
from .providers.codex.app_server import CodexAppServerTransport
|
|
173
181
|
from .providers.copilot import CopilotProvider
|
|
@@ -176,9 +184,9 @@ from .providers.opencode import OpencodeProvider
|
|
|
176
184
|
from .runtime import Agent
|
|
177
185
|
from .session import Session, Turn, TurnTicket, map_mcp_servers
|
|
178
186
|
|
|
179
|
-
__version__ = "0.
|
|
187
|
+
__version__ = "0.20.0"
|
|
180
188
|
|
|
181
|
-
__all__ = [
|
|
189
|
+
__all__ = [ # noqa: RUF022 - public API test uses Python's case-sensitive order
|
|
182
190
|
"Agent",
|
|
183
191
|
"AgentEvent",
|
|
184
192
|
"AgentEventHandler",
|
|
@@ -198,6 +206,7 @@ __all__ = [
|
|
|
198
206
|
"ClaudeHook",
|
|
199
207
|
"ClaudeProvider",
|
|
200
208
|
"ClaudeStreamTransport",
|
|
209
|
+
"ClaudeToolPolicy",
|
|
201
210
|
"CliAgent",
|
|
202
211
|
"CliCheckError",
|
|
203
212
|
"CliExitError",
|
|
@@ -216,6 +225,7 @@ __all__ = [
|
|
|
216
225
|
"ConfigFileInstallation",
|
|
217
226
|
"ConfigScope",
|
|
218
227
|
"Confinement",
|
|
228
|
+
"ConnectionOptions",
|
|
219
229
|
"ConsoleEventHandler",
|
|
220
230
|
"ContextualStreamParser",
|
|
221
231
|
"Continuity",
|
|
@@ -225,6 +235,7 @@ __all__ = [
|
|
|
225
235
|
"CopilotProvider",
|
|
226
236
|
"CredentialKind",
|
|
227
237
|
"CredentialSource",
|
|
238
|
+
"DEFAULT_CATALOG",
|
|
228
239
|
"DockerExecConfinement",
|
|
229
240
|
"EndpointSpec",
|
|
230
241
|
"EnvCredential",
|
|
@@ -264,6 +275,7 @@ __all__ = [
|
|
|
264
275
|
"ParsedTurn",
|
|
265
276
|
"ParserContext",
|
|
266
277
|
"PolicyConfig",
|
|
278
|
+
"PricingContext",
|
|
267
279
|
"PricingTable",
|
|
268
280
|
"Process",
|
|
269
281
|
"ProcessClosedError",
|
|
@@ -271,9 +283,12 @@ __all__ = [
|
|
|
271
283
|
"ProcessOutput",
|
|
272
284
|
"Provider",
|
|
273
285
|
"ProviderCapabilityError",
|
|
286
|
+
"ProviderCatalog",
|
|
274
287
|
"ProviderError",
|
|
288
|
+
"ProviderPlugin",
|
|
275
289
|
"ProviderProfile",
|
|
276
290
|
"ProviderStatus",
|
|
291
|
+
"ProviderTestSupport",
|
|
277
292
|
"ProviderUsage",
|
|
278
293
|
"RandomIds",
|
|
279
294
|
"RateLimitStatus",
|
|
@@ -38,7 +38,7 @@ from agentshim.core.skills import SkillTracker
|
|
|
38
38
|
from agentshim.core.turn import TurnRequest, TurnResult, coerce_request
|
|
39
39
|
from agentshim.execution.executor import CommandRequest
|
|
40
40
|
from agentshim.execution.host import HostCommandExecutor
|
|
41
|
-
from agentshim.providers import get_provider
|
|
41
|
+
from agentshim.providers import DEFAULT_CATALOG, ProviderCatalog, get_provider
|
|
42
42
|
|
|
43
43
|
if TYPE_CHECKING:
|
|
44
44
|
from collections.abc import Callable, Mapping, Sequence
|
|
@@ -51,14 +51,14 @@ if TYPE_CHECKING:
|
|
|
51
51
|
from agentshim.execution.executor import CommandExecutor, CommandHandle, CommandResult
|
|
52
52
|
|
|
53
53
|
|
|
54
|
-
def _resolve_provider(provider: str | Provider) -> Provider:
|
|
54
|
+
def _resolve_provider(provider: str | Provider, catalog: ProviderCatalog) -> Provider:
|
|
55
55
|
"""Resolve a provider name to a provider instance.
|
|
56
56
|
|
|
57
57
|
This module sits above ``providers`` precisely so the documented
|
|
58
58
|
``CliAgent("claude")`` spelling can be a plain module-level import.
|
|
59
59
|
"""
|
|
60
60
|
if isinstance(provider, str):
|
|
61
|
-
return get_provider(provider)
|
|
61
|
+
return get_provider(provider, catalog=catalog)
|
|
62
62
|
return provider
|
|
63
63
|
|
|
64
64
|
|
|
@@ -82,6 +82,7 @@ class CliAgent:
|
|
|
82
82
|
event_handlers: Sequence[AgentEventHandler] = (),
|
|
83
83
|
check_timeout: float = 15.0,
|
|
84
84
|
log: Callable[[str], None] | None = None,
|
|
85
|
+
catalog: ProviderCatalog = DEFAULT_CATALOG,
|
|
85
86
|
) -> None:
|
|
86
87
|
"""Configure a provider CLI and prove it is usable before any turn runs.
|
|
87
88
|
|
|
@@ -104,8 +105,9 @@ class CliAgent:
|
|
|
104
105
|
check_timeout: Seconds allowed for the one-off health check. It is
|
|
105
106
|
not a budget for a turn.
|
|
106
107
|
log: Sink for agentshim's own progress lines; dropped by default.
|
|
108
|
+
catalog: Provider names and implementations to use; defaults to the built-ins.
|
|
107
109
|
"""
|
|
108
|
-
self.provider: Provider = _resolve_provider(provider)
|
|
110
|
+
self.provider: Provider = _resolve_provider(provider, catalog)
|
|
109
111
|
self.profile: ProviderProfile = self.provider.profile
|
|
110
112
|
self.model = model
|
|
111
113
|
self.executor: CommandExecutor = executor if executor is not None else HostCommandExecutor()
|
|
@@ -95,9 +95,10 @@ from .mcp import (
|
|
|
95
95
|
StdioMcpServer,
|
|
96
96
|
install_config_file,
|
|
97
97
|
)
|
|
98
|
-
from .models import ModelSpec, ModelTable
|
|
98
|
+
from .models import ModelSpec, ModelTable
|
|
99
99
|
from .permissions import ApprovalPolicy, NativeMode, NativePermissions
|
|
100
|
-
from .
|
|
100
|
+
from .plugin import ConnectionOptions, PricingContext, ProviderPlugin, ProviderTestSupport
|
|
101
|
+
from .pricing import ModelPricing, PricingTable, cost_usd
|
|
101
102
|
from .profile import (
|
|
102
103
|
ConfigScope,
|
|
103
104
|
McpMechanism,
|
|
@@ -148,6 +149,7 @@ __all__ = [
|
|
|
148
149
|
"CompositeEventHandler",
|
|
149
150
|
"ConfigFileInstallation",
|
|
150
151
|
"ConfigScope",
|
|
152
|
+
"ConnectionOptions",
|
|
151
153
|
"ConsoleEventHandler",
|
|
152
154
|
"ContextualStreamParser",
|
|
153
155
|
"Continuity",
|
|
@@ -188,13 +190,16 @@ __all__ = [
|
|
|
188
190
|
"ParsedTurn",
|
|
189
191
|
"ParserContext",
|
|
190
192
|
"PolicyConfig",
|
|
193
|
+
"PricingContext",
|
|
191
194
|
"PricingTable",
|
|
192
195
|
"ProcessClosedError",
|
|
193
196
|
"Provider",
|
|
194
197
|
"ProviderCapabilityError",
|
|
195
198
|
"ProviderError",
|
|
199
|
+
"ProviderPlugin",
|
|
196
200
|
"ProviderProfile",
|
|
197
201
|
"ProviderStatus",
|
|
202
|
+
"ProviderTestSupport",
|
|
198
203
|
"ProviderUsage",
|
|
199
204
|
"RandomIds",
|
|
200
205
|
"RateLimitStatus",
|
|
@@ -245,20 +250,15 @@ __all__ = [
|
|
|
245
250
|
"UsageReport",
|
|
246
251
|
"compact_json",
|
|
247
252
|
"compose_event_handlers",
|
|
248
|
-
"context_window",
|
|
249
253
|
"cost_usd",
|
|
250
|
-
"default_models",
|
|
251
|
-
"default_pricing",
|
|
252
254
|
"dialect_problems",
|
|
253
255
|
"host_auth_plan",
|
|
254
256
|
"install_config_file",
|
|
255
257
|
"interactive_env",
|
|
256
258
|
"materialize",
|
|
257
|
-
"model_spec",
|
|
258
259
|
"normalize",
|
|
259
260
|
"normalized_usage",
|
|
260
261
|
"parse_json_object",
|
|
261
262
|
"plan_auth",
|
|
262
263
|
"prepare_config_home",
|
|
263
|
-
"price_for",
|
|
264
264
|
]
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""Provider-independent model facts and tables."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import datetime as dt
|
|
6
|
+
from dataclasses import dataclass, field, replace
|
|
7
|
+
from typing import TYPE_CHECKING, cast
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from collections.abc import Iterable
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class ModelSpec:
|
|
15
|
+
"""One model's context window, with where and when it was read.
|
|
16
|
+
|
|
17
|
+
Attributes:
|
|
18
|
+
vendor: Who serves the model (``"openai"``, ``"anthropic"``).
|
|
19
|
+
model: The model id as the vendor names it.
|
|
20
|
+
context_window: The model's context window in tokens (input and
|
|
21
|
+
output together), at the vendor's default for that id.
|
|
22
|
+
source: URL of the official page the number was read from.
|
|
23
|
+
checked: Date the number was last checked against ``source``.
|
|
24
|
+
display_name: A readable name for the model (``"Opus 5.5"``), or
|
|
25
|
+
``None`` for a caller-built entry that has none.
|
|
26
|
+
family: The model line the model belongs to (``"Claude"``, ``"GPT"``,
|
|
27
|
+
``"Gemini"``); set together with ``display_name``.
|
|
28
|
+
tier: The model's rank within its family, ``1`` the smallest and
|
|
29
|
+
cheapest; versions of one tier share a rank. ``None`` for a model
|
|
30
|
+
that sits on no tier ladder.
|
|
31
|
+
tier_name: The tier's readable name (``"Opus"``); set together with
|
|
32
|
+
``tier``.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
vendor: str
|
|
36
|
+
model: str
|
|
37
|
+
context_window: int
|
|
38
|
+
source: str
|
|
39
|
+
checked: dt.date
|
|
40
|
+
display_name: str | None = None
|
|
41
|
+
family: str | None = None
|
|
42
|
+
tier: int | None = None
|
|
43
|
+
tier_name: str | None = None
|
|
44
|
+
|
|
45
|
+
def __post_init__(self) -> None:
|
|
46
|
+
"""Reject a missing name, source or date, and a window that is not a positive int."""
|
|
47
|
+
for name in ("vendor", "model"):
|
|
48
|
+
value: object = getattr(self, name)
|
|
49
|
+
if not isinstance(value, str) or not value.strip():
|
|
50
|
+
msg = f"{name} must be a non-empty string"
|
|
51
|
+
raise ValueError(msg)
|
|
52
|
+
source = cast("object", self.source)
|
|
53
|
+
if not isinstance(source, str) or not source.startswith("https://"):
|
|
54
|
+
msg = f"{self.vendor}/{self.model}: source must be an https:// URL"
|
|
55
|
+
raise ValueError(msg)
|
|
56
|
+
checked = cast("object", self.checked)
|
|
57
|
+
if not isinstance(checked, dt.date) or isinstance(checked, dt.datetime):
|
|
58
|
+
msg = f"{self.vendor}/{self.model}: checked must be a datetime.date"
|
|
59
|
+
raise TypeError(msg)
|
|
60
|
+
window = cast("object", self.context_window)
|
|
61
|
+
if isinstance(window, bool) or not isinstance(window, int) or window <= 0:
|
|
62
|
+
msg = f"{self.vendor}/{self.model}: context_window must be a positive int"
|
|
63
|
+
raise ValueError(msg)
|
|
64
|
+
self._check_presentation()
|
|
65
|
+
|
|
66
|
+
def _check_presentation(self) -> None:
|
|
67
|
+
"""Reject a half-set name pair or tier pair, and a tier that is not a positive int."""
|
|
68
|
+
for first, second in (("display_name", "family"), ("tier", "tier_name")):
|
|
69
|
+
if (getattr(self, first) is None) != (getattr(self, second) is None):
|
|
70
|
+
msg = f"{self.vendor}/{self.model}: {first} and {second} are set together"
|
|
71
|
+
raise ValueError(msg)
|
|
72
|
+
for name in ("display_name", "family", "tier_name"):
|
|
73
|
+
value: object = getattr(self, name)
|
|
74
|
+
if value is not None and (not isinstance(value, str) or not value.strip()):
|
|
75
|
+
msg = f"{self.vendor}/{self.model}: {name} must be a non-empty string"
|
|
76
|
+
raise ValueError(msg)
|
|
77
|
+
tier = cast("object", self.tier)
|
|
78
|
+
if tier is not None and (isinstance(tier, bool) or not isinstance(tier, int) or tier < 1):
|
|
79
|
+
msg = f"{self.vendor}/{self.model}: tier must be a positive int"
|
|
80
|
+
raise ValueError(msg)
|
|
81
|
+
if self.tier is not None and self.family is None:
|
|
82
|
+
msg = f"{self.vendor}/{self.model}: tier needs a family"
|
|
83
|
+
raise ValueError(msg)
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def key(self) -> tuple[str, str]:
|
|
87
|
+
"""The ``(vendor, model)`` this entry describes."""
|
|
88
|
+
return (self.vendor, self.model)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@dataclass(frozen=True)
|
|
92
|
+
class ModelTable:
|
|
93
|
+
"""A versioned set of :class:`ModelSpec` entries.
|
|
94
|
+
|
|
95
|
+
Attributes:
|
|
96
|
+
version: Names this set of facts; change it whenever a fact changes.
|
|
97
|
+
last_updated: Date the table as a whole was last updated. No entry
|
|
98
|
+
may have been checked later.
|
|
99
|
+
entries: One entry per ``(vendor, model)``.
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
version: str
|
|
103
|
+
last_updated: dt.date
|
|
104
|
+
entries: tuple[ModelSpec, ...] = field(default=())
|
|
105
|
+
|
|
106
|
+
def __post_init__(self) -> None:
|
|
107
|
+
"""Reject a missing version, duplicate keys, and entries checked after ``last_updated``."""
|
|
108
|
+
version = cast("object", self.version)
|
|
109
|
+
if not isinstance(version, str) or not version.strip():
|
|
110
|
+
msg = "version must be a non-empty string"
|
|
111
|
+
raise ValueError(msg)
|
|
112
|
+
object.__setattr__(self, "entries", tuple(self.entries))
|
|
113
|
+
seen: set[tuple[str, str]] = set()
|
|
114
|
+
for entry in self.entries:
|
|
115
|
+
if not isinstance(cast("object", entry), ModelSpec):
|
|
116
|
+
msg = f"entries must be ModelSpec, got {entry!r}"
|
|
117
|
+
raise TypeError(msg)
|
|
118
|
+
if entry.key in seen:
|
|
119
|
+
msg = f"duplicate model entry for {entry.vendor}/{entry.model}"
|
|
120
|
+
raise ValueError(msg)
|
|
121
|
+
seen.add(entry.key)
|
|
122
|
+
if entry.checked > self.last_updated:
|
|
123
|
+
msg = (
|
|
124
|
+
f"{entry.vendor}/{entry.model} was checked {entry.checked}, "
|
|
125
|
+
f"after the table's last_updated {self.last_updated}"
|
|
126
|
+
)
|
|
127
|
+
raise ValueError(msg)
|
|
128
|
+
|
|
129
|
+
def get(self, vendor: str, model: str) -> ModelSpec | None:
|
|
130
|
+
"""The entry for exactly ``(vendor, model)``, or ``None``."""
|
|
131
|
+
for entry in self.entries:
|
|
132
|
+
if entry.key == (vendor, model):
|
|
133
|
+
return entry
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
def with_entries(
|
|
137
|
+
self,
|
|
138
|
+
entries: Iterable[ModelSpec],
|
|
139
|
+
*,
|
|
140
|
+
version: str,
|
|
141
|
+
last_updated: dt.date | None = None,
|
|
142
|
+
) -> ModelTable:
|
|
143
|
+
"""A new table with *entries* added, replacing any with the same key."""
|
|
144
|
+
added = tuple(entries)
|
|
145
|
+
by_key = {entry.key: entry for entry in self.entries}
|
|
146
|
+
by_key.update({entry.key: entry for entry in added})
|
|
147
|
+
latest = max([self.last_updated, *(entry.checked for entry in added)])
|
|
148
|
+
return replace(
|
|
149
|
+
self,
|
|
150
|
+
version=version,
|
|
151
|
+
last_updated=last_updated or latest,
|
|
152
|
+
entries=tuple(by_key.values()),
|
|
153
|
+
)
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""Method-based contract implemented by built-in and third-party plugins."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
import datetime as dt
|
|
10
|
+
from collections.abc import Callable, Mapping, Sequence
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from agentshim.execution.executor import CommandExecutor, CommandRequest
|
|
14
|
+
|
|
15
|
+
from .clock import Clock
|
|
16
|
+
from .conversation import Transport, TransportKind
|
|
17
|
+
from .errors import FailureKind
|
|
18
|
+
from .ids import IdAllocator
|
|
19
|
+
from .models import ModelSpec, ModelTable
|
|
20
|
+
from .pricing import ModelPricing, PricingTable
|
|
21
|
+
from .profile import ProviderProfile
|
|
22
|
+
from .provider import Provider
|
|
23
|
+
from .status import AuthState, ProviderStatus
|
|
24
|
+
from .usage import TokenUsage
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ScriptedLines(Protocol):
|
|
28
|
+
"""Build one turn of a provider's real stdout for consumer tests."""
|
|
29
|
+
|
|
30
|
+
def __call__( # noqa: PLR0913
|
|
31
|
+
self,
|
|
32
|
+
*,
|
|
33
|
+
text: str = "",
|
|
34
|
+
session_id: str | None = None,
|
|
35
|
+
usage: TokenUsage | None = None,
|
|
36
|
+
tool_calls: Sequence[tuple[str, Mapping[str, Any], str]] = (),
|
|
37
|
+
structured_output: object | None = None,
|
|
38
|
+
skills_offered: Sequence[str] | None = None,
|
|
39
|
+
skills_invoked: Sequence[str] = (),
|
|
40
|
+
) -> list[str]:
|
|
41
|
+
"""Return stdout lines in this provider's stream format."""
|
|
42
|
+
...
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class ResumeFailureLines(Protocol):
|
|
46
|
+
"""Build the stream and exit status of a failed resume."""
|
|
47
|
+
|
|
48
|
+
def __call__(
|
|
49
|
+
self, *, session_id: str | None = None
|
|
50
|
+
) -> tuple[Sequence[str], Sequence[str], int]:
|
|
51
|
+
"""Return stdout, stderr, and exit status."""
|
|
52
|
+
...
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class FailureLines(Protocol):
|
|
56
|
+
"""Build the stream and exit status of a classified failure."""
|
|
57
|
+
|
|
58
|
+
def __call__(
|
|
59
|
+
self, kind: FailureKind, *, session_id: str | None = None
|
|
60
|
+
) -> tuple[Sequence[str], Sequence[str], int]:
|
|
61
|
+
"""Return stdout, stderr, and exit status."""
|
|
62
|
+
...
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class StreamTransportFactory(Protocol):
|
|
66
|
+
"""Legacy type for provider-internal stream constructors."""
|
|
67
|
+
|
|
68
|
+
def __call__( # noqa: PLR0913
|
|
69
|
+
self,
|
|
70
|
+
*,
|
|
71
|
+
executor: CommandExecutor,
|
|
72
|
+
env: Mapping[str, str] | None,
|
|
73
|
+
clock: Clock,
|
|
74
|
+
ids: IdAllocator,
|
|
75
|
+
check_timeout: float,
|
|
76
|
+
log: Callable[[str], None] | None,
|
|
77
|
+
) -> Transport:
|
|
78
|
+
"""Create a stream transport."""
|
|
79
|
+
...
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class ProbeFixture(Protocol):
|
|
83
|
+
"""Build fake CLI output for a readiness command."""
|
|
84
|
+
|
|
85
|
+
def __call__(
|
|
86
|
+
self, args: Sequence[str], *, version: str, auth: AuthState
|
|
87
|
+
) -> tuple[str, str, int]:
|
|
88
|
+
"""Return stdout, stderr, and exit status."""
|
|
89
|
+
...
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dataclass(frozen=True)
|
|
93
|
+
class ProviderTestSupport:
|
|
94
|
+
"""Optional provider-owned adapters for the generic test helpers."""
|
|
95
|
+
|
|
96
|
+
scripted_lines: ScriptedLines
|
|
97
|
+
resume_failure_lines: ResumeFailureLines
|
|
98
|
+
failure_lines: FailureLines | None = None
|
|
99
|
+
installed_mcp_servers: Callable[[CommandRequest, Path], dict[str, dict[str, Any]]] | None = None
|
|
100
|
+
probe_fixture: ProbeFixture | None = None
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@dataclass(frozen=True)
|
|
104
|
+
class ConnectionOptions:
|
|
105
|
+
"""Dependencies for a requested connection mode."""
|
|
106
|
+
|
|
107
|
+
kind: TransportKind
|
|
108
|
+
executor: CommandExecutor | None = None
|
|
109
|
+
env: Mapping[str, str] | None = None
|
|
110
|
+
clock: Clock | None = None
|
|
111
|
+
ids: IdAllocator | None = None
|
|
112
|
+
check_timeout: float = 15.0
|
|
113
|
+
log: Callable[[str], None] | None = None
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@dataclass(frozen=True)
|
|
117
|
+
class PricingContext:
|
|
118
|
+
"""Conditions under which a rate is requested; omitted values mean standard tier."""
|
|
119
|
+
|
|
120
|
+
as_of: dt.date | None = None
|
|
121
|
+
region: str | None = None
|
|
122
|
+
tier: str | None = None
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@runtime_checkable
|
|
126
|
+
class ProviderPlugin(Protocol):
|
|
127
|
+
"""Required methods for every provider; unsupported facts return ``None``."""
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def name(self) -> str:
|
|
131
|
+
"""Stable catalog key."""
|
|
132
|
+
...
|
|
133
|
+
|
|
134
|
+
def describe(self) -> ProviderProfile:
|
|
135
|
+
"""Describe capabilities independently of local installation state."""
|
|
136
|
+
...
|
|
137
|
+
|
|
138
|
+
def connection_modes(self) -> frozenset[TransportKind]:
|
|
139
|
+
"""Declare which connection modes ``connect`` accepts."""
|
|
140
|
+
...
|
|
141
|
+
|
|
142
|
+
def connect(self, options: ConnectionOptions) -> Provider | Transport:
|
|
143
|
+
"""Create the requested one-shot provider or stream transport."""
|
|
144
|
+
...
|
|
145
|
+
|
|
146
|
+
def probe(
|
|
147
|
+
self, executor: CommandExecutor, env: Mapping[str, str], timeout: float
|
|
148
|
+
) -> ProviderStatus:
|
|
149
|
+
"""Check readiness on the supplied executor, without running a model."""
|
|
150
|
+
...
|
|
151
|
+
|
|
152
|
+
def model_info(self, model: str, table: ModelTable | None = None) -> ModelSpec | None:
|
|
153
|
+
"""Resolve context-window facts; *table* overrides built-in facts."""
|
|
154
|
+
...
|
|
155
|
+
|
|
156
|
+
def pricing_info(
|
|
157
|
+
self, model: str, context: PricingContext, table: PricingTable | None = None
|
|
158
|
+
) -> ModelPricing | None:
|
|
159
|
+
"""Resolve a rate for *model* and *context*, or report it unknown."""
|
|
160
|
+
...
|
|
161
|
+
|
|
162
|
+
def known_models(self) -> tuple[ModelSpec, ...]:
|
|
163
|
+
"""Enumerate facts available for a reproducible table snapshot."""
|
|
164
|
+
...
|
|
165
|
+
|
|
166
|
+
def known_prices(self) -> tuple[ModelPricing, ...]:
|
|
167
|
+
"""Enumerate standard-tier prices for a reproducible table snapshot."""
|
|
168
|
+
...
|
|
169
|
+
|
|
170
|
+
def test_support(self) -> ProviderTestSupport | None:
|
|
171
|
+
"""Return consumer-test adapters, if provided."""
|
|
172
|
+
...
|