agentshim 0.6.8__tar.gz → 0.8.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. {agentshim-0.6.8 → agentshim-0.8.0}/CHANGELOG.md +91 -0
  2. {agentshim-0.6.8 → agentshim-0.8.0}/PKG-INFO +4 -3
  3. {agentshim-0.6.8 → agentshim-0.8.0}/README.md +3 -2
  4. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/__init__.py +25 -1
  5. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/agent.py +4 -0
  6. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/core/__init__.py +24 -2
  7. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/core/events.py +32 -0
  8. agentshim-0.8.0/agentshim/core/pricing.py +466 -0
  9. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/core/profile.py +19 -0
  10. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/core/schema.py +14 -9
  11. agentshim-0.8.0/agentshim/core/skills.py +75 -0
  12. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/core/turn.py +5 -1
  13. agentshim-0.8.0/agentshim/core/usage.py +287 -0
  14. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/providers/__init__.py +7 -2
  15. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/providers/claude/events.py +9 -0
  16. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/providers/claude/parser.py +59 -10
  17. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/providers/claude/provider.py +10 -1
  18. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/providers/claude/scripted.py +27 -8
  19. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/providers/codex/events.py +7 -2
  20. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/providers/codex/parser.py +12 -6
  21. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/providers/codex/provider.py +4 -0
  22. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/providers/codex/scripted.py +30 -5
  23. agentshim-0.8.0/agentshim/providers/codex/skills.py +27 -0
  24. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/providers/copilot/parser.py +7 -8
  25. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/providers/copilot/scripted.py +14 -4
  26. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/providers/gemini/parser.py +9 -8
  27. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/providers/gemini/scripted.py +12 -1
  28. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/providers/opencode/parser.py +9 -8
  29. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/providers/opencode/scripted.py +15 -4
  30. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/testing/__init__.py +12 -1
  31. {agentshim-0.6.8 → agentshim-0.8.0}/pyproject.toml +1 -1
  32. agentshim-0.6.8/agentshim/core/usage.py +0 -78
  33. {agentshim-0.6.8 → agentshim-0.8.0}/.gitignore +0 -0
  34. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/core/_files.py +0 -0
  35. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/core/env.py +0 -0
  36. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/core/errors.py +0 -0
  37. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/core/mcp.py +0 -0
  38. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/core/provider.py +0 -0
  39. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/core/stream.py +0 -0
  40. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/execution/__init__.py +0 -0
  41. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/execution/executor.py +0 -0
  42. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/execution/host.py +0 -0
  43. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/execution/transform.py +0 -0
  44. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/providers/claude/__init__.py +0 -0
  45. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/providers/claude/hooks/__init__.py +0 -0
  46. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/providers/claude/hooks/confine_reads.py +0 -0
  47. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/providers/claude/sandbox.py +0 -0
  48. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/providers/claude/user_hooks.py +0 -0
  49. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/providers/codex/__init__.py +0 -0
  50. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/providers/codex/_toml.py +0 -0
  51. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/providers/codex/rules.py +0 -0
  52. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/providers/codex/sandbox.py +0 -0
  53. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/providers/copilot/__init__.py +0 -0
  54. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/providers/copilot/events.py +0 -0
  55. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/providers/copilot/provider.py +0 -0
  56. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/providers/gemini/__init__.py +0 -0
  57. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/providers/gemini/events.py +0 -0
  58. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/providers/gemini/provider.py +0 -0
  59. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/providers/opencode/__init__.py +0 -0
  60. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/providers/opencode/events.py +0 -0
  61. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/providers/opencode/provider.py +0 -0
  62. {agentshim-0.6.8 → agentshim-0.8.0}/agentshim/py.typed +0 -0
@@ -1,5 +1,96 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.0 (2026-10-03)
4
+
5
+ Skill observability. Additive: a caller that ignores the new events and the
6
+ new `TurnResult` field sees no change, but an exhaustive match over
7
+ `AgentEvent` gains two members.
8
+
9
+ ### Added
10
+
11
+ - `SkillsDiscovered(names)` and `SkillInvoked(name, source_path, tool_id)`
12
+ events. `SkillInvoked` sits directly after the `ToolCall` that loaded the
13
+ skill.
14
+ - `TurnResult.skills`: a `SkillSummary` (`discovered`, `invocations`,
15
+ `invoked`, `invocation_count`) folded from those events by `SkillTracker`,
16
+ which callers can also register as a handler to cover several turns.
17
+ `None` means unknown, never zero.
18
+ - `ProviderProfile.skill_discovery` and `skill_invocation`, each a
19
+ `SkillSignal` (`NONE`, `STRUCTURED`, `INFERRED`).
20
+ - Claude Code: discovery from the `system/init` `skills` list; a load is a
21
+ `Skill` tool call or a `Read` of a `SKILL.md`.
22
+ - Codex: a load is inferred from a shell command that names a `SKILL.md`
23
+ under a `skills/` directory. `codex exec --json` does not list offered
24
+ skills, so discovery is unknown.
25
+ - Gemini, opencode and Copilot report unknown for both.
26
+ - `scripted_turn(..., skills_offered=[...], skills_invoked=[...])` scripts the
27
+ offered list (Claude) and skill loads (Claude, Codex) in each provider's own
28
+ format; a provider whose stream cannot carry them raises `ValueError`.
29
+ - Recorded skill streams under `tests/fixtures/{claude,codex}/` and a live
30
+ e2e suite, `tests/e2e/test_skills_e2e.py`, with a positive and a negative
31
+ turn per provider.
32
+
33
+ ### Fixed
34
+
35
+ - `normalize(schema, SchemaDialect.STRICT)` no longer closes an open map
36
+ (`additionalProperties` a schema or `true`), which silently turned
37
+ `dict[str, float]` into an object that can only be empty. The map is kept
38
+ and `dialect_problems` on the normalized schema reports it, so
39
+ `dialect_problems(normalize(s, d), d) == []` is the check for whether a
40
+ generated schema can go native.
41
+
42
+ ## 0.7.0 (2026-09-27)
43
+
44
+ First-class cache accounting and a static pricing table. Additive except one
45
+ change of meaning: `cached_input_tokens` now counts cache reads only (see
46
+ Changed).
47
+
48
+ ### Added
49
+
50
+ - `TokenUsage.cache_read_input_tokens` (input served from the prompt cache),
51
+ `cache_write_1h_input_tokens` (the one-hour-TTL part of cache writes, from
52
+ Claude's `cache_creation` breakdown) and the derived
53
+ `uncached_input_tokens` (`input - cache_read - cache_write`). `to_dict()`
54
+ gains the three keys; no key was removed.
55
+ - Codex turns now report `cache_write_input_tokens` and
56
+ `reasoning_output_tokens` from `turn.completed` (both used to be dropped).
57
+ - `TokenUsage` checks its invariants on construction:
58
+ `cache_read + cache_write <= input`, `cache_write_1h <= cache_write`,
59
+ `reasoning <= output`, no negative counts. Parsers build it through the new
60
+ `normalized_usage`, which clamps a CLI's inconsistent counts instead.
61
+ - `TokenUsage.from_dict` reads a `to_dict()` mapping back, including one
62
+ written before 0.7.0.
63
+ - `TokenWeights` and `TokenUsage.weighted_total(weights)`: a total weighted by
64
+ token class (uncached input, cache reads, 5m and 1h cache writes, output,
65
+ reasoning), each token weighted once.
66
+ - A static, versioned pricing table (`agentshim.core.pricing`):
67
+ `ModelPricing`, `PricingTable`, `default_pricing()`,
68
+ `price_for(provider, model, table=None)` (`None` for an unknown model, never
69
+ zero), `cost_usd(usage, pricing)` and `ModelPricing.relative_weights()`.
70
+ It covers OpenAI GPT-6 (astra, sol, luna), GPT-5.6, GPT-5.5 and earlier
71
+ GPT-5 models, and Claude Fable 5.1, Fable 5, Opus 5.5, Opus 5, Opus 4.8,
72
+ Sonnet 5, Sonnet 4.6 and Haiku 4.5, each with its official source URL and
73
+ check date (all 2026-09-27). Callers override or extend it with
74
+ `PricingTable.with_entries` or `PricingTable.from_dict`. Holding prices in
75
+ agentshim is a user-directed decision; see `docs/pricing.md`.
76
+ - Recorded Codex, Claude (5m and 1h cache writes), opencode and Gemini
77
+ streams under `tests/fixtures/`, with tests pinning each provider's mapping.
78
+ The Claude Haiku 4.5 recording's `total_cost_usd` is reproduced exactly by
79
+ the table.
80
+
81
+ ### Changed
82
+
83
+ - `cached_input_tokens` is a deprecated alias of `cache_read_input_tokens`.
84
+ On Claude, opencode and Copilot it used to be reads plus writes; it is now
85
+ reads only, since a cache write is billed above base input and a read far
86
+ below it. Codex and Gemini report no writes in their cached count, so their
87
+ value is unchanged. Constructing with `cached_input_tokens=` still works.
88
+
89
+ ### Documented
90
+
91
+ - Claude's `result.usage` covers the main conversation only; a Task
92
+ subagent's tokens appear only in `modelUsage` and `total_cost_usd`.
93
+
3
94
  ## 0.6.8 (2026-09-27)
4
95
 
5
96
  A tightening of the Codex (`STRICT`) schema check: schemas it now rejects
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: agentshim
3
- Version: 0.6.8
3
+ Version: 0.8.0
4
4
  Summary: Provider-agnostic coding-agent CLI shims
5
5
  Requires-Python: >=3.10
6
6
  Provides-Extra: test
@@ -23,8 +23,9 @@ No required runtime dependencies. Python 3.10+.
23
23
 
24
24
  - `CliAgent` / `AgentSession`: one turn at a time, resumable, cancellable
25
25
  - typed events delivered on the calling thread
26
- - normalized token accounting where `cached_input_tokens <= input_tokens` on
27
- every provider
26
+ - normalized token accounting (total input, cache reads, cache writes,
27
+ uncached input, output and reasoning) on every provider, plus a static,
28
+ versioned pricing table (see [Usage and Pricing](docs/pricing.md))
28
29
  - declared capabilities on `ProviderProfile`, so no caller probes a provider
29
30
  - injectable `CommandExecutor`s for running the CLI in a container or over a
30
31
  remote shell
@@ -14,8 +14,9 @@ No required runtime dependencies. Python 3.10+.
14
14
 
15
15
  - `CliAgent` / `AgentSession`: one turn at a time, resumable, cancellable
16
16
  - typed events delivered on the calling thread
17
- - normalized token accounting where `cached_input_tokens <= input_tokens` on
18
- every provider
17
+ - normalized token accounting (total input, cache reads, cache writes,
18
+ uncached input, output and reasoning) on every provider, plus a static,
19
+ versioned pricing table (see [Usage and Pricing](docs/pricing.md))
19
20
  - declared capabilities on `ProviderProfile`, so no caller probes a provider
20
21
  - injectable `CommandExecutor`s for running the CLI in a container or over a
21
22
  remote shell
@@ -29,11 +29,13 @@ from .core import (
29
29
  McpMechanism,
30
30
  McpServer,
31
31
  McpTransport,
32
+ ModelPricing,
32
33
  NoopInstallation,
33
34
  NullEventHandler,
34
35
  OutputSchema,
35
36
  OutputSchemaStyle,
36
37
  ParsedTurn,
38
+ PricingTable,
37
39
  Provider,
38
40
  ProviderCapabilityError,
39
41
  ProviderError,
@@ -47,10 +49,16 @@ from .core import (
47
49
  SchemaDialectError,
48
50
  SessionResumeError,
49
51
  SessionStarted,
52
+ SkillInvoked,
53
+ SkillsDiscovered,
54
+ SkillSignal,
55
+ SkillSummary,
56
+ SkillTracker,
50
57
  Stderr,
51
58
  StdioMcpServer,
52
59
  StreamParser,
53
60
  TokenUsage,
61
+ TokenWeights,
54
62
  ToolCall,
55
63
  ToolResult,
56
64
  ToolTracker,
@@ -59,12 +67,16 @@ from .core import (
59
67
  UsageReport,
60
68
  compact_json,
61
69
  compose_event_handlers,
70
+ cost_usd,
71
+ default_pricing,
62
72
  dialect_problems,
63
73
  install_config_file,
64
74
  interactive_env,
65
75
  materialize,
66
76
  normalize,
77
+ normalized_usage,
67
78
  parse_json_object,
79
+ price_for,
68
80
  )
69
81
  from .execution import (
70
82
  CallbackCommandStreamSink,
@@ -84,7 +96,7 @@ from .providers.copilot import CopilotProvider
84
96
  from .providers.gemini import GeminiProvider
85
97
  from .providers.opencode import OpencodeProvider
86
98
 
87
- __version__ = "0.6.8"
99
+ __version__ = "0.8.0"
88
100
 
89
101
  __all__ = [
90
102
  "AgentEvent",
@@ -123,6 +135,7 @@ __all__ = [
123
135
  "McpMechanism",
124
136
  "McpServer",
125
137
  "McpTransport",
138
+ "ModelPricing",
126
139
  "NoopInstallation",
127
140
  "NullEventHandler",
128
141
  "NullSink",
@@ -130,6 +143,7 @@ __all__ = [
130
143
  "OutputSchema",
131
144
  "OutputSchemaStyle",
132
145
  "ParsedTurn",
146
+ "PricingTable",
133
147
  "Provider",
134
148
  "ProviderCapabilityError",
135
149
  "ProviderError",
@@ -144,10 +158,16 @@ __all__ = [
144
158
  "SchemaDialectError",
145
159
  "SessionResumeError",
146
160
  "SessionStarted",
161
+ "SkillInvoked",
162
+ "SkillSignal",
163
+ "SkillSummary",
164
+ "SkillTracker",
165
+ "SkillsDiscovered",
147
166
  "Stderr",
148
167
  "StdioMcpServer",
149
168
  "StreamParser",
150
169
  "TokenUsage",
170
+ "TokenWeights",
151
171
  "ToolCall",
152
172
  "ToolResult",
153
173
  "ToolTracker",
@@ -158,12 +178,16 @@ __all__ = [
158
178
  "__version__",
159
179
  "compact_json",
160
180
  "compose_event_handlers",
181
+ "cost_usd",
182
+ "default_pricing",
161
183
  "dialect_problems",
162
184
  "get_provider",
163
185
  "install_config_file",
164
186
  "interactive_env",
165
187
  "materialize",
166
188
  "normalize",
189
+ "normalized_usage",
167
190
  "parse_json_object",
191
+ "price_for",
168
192
  "provider_names",
169
193
  ]
@@ -26,6 +26,7 @@ from agentshim.core.events import RunFinished, RunStarted, compose_event_handler
26
26
  from agentshim.core.profile import McpMechanism, OutputSchemaStyle, SchemaDialect
27
27
  from agentshim.core.provider import ArgvContext
28
28
  from agentshim.core.schema import compact_json, dialect_problems, materialize
29
+ from agentshim.core.skills import SkillTracker
29
30
  from agentshim.core.turn import TurnRequest, TurnResult, coerce_request
30
31
  from agentshim.execution.executor import CommandRequest
31
32
  from agentshim.execution.host import HostCommandExecutor
@@ -278,8 +279,10 @@ class AgentSession:
278
279
  agent = self._agent
279
280
  handler = agent.event_handler
280
281
  resumed = self.session_id is not None
282
+ skills = SkillTracker(self.profile)
281
283
 
282
284
  def emit(event: AgentEvent) -> None:
285
+ skills.on_event(event)
283
286
  handler.on_event(event)
284
287
 
285
288
  argv = list(command.argv)
@@ -305,6 +308,7 @@ class AgentSession:
305
308
  cost_usd=parsed.cost_usd,
306
309
  duration_ms=duration_ms,
307
310
  exit_code=result.returncode,
311
+ skills=skills.summary(),
308
312
  )
309
313
  self.last_result = turn_result
310
314
  return turn_result
@@ -34,6 +34,8 @@ from .events import (
34
34
  RunFinished,
35
35
  RunStarted,
36
36
  SessionStarted,
37
+ SkillInvoked,
38
+ SkillsDiscovered,
37
39
  Stderr,
38
40
  ToolCall,
39
41
  ToolResult,
@@ -50,12 +52,20 @@ from .mcp import (
50
52
  StdioMcpServer,
51
53
  install_config_file,
52
54
  )
53
- from .profile import McpMechanism, OutputSchemaStyle, ProviderProfile, SchemaDialect
55
+ from .pricing import ModelPricing, PricingTable, cost_usd, default_pricing, price_for
56
+ from .profile import (
57
+ McpMechanism,
58
+ OutputSchemaStyle,
59
+ ProviderProfile,
60
+ SchemaDialect,
61
+ SkillSignal,
62
+ )
54
63
  from .provider import ArgvContext, McpInstallation, ParsedTurn, Provider, StreamParser
55
64
  from .schema import compact_json, dialect_problems, materialize, normalize
65
+ from .skills import SkillSummary, SkillTracker
56
66
  from .stream import ToolTracker, parse_json_object
57
67
  from .turn import OutputSchema, TurnRequest, TurnResult
58
- from .usage import ProviderUsage, TokenUsage
68
+ from .usage import ProviderUsage, TokenUsage, TokenWeights, normalized_usage
59
69
 
60
70
  __all__ = [
61
71
  "AgentEvent",
@@ -79,11 +89,13 @@ __all__ = [
79
89
  "McpMechanism",
80
90
  "McpServer",
81
91
  "McpTransport",
92
+ "ModelPricing",
82
93
  "NoopInstallation",
83
94
  "NullEventHandler",
84
95
  "OutputSchema",
85
96
  "OutputSchemaStyle",
86
97
  "ParsedTurn",
98
+ "PricingTable",
87
99
  "Provider",
88
100
  "ProviderCapabilityError",
89
101
  "ProviderError",
@@ -97,10 +109,16 @@ __all__ = [
97
109
  "SchemaDialectError",
98
110
  "SessionResumeError",
99
111
  "SessionStarted",
112
+ "SkillInvoked",
113
+ "SkillSignal",
114
+ "SkillSummary",
115
+ "SkillTracker",
116
+ "SkillsDiscovered",
100
117
  "Stderr",
101
118
  "StdioMcpServer",
102
119
  "StreamParser",
103
120
  "TokenUsage",
121
+ "TokenWeights",
104
122
  "ToolCall",
105
123
  "ToolResult",
106
124
  "ToolTracker",
@@ -109,10 +127,14 @@ __all__ = [
109
127
  "UsageReport",
110
128
  "compact_json",
111
129
  "compose_event_handlers",
130
+ "cost_usd",
131
+ "default_pricing",
112
132
  "dialect_problems",
113
133
  "install_config_file",
114
134
  "interactive_env",
115
135
  "materialize",
116
136
  "normalize",
137
+ "normalized_usage",
117
138
  "parse_json_object",
139
+ "price_for",
118
140
  ]
@@ -74,6 +74,34 @@ class ToolResult:
74
74
  duration_s: float | None
75
75
 
76
76
 
77
+ @dataclass(frozen=True)
78
+ class SkillsDiscovered:
79
+ """The provider listed the skills it offers this session.
80
+
81
+ ``names`` are the skill names as the provider reports them (a plugin
82
+ skill may carry a ``plugin:`` prefix). Only emitted by a provider whose
83
+ profile declares ``skill_discovery`` other than ``SkillSignal.NONE``, and
84
+ only when its stream actually carried the list.
85
+ """
86
+
87
+ names: tuple[str, ...]
88
+
89
+
90
+ @dataclass(frozen=True)
91
+ class SkillInvoked:
92
+ """The agent loaded a skill's instructions.
93
+
94
+ Emitted at the point in the stream where the load happened, directly
95
+ after the ``ToolCall`` that performed it; ``tool_id`` names that call.
96
+ ``source_path`` is the skill file or directory when the stream reveals
97
+ it, and ``None`` otherwise.
98
+ """
99
+
100
+ name: str
101
+ source_path: str | None = None
102
+ tool_id: str | None = None
103
+
104
+
77
105
  @dataclass(frozen=True)
78
106
  class UsageReport:
79
107
  """Provider accounting for the turn so far."""
@@ -119,6 +147,8 @@ AgentEvent = (
119
147
  | Reasoning
120
148
  | ToolCall
121
149
  | ToolResult
150
+ | SkillsDiscovered
151
+ | SkillInvoked
122
152
  | UsageReport
123
153
  | Lifecycle
124
154
  | Stderr
@@ -351,6 +381,8 @@ class ConsoleEventHandler:
351
381
  """
352
382
  if isinstance(event, Stderr):
353
383
  self._line(f"[stderr] {event.text.rstrip()}")
384
+ elif isinstance(event, SkillInvoked):
385
+ self._line(self._paint(f"[Skill] {event.name}", self._BLUE))
354
386
  elif isinstance(event, ProviderError):
355
387
  self._line(f"[error] {event.message}")
356
388
  elif isinstance(event, Lifecycle) and self._show_lifecycle: