agentshim 0.15.0__tar.gz → 0.15.2__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.15.0 → agentshim-0.15.2}/CHANGELOG.md +66 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/PKG-INFO +1 -1
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/__init__.py +25 -2
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/core/__init__.py +22 -1
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/core/conversation.py +33 -1
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/core/errors.py +9 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/core/events.py +107 -1
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/core/profile.py +4 -0
- agentshim-0.15.2/agentshim/core/status.py +110 -0
- agentshim-0.15.2/agentshim/probe.py +116 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/providers/__init__.py +78 -1
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/providers/claude/__init__.py +3 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/providers/claude/events.py +23 -1
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/providers/claude/parser.py +26 -5
- agentshim-0.15.2/agentshim/providers/claude/probe.py +46 -0
- agentshim-0.15.2/agentshim/providers/claude/rate_limits.py +81 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/providers/claude/sandbox.py +31 -2
- agentshim-0.15.2/agentshim/providers/claude/stream_argv.py +150 -0
- agentshim-0.15.2/agentshim/providers/claude/stream_transport.py +963 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/providers/codex/app_server/__init__.py +6 -2
- agentshim-0.15.2/agentshim/providers/codex/app_server/approvals.py +152 -0
- agentshim-0.15.2/agentshim/providers/codex/app_server/channel.py +260 -0
- agentshim-0.15.2/agentshim/providers/codex/app_server/errors.py +74 -0
- agentshim-0.15.2/agentshim/providers/codex/app_server/items.py +173 -0
- agentshim-0.15.2/agentshim/providers/codex/app_server/mcp.py +75 -0
- agentshim-0.15.2/agentshim/providers/codex/app_server/permissions.py +105 -0
- agentshim-0.15.2/agentshim/providers/codex/app_server/profile.py +25 -0
- agentshim-0.15.2/agentshim/providers/codex/app_server/rate_limits.py +56 -0
- agentshim-0.15.2/agentshim/providers/codex/app_server/transport.py +1009 -0
- agentshim-0.15.2/agentshim/providers/codex/app_server/usage.py +100 -0
- agentshim-0.15.2/agentshim/providers/codex/probe.py +45 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/providers/codex/provider.py +29 -10
- agentshim-0.15.2/agentshim/providers/copilot/probe.py +10 -0
- agentshim-0.15.2/agentshim/providers/gemini/probe.py +10 -0
- agentshim-0.15.2/agentshim/providers/opencode/probe.py +10 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/runtime.py +55 -8
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/session.py +32 -1
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/testing/__init__.py +85 -0
- agentshim-0.15.2/agentshim/testing/claude_stream.py +616 -0
- agentshim-0.15.2/agentshim/testing/codex_app_server.py +1127 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/testing/contracts.py +94 -2
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/testing/conversation.py +34 -4
- agentshim-0.15.2/agentshim/testing/probe.py +63 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/pyproject.toml +1 -1
- agentshim-0.15.2/tests/fixtures/claude_stream/README.md +18 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/.gitignore +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/LICENSE +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/README.md +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/agent.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/core/_files.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/core/checkpoints.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/core/clock.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/core/env.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/core/home.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/core/ids.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/core/mcp.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/core/permissions.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/core/pricing.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/core/provider.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/core/schema.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/core/session_policy.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/core/skills.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/core/stream.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/core/turn.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/core/usage.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/execution/__init__.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/execution/confinement.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/execution/docker.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/execution/executor.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/execution/host.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/execution/process.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/execution/transform.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/oneshot.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/providers/claude/failures.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/providers/claude/hooks/__init__.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/providers/claude/hooks/confine_reads.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/providers/claude/provider.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/providers/claude/scripted.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/providers/claude/user_hooks.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/providers/codex/__init__.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/providers/codex/_toml.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/providers/codex/app_server/_wire.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/providers/codex/app_server/protocol.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/providers/codex/events.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/providers/codex/failures.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/providers/codex/mcp_scope.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/providers/codex/parser.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/providers/codex/rules.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/providers/codex/sandbox.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/providers/codex/scripted.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/providers/codex/skills.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/providers/copilot/__init__.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/providers/copilot/events.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/providers/copilot/parser.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/providers/copilot/provider.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/providers/copilot/scripted.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/providers/gemini/__init__.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/providers/gemini/events.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/providers/gemini/parser.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/providers/gemini/provider.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/providers/gemini/scripted.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/providers/opencode/__init__.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/providers/opencode/events.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/providers/opencode/parser.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/providers/opencode/provider.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/providers/opencode/scripted.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/py.typed +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/testing/clock.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/testing/confinement.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/agentshim/testing/process.py +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/tests/fixtures/codex/README.md +0 -0
- {agentshim-0.15.0 → agentshim-0.15.2}/tests/fixtures/codex_app_server/README.md +0 -0
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.15.2 (2026-10-09)
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `RateLimitStatus` event (also in `AgentEvent` and `ConsoleEventHandler`):
|
|
8
|
+
the provider's report of one rate-limit window, with `window`, `limit`,
|
|
9
|
+
`used_fraction`, `remaining_fraction`, `resets_at`, `window_minutes`,
|
|
10
|
+
`exhausted` and `raw`. Unstated values are `None`, never zero. Emitted by the
|
|
11
|
+
Claude stream transport and one-shot parser (`rate_limit_event`) and by the
|
|
12
|
+
Codex app-server transport (`account/rateLimits/updated`). Test doubles:
|
|
13
|
+
`ClaudePeerTurn(rate_limit=...)` and the `ReportRateLimits` Codex script step.
|
|
14
|
+
- Provider readiness probe. `probe_provider(name, *, executor, confinement, env,
|
|
15
|
+
timeout)` and `Agent.probe()` return a `ProviderStatus` (`binary_found`,
|
|
16
|
+
`path`, `version`, `auth: AuthState`, `auth_detail`) without running a turn or
|
|
17
|
+
calling a model, through the executor and confinement a turn would use. Claude
|
|
18
|
+
uses `claude auth status`, Codex `codex login status`; Gemini, Copilot and
|
|
19
|
+
opencode have no status command and report `AuthState.UNKNOWN`. A missing
|
|
20
|
+
binary is a result, not an error. Test double `agentshim.testing.probe_executor`.
|
|
21
|
+
- Mid-turn steering. `Session.steer(text)` (thread-safe) sends a message into the
|
|
22
|
+
running turn; `SteerableConversation` is the transport-tier protocol and
|
|
23
|
+
`ProviderProfile.supports_steer` the capability (true for the Claude and Codex
|
|
24
|
+
stream transports). It never queues: `ProviderCapabilityError` when
|
|
25
|
+
unsupported, new `NoRunningTurnError` (a `SessionStateError`) when no turn can
|
|
26
|
+
take it. Events `SteerDelivered`, `SteerConsumed`, `SteerRejected`.
|
|
27
|
+
- Codex maps to `turn/steer`. Claude Code writes a user envelope and runs with
|
|
28
|
+
`--replay-user-messages`; `turn()` still returns one `TurnResult` when the CLI
|
|
29
|
+
queues the message as a follow-on turn (new `steer_grace_s` option).
|
|
30
|
+
- Test doubles: `AwaitSteer` and `CodexScript(steer_refusal=)`,
|
|
31
|
+
`ClaudePeerTurn(injects_steer=)` and `ClaudeStreamPeers(drops_queued_messages=)`,
|
|
32
|
+
`fake_profile(supports_steer=)`, `FakeConversation.steer`, and
|
|
33
|
+
`SteerableConversationContract`.
|
|
34
|
+
|
|
35
|
+
## 0.15.1 (2026-10-09)
|
|
36
|
+
|
|
37
|
+
### Added
|
|
38
|
+
|
|
39
|
+
- `CodexAppServerTransport`: a long-lived `codex app-server` per conversation,
|
|
40
|
+
registered as Codex's stream transport, so
|
|
41
|
+
`Agent("codex", transport=TransportKind.STREAM)` selects it (one-shot stays
|
|
42
|
+
the default). Supports read-only, workspace-write and bypass
|
|
43
|
+
permissions, always-answered server requests, per-turn usage diffing, failure
|
|
44
|
+
classification, interrupt and timeout. Test double `CodexScript` /
|
|
45
|
+
`CodexAppServerPeer` in `agentshim.testing`.
|
|
46
|
+
|
|
3
47
|
## 0.15.0 (2026-10-09)
|
|
4
48
|
|
|
5
49
|
### Added
|
|
@@ -31,6 +75,28 @@
|
|
|
31
75
|
`ConversationContract`, `CheckpointStoreContract`.
|
|
32
76
|
- Docs: [sessions](https://vic-lsh.github.io/agentshim/sessions/).
|
|
33
77
|
|
|
78
|
+
### Added (Claude stream transport)
|
|
79
|
+
|
|
80
|
+
- `ClaudeStreamTransport`: Claude Code over one long-lived
|
|
81
|
+
`claude --input-format stream-json --output-format stream-json` process per
|
|
82
|
+
conversation (no `claude-agent-sdk`). `Agent("claude", transport=TransportKind.STREAM,
|
|
83
|
+
...)` selects it; `TransportKind.ONE_SHOT` stays the default for now.
|
|
84
|
+
Supports `NativeMode.BYPASS` and `NativeMode.WORKSPACE_WRITE` (Claude's bash
|
|
85
|
+
sandbox plus a write-confinement hook for its file tools, no network);
|
|
86
|
+
`READ_ONLY` and network access are rejected with `ProviderCapabilityError`.
|
|
87
|
+
Permission prompts are denied by the CLI (`--permission-prompts none`) and any
|
|
88
|
+
request that arrives anyway is answered at once per `ApprovalPolicy`. A turn
|
|
89
|
+
whose output schema, MCP servers, effort or extra arguments differ from the
|
|
90
|
+
running process restarts it with `--resume` (the conversation id is unchanged).
|
|
91
|
+
- `TransportKind`, `agentshim.providers.get_stream_transport`.
|
|
92
|
+
- Test doubles `ClaudeStreamPeers` / `ClaudeStreamPeer` (a reactive fake CLI
|
|
93
|
+
scripted with `ClaudePeerTurn`, `ClaudeApiError`, `ClaudeCrash`) and
|
|
94
|
+
`ClaudeRecordedPeer` (replays recorded frames), plus recordings under
|
|
95
|
+
`tests/fixtures/claude_stream`.
|
|
96
|
+
- `ClaudeStreamParser(..., cost_baseline_usd=)` and `.total_cost_usd` for
|
|
97
|
+
per-turn cost on a process that reports cumulative totals; `ResultFrame`
|
|
98
|
+
gains `terminal_reason` and `permission_denials`.
|
|
99
|
+
|
|
34
100
|
### Added (process layer)
|
|
35
101
|
|
|
36
102
|
- MIT license (`LICENSE`), declared in the package metadata.
|
|
@@ -15,6 +15,7 @@ from .core import (
|
|
|
15
15
|
ApprovalPolicy,
|
|
16
16
|
ArgvContext,
|
|
17
17
|
AssistantText,
|
|
18
|
+
AuthState,
|
|
18
19
|
Checkpoint,
|
|
19
20
|
CheckpointStore,
|
|
20
21
|
CliCheckError,
|
|
@@ -48,6 +49,7 @@ from .core import (
|
|
|
48
49
|
NativeMode,
|
|
49
50
|
NativePermissions,
|
|
50
51
|
NoopInstallation,
|
|
52
|
+
NoRunningTurnError,
|
|
51
53
|
NullEventHandler,
|
|
52
54
|
OutputSchema,
|
|
53
55
|
OutputSchemaStyle,
|
|
@@ -60,8 +62,10 @@ from .core import (
|
|
|
60
62
|
ProviderCapabilityError,
|
|
61
63
|
ProviderError,
|
|
62
64
|
ProviderProfile,
|
|
65
|
+
ProviderStatus,
|
|
63
66
|
ProviderUsage,
|
|
64
67
|
RandomIds,
|
|
68
|
+
RateLimitStatus,
|
|
65
69
|
RawOutput,
|
|
66
70
|
ReapError,
|
|
67
71
|
Reasoning,
|
|
@@ -84,6 +88,10 @@ from .core import (
|
|
|
84
88
|
SkillTracker,
|
|
85
89
|
Stderr,
|
|
86
90
|
StdioMcpServer,
|
|
91
|
+
SteerableConversation,
|
|
92
|
+
SteerConsumed,
|
|
93
|
+
SteerDelivered,
|
|
94
|
+
SteerRejected,
|
|
87
95
|
StopSignal,
|
|
88
96
|
StreamParser,
|
|
89
97
|
SystemClock,
|
|
@@ -93,6 +101,7 @@ from .core import (
|
|
|
93
101
|
ToolResult,
|
|
94
102
|
ToolTracker,
|
|
95
103
|
Transport,
|
|
104
|
+
TransportKind,
|
|
96
105
|
TurnCancelledError,
|
|
97
106
|
TurnFailedError,
|
|
98
107
|
TurnInterrupted,
|
|
@@ -136,16 +145,18 @@ from .execution import (
|
|
|
136
145
|
confine,
|
|
137
146
|
)
|
|
138
147
|
from .oneshot import OneShotTransport
|
|
148
|
+
from .probe import probe_provider
|
|
139
149
|
from .providers import get_provider, provider_names
|
|
140
|
-
from .providers.claude import ClaudeHook, ClaudeProvider, SandboxConfig
|
|
150
|
+
from .providers.claude import ClaudeHook, ClaudeProvider, ClaudeStreamTransport, SandboxConfig
|
|
141
151
|
from .providers.codex import CodexProvider, CodexSandboxConfig
|
|
152
|
+
from .providers.codex.app_server import CodexAppServerTransport
|
|
142
153
|
from .providers.copilot import CopilotProvider
|
|
143
154
|
from .providers.gemini import GeminiProvider
|
|
144
155
|
from .providers.opencode import OpencodeProvider
|
|
145
156
|
from .runtime import Agent
|
|
146
157
|
from .session import Session, Turn, TurnTicket, map_mcp_servers
|
|
147
158
|
|
|
148
|
-
__version__ = "0.15.
|
|
159
|
+
__version__ = "0.15.2"
|
|
149
160
|
|
|
150
161
|
__all__ = [
|
|
151
162
|
"Agent",
|
|
@@ -157,17 +168,20 @@ __all__ = [
|
|
|
157
168
|
"ApprovalPolicy",
|
|
158
169
|
"ArgvContext",
|
|
159
170
|
"AssistantText",
|
|
171
|
+
"AuthState",
|
|
160
172
|
"CallbackCommandStreamSink",
|
|
161
173
|
"Checkpoint",
|
|
162
174
|
"CheckpointStore",
|
|
163
175
|
"ClaudeHook",
|
|
164
176
|
"ClaudeProvider",
|
|
177
|
+
"ClaudeStreamTransport",
|
|
165
178
|
"CliAgent",
|
|
166
179
|
"CliCheckError",
|
|
167
180
|
"CliExitError",
|
|
168
181
|
"CliNotFoundError",
|
|
169
182
|
"CliTimeoutError",
|
|
170
183
|
"Clock",
|
|
184
|
+
"CodexAppServerTransport",
|
|
171
185
|
"CodexProvider",
|
|
172
186
|
"CodexSandboxConfig",
|
|
173
187
|
"CommandExecutor",
|
|
@@ -206,6 +220,7 @@ __all__ = [
|
|
|
206
220
|
"ModelPricing",
|
|
207
221
|
"NativeMode",
|
|
208
222
|
"NativePermissions",
|
|
223
|
+
"NoRunningTurnError",
|
|
209
224
|
"NoopInstallation",
|
|
210
225
|
"NullEventHandler",
|
|
211
226
|
"NullSink",
|
|
@@ -225,8 +240,10 @@ __all__ = [
|
|
|
225
240
|
"ProviderCapabilityError",
|
|
226
241
|
"ProviderError",
|
|
227
242
|
"ProviderProfile",
|
|
243
|
+
"ProviderStatus",
|
|
228
244
|
"ProviderUsage",
|
|
229
245
|
"RandomIds",
|
|
246
|
+
"RateLimitStatus",
|
|
230
247
|
"RawOutput",
|
|
231
248
|
"ReapError",
|
|
232
249
|
"Reasoning",
|
|
@@ -254,6 +271,10 @@ __all__ = [
|
|
|
254
271
|
"StderrLine",
|
|
255
272
|
"StdioMcpServer",
|
|
256
273
|
"StdoutLine",
|
|
274
|
+
"SteerConsumed",
|
|
275
|
+
"SteerDelivered",
|
|
276
|
+
"SteerRejected",
|
|
277
|
+
"SteerableConversation",
|
|
257
278
|
"StopSignal",
|
|
258
279
|
"StreamParser",
|
|
259
280
|
"SystemClock",
|
|
@@ -264,6 +285,7 @@ __all__ = [
|
|
|
264
285
|
"ToolTracker",
|
|
265
286
|
"TransformingExecutor",
|
|
266
287
|
"Transport",
|
|
288
|
+
"TransportKind",
|
|
267
289
|
"Turn",
|
|
268
290
|
"TurnCancelledError",
|
|
269
291
|
"TurnFailedError",
|
|
@@ -290,5 +312,6 @@ __all__ = [
|
|
|
290
312
|
"parse_json_object",
|
|
291
313
|
"prepare_config_home",
|
|
292
314
|
"price_for",
|
|
315
|
+
"probe_provider",
|
|
293
316
|
"provider_names",
|
|
294
317
|
]
|
|
@@ -9,7 +9,13 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
from .checkpoints import Checkpoint, CheckpointStore, InMemoryCheckpointStore
|
|
11
11
|
from .clock import Clock, StopSignal, SystemClock
|
|
12
|
-
from .conversation import
|
|
12
|
+
from .conversation import (
|
|
13
|
+
Conversation,
|
|
14
|
+
ConversationSpec,
|
|
15
|
+
SteerableConversation,
|
|
16
|
+
Transport,
|
|
17
|
+
TransportKind,
|
|
18
|
+
)
|
|
13
19
|
from .env import interactive_env
|
|
14
20
|
from .errors import (
|
|
15
21
|
AgentShimError,
|
|
@@ -20,6 +26,7 @@ from .errors import (
|
|
|
20
26
|
ContinuityError,
|
|
21
27
|
FailureKind,
|
|
22
28
|
McpConfigError,
|
|
29
|
+
NoRunningTurnError,
|
|
23
30
|
ProcessClosedError,
|
|
24
31
|
ProviderCapabilityError,
|
|
25
32
|
ReapError,
|
|
@@ -41,6 +48,7 @@ from .events import (
|
|
|
41
48
|
Lifecycle,
|
|
42
49
|
NullEventHandler,
|
|
43
50
|
ProviderError,
|
|
51
|
+
RateLimitStatus,
|
|
44
52
|
RawOutput,
|
|
45
53
|
Reasoning,
|
|
46
54
|
RunFinished,
|
|
@@ -49,6 +57,9 @@ from .events import (
|
|
|
49
57
|
SkillInvoked,
|
|
50
58
|
SkillsDiscovered,
|
|
51
59
|
Stderr,
|
|
60
|
+
SteerConsumed,
|
|
61
|
+
SteerDelivered,
|
|
62
|
+
SteerRejected,
|
|
52
63
|
ToolCall,
|
|
53
64
|
ToolResult,
|
|
54
65
|
TurnInterrupted,
|
|
@@ -92,6 +103,7 @@ from .provider import (
|
|
|
92
103
|
from .schema import compact_json, dialect_problems, materialize, normalize
|
|
93
104
|
from .session_policy import Continuity, PolicyConfig, RetryPolicy, SessionPolicy, SessionState
|
|
94
105
|
from .skills import SkillSummary, SkillTracker
|
|
106
|
+
from .status import AuthState, ProviderStatus
|
|
95
107
|
from .stream import ToolTracker, parse_json_object
|
|
96
108
|
from .turn import OutputSchema, TurnRequest, TurnResult
|
|
97
109
|
from .usage import ProviderUsage, TokenUsage, TokenWeights, normalized_usage
|
|
@@ -104,6 +116,7 @@ __all__ = [
|
|
|
104
116
|
"ApprovalPolicy",
|
|
105
117
|
"ArgvContext",
|
|
106
118
|
"AssistantText",
|
|
119
|
+
"AuthState",
|
|
107
120
|
"Checkpoint",
|
|
108
121
|
"CheckpointStore",
|
|
109
122
|
"CliCheckError",
|
|
@@ -136,6 +149,7 @@ __all__ = [
|
|
|
136
149
|
"ModelPricing",
|
|
137
150
|
"NativeMode",
|
|
138
151
|
"NativePermissions",
|
|
152
|
+
"NoRunningTurnError",
|
|
139
153
|
"NoopInstallation",
|
|
140
154
|
"NullEventHandler",
|
|
141
155
|
"OutputSchema",
|
|
@@ -149,8 +163,10 @@ __all__ = [
|
|
|
149
163
|
"ProviderCapabilityError",
|
|
150
164
|
"ProviderError",
|
|
151
165
|
"ProviderProfile",
|
|
166
|
+
"ProviderStatus",
|
|
152
167
|
"ProviderUsage",
|
|
153
168
|
"RandomIds",
|
|
169
|
+
"RateLimitStatus",
|
|
154
170
|
"RawOutput",
|
|
155
171
|
"ReapError",
|
|
156
172
|
"Reasoning",
|
|
@@ -173,6 +189,10 @@ __all__ = [
|
|
|
173
189
|
"SkillsDiscovered",
|
|
174
190
|
"Stderr",
|
|
175
191
|
"StdioMcpServer",
|
|
192
|
+
"SteerConsumed",
|
|
193
|
+
"SteerDelivered",
|
|
194
|
+
"SteerRejected",
|
|
195
|
+
"SteerableConversation",
|
|
176
196
|
"StopSignal",
|
|
177
197
|
"StreamParser",
|
|
178
198
|
"SystemClock",
|
|
@@ -182,6 +202,7 @@ __all__ = [
|
|
|
182
202
|
"ToolResult",
|
|
183
203
|
"ToolTracker",
|
|
184
204
|
"Transport",
|
|
205
|
+
"TransportKind",
|
|
185
206
|
"TurnCancelledError",
|
|
186
207
|
"TurnFailedError",
|
|
187
208
|
"TurnInterrupted",
|
|
@@ -10,7 +10,8 @@ nothing else changes.
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
12
|
from dataclasses import dataclass
|
|
13
|
-
from
|
|
13
|
+
from enum import Enum
|
|
14
|
+
from typing import TYPE_CHECKING, Protocol, runtime_checkable
|
|
14
15
|
|
|
15
16
|
from .profile import ConfigScope, McpScope, SkillScope
|
|
16
17
|
|
|
@@ -25,6 +26,16 @@ if TYPE_CHECKING:
|
|
|
25
26
|
from .usage import ProviderUsage
|
|
26
27
|
|
|
27
28
|
|
|
29
|
+
class TransportKind(Enum):
|
|
30
|
+
"""Which way an ``Agent`` reaches a provider by name."""
|
|
31
|
+
|
|
32
|
+
#: One CLI process per turn (today's behaviour, the default for now).
|
|
33
|
+
ONE_SHOT = "one_shot"
|
|
34
|
+
#: One long-lived process per conversation, over the provider's structured
|
|
35
|
+
#: streaming protocol.
|
|
36
|
+
STREAM = "stream"
|
|
37
|
+
|
|
38
|
+
|
|
28
39
|
@dataclass(frozen=True)
|
|
29
40
|
class ConversationSpec:
|
|
30
41
|
"""Everything fixed for the life of one conversation.
|
|
@@ -85,6 +96,27 @@ class Conversation(Protocol):
|
|
|
85
96
|
...
|
|
86
97
|
|
|
87
98
|
|
|
99
|
+
@runtime_checkable
|
|
100
|
+
class SteerableConversation(Conversation, Protocol):
|
|
101
|
+
"""A conversation that accepts a message while a turn runs.
|
|
102
|
+
|
|
103
|
+
A transport whose profile has ``supports_steer`` opens conversations of
|
|
104
|
+
this kind. ``steer`` neither queues nor retries: the message goes to the
|
|
105
|
+
running turn now or the call raises.
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
def steer(self, text: str) -> None:
|
|
109
|
+
"""Send *text* into the running turn. Thread-safe.
|
|
110
|
+
|
|
111
|
+
Raises ``NoRunningTurnError`` when no turn is running or the running
|
|
112
|
+
one cannot take a message yet (or any longer). Returns once the message
|
|
113
|
+
is on its way; the outcome is reported as ``SteerDelivered``,
|
|
114
|
+
``SteerConsumed`` and ``SteerRejected`` events, on the thread running
|
|
115
|
+
the turn.
|
|
116
|
+
"""
|
|
117
|
+
...
|
|
118
|
+
|
|
119
|
+
|
|
88
120
|
class Transport(Protocol):
|
|
89
121
|
"""A way to reach one provider, producing conversations."""
|
|
90
122
|
|
|
@@ -227,6 +227,15 @@ class SessionStateError(AgentShimError):
|
|
|
227
227
|
"""
|
|
228
228
|
|
|
229
229
|
|
|
230
|
+
class NoRunningTurnError(SessionStateError):
|
|
231
|
+
"""``steer`` was called when no turn could take a message.
|
|
232
|
+
|
|
233
|
+
Either no turn is running, or the running one has not started yet or is
|
|
234
|
+
already finishing. agentshim never holds a steer back for a later turn: a
|
|
235
|
+
caller that wants one delivered later sends it with the next prompt.
|
|
236
|
+
"""
|
|
237
|
+
|
|
238
|
+
|
|
230
239
|
class TurnCancelledError(AgentShimError):
|
|
231
240
|
"""An interrupt arrived before the turn could start, so it never ran."""
|
|
232
241
|
|
|
@@ -8,7 +8,7 @@ the session sees it.
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
10
|
import sys
|
|
11
|
-
from dataclasses import dataclass
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
12
|
from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable
|
|
13
13
|
|
|
14
14
|
if TYPE_CHECKING:
|
|
@@ -156,6 +156,83 @@ class ApprovalDenied:
|
|
|
156
156
|
detail: str
|
|
157
157
|
|
|
158
158
|
|
|
159
|
+
def _no_raw() -> Mapping[str, Any]:
|
|
160
|
+
return {}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@dataclass(frozen=True)
|
|
164
|
+
class RateLimitStatus:
|
|
165
|
+
"""The provider's view of one rate-limit window, as it just reported it.
|
|
166
|
+
|
|
167
|
+
Optional: a provider that has no such signal emits nothing, and a field the
|
|
168
|
+
provider did not state is ``None``, never zero. One event describes one
|
|
169
|
+
window, so a provider that reports several (a five-hour and a weekly one)
|
|
170
|
+
emits several events. A consumer reads the newest event per
|
|
171
|
+
``(limit, window)``.
|
|
172
|
+
|
|
173
|
+
``window`` names the window in the provider's words (``five_hour``,
|
|
174
|
+
``primary``). ``limit`` names the limit it belongs to when the provider
|
|
175
|
+
has more than one (Codex ``limit_id``). ``used_fraction`` is the share of
|
|
176
|
+
the window already used, ``0.0`` to ``1.0`` (it may exceed 1 when the
|
|
177
|
+
provider reports over-use). ``resets_at`` is epoch seconds.
|
|
178
|
+
``exhausted`` is ``True`` when the provider says this limit is reached,
|
|
179
|
+
``False`` when it says it is not, ``None`` when it does not say.
|
|
180
|
+
``raw`` is the provider's own mapping, for diagnostics.
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
window: str | None
|
|
184
|
+
used_fraction: float | None
|
|
185
|
+
resets_at: float | None
|
|
186
|
+
limit: str | None = None
|
|
187
|
+
window_minutes: int | None = None
|
|
188
|
+
exhausted: bool | None = None
|
|
189
|
+
raw: Mapping[str, Any] = field(default_factory=_no_raw)
|
|
190
|
+
|
|
191
|
+
@property
|
|
192
|
+
def remaining_fraction(self) -> float | None:
|
|
193
|
+
"""The share of the window still available, or ``None`` when usage is unknown."""
|
|
194
|
+
if self.used_fraction is None:
|
|
195
|
+
return None
|
|
196
|
+
return max(0.0, 1.0 - self.used_fraction)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@dataclass(frozen=True)
|
|
200
|
+
class SteerDelivered:
|
|
201
|
+
"""A message sent with ``steer`` reached the provider, which accepted it.
|
|
202
|
+
|
|
203
|
+
This says the provider holds the message, not that the model has seen it
|
|
204
|
+
(see ``SteerConsumed``). Emitted on the thread running the turn.
|
|
205
|
+
"""
|
|
206
|
+
|
|
207
|
+
text: str
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@dataclass(frozen=True)
|
|
211
|
+
class SteerConsumed:
|
|
212
|
+
"""The provider took a steered message into the running turn's context.
|
|
213
|
+
|
|
214
|
+
Emitted where the provider reports it: Codex when the message becomes an
|
|
215
|
+
item of the turn, Claude Code when it echoes the message back. A message
|
|
216
|
+
that the provider only queued for the next turn of its own (Claude Code,
|
|
217
|
+
when the model is not at a tool boundary) is consumed when that turn starts;
|
|
218
|
+
the steering turn then continues until that follow-on finishes.
|
|
219
|
+
"""
|
|
220
|
+
|
|
221
|
+
text: str
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
@dataclass(frozen=True)
|
|
225
|
+
class SteerRejected:
|
|
226
|
+
"""The provider refused a steered message; the turn is unaffected.
|
|
227
|
+
|
|
228
|
+
``reason`` is the provider's own explanation. Codex refuses a steer for a
|
|
229
|
+
turn that cannot take one (a compaction, say) or that has just ended.
|
|
230
|
+
"""
|
|
231
|
+
|
|
232
|
+
text: str
|
|
233
|
+
reason: str
|
|
234
|
+
|
|
235
|
+
|
|
159
236
|
AgentEvent = (
|
|
160
237
|
RunStarted
|
|
161
238
|
| RunFinished
|
|
@@ -173,6 +250,10 @@ AgentEvent = (
|
|
|
173
250
|
| ProviderError
|
|
174
251
|
| TurnInterrupted
|
|
175
252
|
| ApprovalDenied
|
|
253
|
+
| RateLimitStatus
|
|
254
|
+
| SteerDelivered
|
|
255
|
+
| SteerConsumed
|
|
256
|
+
| SteerRejected
|
|
176
257
|
)
|
|
177
258
|
|
|
178
259
|
|
|
@@ -260,6 +341,19 @@ def _truncate_lines(content: str, max_lines: int = _TOOL_OUTPUT_MAX_LINES) -> st
|
|
|
260
341
|
return content
|
|
261
342
|
|
|
262
343
|
|
|
344
|
+
def _describe_rate_limit(event: RateLimitStatus) -> str:
|
|
345
|
+
"""One console line for a rate-limit window; unknown parts are left out."""
|
|
346
|
+
name = " ".join(part for part in (event.limit, event.window) if part) or "rate limit"
|
|
347
|
+
parts = [f"[rate limit] {name}"]
|
|
348
|
+
if event.used_fraction is not None:
|
|
349
|
+
parts.append(f"{event.used_fraction:.0%} used")
|
|
350
|
+
if event.exhausted:
|
|
351
|
+
parts.append("reached")
|
|
352
|
+
if event.resets_at is not None:
|
|
353
|
+
parts.append(f"resets at epoch {event.resets_at:.0f}")
|
|
354
|
+
return ", ".join(parts)
|
|
355
|
+
|
|
356
|
+
|
|
263
357
|
class ConsoleEventHandler:
|
|
264
358
|
"""Render events as human-readable lines on a text stream.
|
|
265
359
|
|
|
@@ -392,6 +486,14 @@ class ConsoleEventHandler:
|
|
|
392
486
|
else:
|
|
393
487
|
self._line(self._paint(f"{event.tool} ran successfully", colour))
|
|
394
488
|
|
|
489
|
+
def _render_steer(self, event: SteerDelivered | SteerConsumed | SteerRejected) -> None:
|
|
490
|
+
if isinstance(event, SteerRejected):
|
|
491
|
+
self._line(f"[steer rejected] {event.text}: {event.reason}")
|
|
492
|
+
elif isinstance(event, SteerConsumed):
|
|
493
|
+
self._line(self._paint(f"[steer consumed] {event.text}", self._DIM))
|
|
494
|
+
else:
|
|
495
|
+
self._line(self._paint(f"[steer delivered] {event.text}", self._DIM))
|
|
496
|
+
|
|
395
497
|
def _render_diagnostic(self, event: AgentEvent) -> None:
|
|
396
498
|
"""Write the tagged non-output events, and drop the rest.
|
|
397
499
|
|
|
@@ -408,5 +510,9 @@ class ConsoleEventHandler:
|
|
|
408
510
|
self._line(self._paint("[interrupted]", self._DIM))
|
|
409
511
|
elif isinstance(event, ApprovalDenied):
|
|
410
512
|
self._line(f"[approval denied] {event.kind}: {event.detail}")
|
|
513
|
+
elif isinstance(event, RateLimitStatus):
|
|
514
|
+
self._line(self._paint(_describe_rate_limit(event), self._DIM))
|
|
515
|
+
elif isinstance(event, (SteerDelivered, SteerConsumed, SteerRejected)):
|
|
516
|
+
self._render_steer(event)
|
|
411
517
|
elif isinstance(event, Lifecycle) and self._show_lifecycle:
|
|
412
518
|
self._line(self._paint(f"[{event.kind}] {event.detail}", self._DIM))
|
|
@@ -198,3 +198,7 @@ class ProviderProfile:
|
|
|
198
198
|
#: When a session should retire a conversation of this provider; ``None``
|
|
199
199
|
#: means never.
|
|
200
200
|
renewal: RenewalBudget | None = None
|
|
201
|
+
#: Whether a conversation on this provider takes ``steer(text)`` while a
|
|
202
|
+
#: turn runs. A provider that does not (every one-shot transport) rejects
|
|
203
|
+
#: it with ``ProviderCapabilityError`` instead of delaying the message.
|
|
204
|
+
supports_steer: bool = False
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Provider readiness: what a probe reports, and how a provider declares its checks.
|
|
2
|
+
|
|
3
|
+
A probe answers "could a turn start?" without running one: is the binary
|
|
4
|
+
there, which version is it, is it logged in. It never calls a model. The
|
|
5
|
+
answer is a ``ProviderStatus``; each provider declares its own checks as a
|
|
6
|
+
``ProbeSpec``, and ``agentshim.probe`` runs them through an executor.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import re
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from enum import Enum
|
|
14
|
+
from typing import TYPE_CHECKING
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from collections.abc import Callable, Mapping
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AuthState(str, Enum):
|
|
21
|
+
"""Whether the provider CLI is authenticated, as far as its own tooling can say.
|
|
22
|
+
|
|
23
|
+
``UNKNOWN`` is distinct from ``FAILED``: it means the CLI offers no cheap
|
|
24
|
+
way to tell (or the check itself could not run), so a caller must not
|
|
25
|
+
treat it as a problem.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
KNOWN_OK = "known_ok"
|
|
29
|
+
FAILED = "failed"
|
|
30
|
+
UNKNOWN = "unknown"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True)
|
|
34
|
+
class ProviderStatus:
|
|
35
|
+
"""The readiness of one provider CLI on one executor.
|
|
36
|
+
|
|
37
|
+
``version`` is ``None`` when the binary is missing or did not report one.
|
|
38
|
+
``auth_detail`` explains ``auth`` in words fit for a log or an error
|
|
39
|
+
message, including the fix when authentication failed.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
provider: str
|
|
43
|
+
binary_found: bool
|
|
44
|
+
path: str | None
|
|
45
|
+
version: str | None
|
|
46
|
+
auth: AuthState
|
|
47
|
+
auth_detail: str
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass(frozen=True)
|
|
51
|
+
class ProbeOutput:
|
|
52
|
+
"""What a status command printed."""
|
|
53
|
+
|
|
54
|
+
returncode: int
|
|
55
|
+
stdout: str
|
|
56
|
+
stderr: str
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def text(self) -> str:
|
|
60
|
+
"""Both streams, stdout first."""
|
|
61
|
+
return self.stdout + self.stderr
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass(frozen=True)
|
|
65
|
+
class AuthCheck:
|
|
66
|
+
"""A provider's reading of its status command's output."""
|
|
67
|
+
|
|
68
|
+
state: AuthState
|
|
69
|
+
detail: str
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass(frozen=True)
|
|
73
|
+
class AuthProbe:
|
|
74
|
+
"""A cheap, non-model command that reports authentication, and how to read it.
|
|
75
|
+
|
|
76
|
+
``argv`` follows the binary. ``read`` receives the output and the
|
|
77
|
+
environment the command ran with (an API key there changes what a failed
|
|
78
|
+
login means) and must return ``UNKNOWN`` for output it does not recognise
|
|
79
|
+
rather than guess.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
argv: tuple[str, ...]
|
|
83
|
+
read: Callable[[ProbeOutput, Mapping[str, str]], AuthCheck]
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass(frozen=True)
|
|
87
|
+
class ProbeSpec:
|
|
88
|
+
"""A provider's readiness checks.
|
|
89
|
+
|
|
90
|
+
``auth`` is ``None`` for a CLI with no status mechanism; ``auth_note`` then
|
|
91
|
+
says why the state is unknown.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
auth: AuthProbe | None
|
|
95
|
+
auth_note: str = "this CLI has no authentication status command"
|
|
96
|
+
version_argv: tuple[str, ...] = ("--version",)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
_VERSION = re.compile(r"\d+(?:\.\d+)+(?:-[0-9A-Za-z.]+)?")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def parse_version(text: str) -> str | None:
|
|
103
|
+
"""The first dotted version number in *text*, or ``None``.
|
|
104
|
+
|
|
105
|
+
``--version`` output varies (``2.1.295 (Claude Code)``,
|
|
106
|
+
``codex-cli 0.160.0``, ``GitHub Copilot CLI 1.0.94.``); the number is the
|
|
107
|
+
only part every CLI shares.
|
|
108
|
+
"""
|
|
109
|
+
match = _VERSION.search(text)
|
|
110
|
+
return match.group(0) if match else None
|