agentshim 0.14.0__tar.gz → 0.14.1__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 (66) hide show
  1. {agentshim-0.14.0 → agentshim-0.14.1}/CHANGELOG.md +9 -0
  2. {agentshim-0.14.0 → agentshim-0.14.1}/PKG-INFO +1 -1
  3. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/__init__.py +1 -1
  4. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/core/schema.py +70 -26
  5. {agentshim-0.14.0 → agentshim-0.14.1}/pyproject.toml +1 -1
  6. {agentshim-0.14.0 → agentshim-0.14.1}/.gitignore +0 -0
  7. {agentshim-0.14.0 → agentshim-0.14.1}/README.md +0 -0
  8. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/agent.py +0 -0
  9. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/core/__init__.py +0 -0
  10. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/core/_files.py +0 -0
  11. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/core/env.py +0 -0
  12. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/core/errors.py +0 -0
  13. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/core/events.py +0 -0
  14. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/core/home.py +0 -0
  15. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/core/mcp.py +0 -0
  16. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/core/pricing.py +0 -0
  17. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/core/profile.py +0 -0
  18. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/core/provider.py +0 -0
  19. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/core/skills.py +0 -0
  20. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/core/stream.py +0 -0
  21. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/core/turn.py +0 -0
  22. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/core/usage.py +0 -0
  23. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/execution/__init__.py +0 -0
  24. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/execution/executor.py +0 -0
  25. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/execution/host.py +0 -0
  26. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/execution/transform.py +0 -0
  27. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/providers/__init__.py +0 -0
  28. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/providers/claude/__init__.py +0 -0
  29. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/providers/claude/events.py +0 -0
  30. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/providers/claude/failures.py +0 -0
  31. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/providers/claude/hooks/__init__.py +0 -0
  32. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/providers/claude/hooks/confine_reads.py +0 -0
  33. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/providers/claude/parser.py +0 -0
  34. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/providers/claude/provider.py +0 -0
  35. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/providers/claude/sandbox.py +0 -0
  36. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/providers/claude/scripted.py +0 -0
  37. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/providers/claude/user_hooks.py +0 -0
  38. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/providers/codex/__init__.py +0 -0
  39. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/providers/codex/_toml.py +0 -0
  40. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/providers/codex/events.py +0 -0
  41. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/providers/codex/failures.py +0 -0
  42. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/providers/codex/mcp_scope.py +0 -0
  43. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/providers/codex/parser.py +0 -0
  44. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/providers/codex/provider.py +0 -0
  45. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/providers/codex/rules.py +0 -0
  46. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/providers/codex/sandbox.py +0 -0
  47. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/providers/codex/scripted.py +0 -0
  48. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/providers/codex/skills.py +0 -0
  49. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/providers/copilot/__init__.py +0 -0
  50. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/providers/copilot/events.py +0 -0
  51. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/providers/copilot/parser.py +0 -0
  52. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/providers/copilot/provider.py +0 -0
  53. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/providers/copilot/scripted.py +0 -0
  54. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/providers/gemini/__init__.py +0 -0
  55. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/providers/gemini/events.py +0 -0
  56. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/providers/gemini/parser.py +0 -0
  57. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/providers/gemini/provider.py +0 -0
  58. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/providers/gemini/scripted.py +0 -0
  59. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/providers/opencode/__init__.py +0 -0
  60. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/providers/opencode/events.py +0 -0
  61. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/providers/opencode/parser.py +0 -0
  62. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/providers/opencode/provider.py +0 -0
  63. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/providers/opencode/scripted.py +0 -0
  64. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/py.typed +0 -0
  65. {agentshim-0.14.0 → agentshim-0.14.1}/agentshim/testing/__init__.py +0 -0
  66. {agentshim-0.14.0 → agentshim-0.14.1}/tests/fixtures/codex/README.md +0 -0
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.14.1 (2026-10-04)
4
+
5
+ ### Fixed
6
+
7
+ - Schema normalization preserves descriptions, titles and examples, including
8
+ annotations alongside `$ref`, so structured-output field guidance reaches
9
+ the model. Only metadata rejected by the selected dialect is removed.
10
+ - The open schema dialect accepts `oneOf` variants, as Claude does.
11
+
3
12
  ## 0.14.0 (2026-10-04)
4
13
 
5
14
  Codex resumed turns report invocation usage. Additive.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: agentshim
3
- Version: 0.14.0
3
+ Version: 0.14.1
4
4
  Summary: Provider-agnostic coding-agent CLI shims
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: tomli>=2; python_full_version < '3.11'
@@ -103,7 +103,7 @@ from .providers.copilot import CopilotProvider
103
103
  from .providers.gemini import GeminiProvider
104
104
  from .providers.opencode import OpencodeProvider
105
105
 
106
- __version__ = "0.14.0"
106
+ __version__ = "0.14.1"
107
107
 
108
108
  __all__ = [
109
109
  "AgentEvent",
@@ -2,7 +2,9 @@
2
2
 
3
3
  Providers disagree about which JSON Schema subset their structured-output
4
4
  flag accepts, and the failure mode is an expensive agent turn that ends in a
5
- CLI parse error. These checks run before the process starts.
5
+ CLI parse error. These checks run before the process starts. Real CLI probes
6
+ on 2026-10-04 confirmed that Codex and Claude accept descriptions, titles
7
+ and examples; normalization preserves these annotations as model guidance.
6
8
  """
7
9
 
8
10
  from __future__ import annotations
@@ -30,8 +32,6 @@ UNSUPPORTED_KEYWORDS = frozenset(
30
32
  "$anchor",
31
33
  "$dynamicAnchor",
32
34
  "$dynamicRef",
33
- "$id",
34
- "$schema",
35
35
  "allOf",
36
36
  "contains",
37
37
  "dependentRequired",
@@ -41,7 +41,6 @@ UNSUPPORTED_KEYWORDS = frozenset(
41
41
  "maxContains",
42
42
  "minContains",
43
43
  "not",
44
- "oneOf",
45
44
  "patternProperties",
46
45
  "prefixItems",
47
46
  "propertyNames",
@@ -53,17 +52,28 @@ UNSUPPORTED_KEYWORDS = frozenset(
53
52
 
54
53
  _SUBSCHEMA_MAPS = frozenset({"properties", "$defs", "definitions"})
55
54
 
56
- # Annotations that describe a schema rather than constrain a value. They are
57
- # what a generator such as Pydantic emits alongside the real constraints, and
58
- # what the strictest CLI subset refuses to read, so ``normalize`` removes
59
- # them. Field names are never matched against this set: ``properties`` is
60
- # traversed as a map of subschemas, so a property named ``title`` survives.
55
+ # Metadata values are annotations or document identifiers, never subschemas.
56
+ # Preserve model guidance, including annotations alongside a $ref. Field
57
+ # names are never matched against this set: properties is a subschema map.
61
58
  _METADATA_KEYWORDS = frozenset({"$schema", "$id", "title", "description", "examples"})
62
59
 
63
- # ``$schema`` and ``$id`` identify the dialect and the document; a CLI that
64
- # accepts open-ended schemas ignores them rather than failing on them. Codex's
65
- # ``--output-schema`` subset does not, so ``STRICT`` keeps reporting them.
66
- _OPEN_DIALECT_TOLERATES = frozenset({"$schema", "$id"})
60
+ # Dialect policy is shared by checking and normalization. Codex's strict
61
+ # subset rejects document identifiers; open schemas tolerate them. Neither
62
+ # dialect rejects description/title/examples in the measured CLI versions.
63
+ _REJECTED_METADATA: dict[SchemaDialect, frozenset[str]] = {
64
+ SchemaDialect.STRICT: frozenset({"$schema", "$id"}),
65
+ SchemaDialect.OPEN: frozenset(),
66
+ }
67
+
68
+ # Claude accepts oneOf variants, while Codex's strict subset rejects oneOf.
69
+ _DIALECT_UNSUPPORTED = {
70
+ SchemaDialect.STRICT: UNSUPPORTED_KEYWORDS | {"oneOf"},
71
+ SchemaDialect.OPEN: UNSUPPORTED_KEYWORDS,
72
+ }
73
+ _REF_ANNOTATIONS_ALLOWED = {
74
+ SchemaDialect.STRICT: False,
75
+ SchemaDialect.OPEN: True,
76
+ }
67
77
 
68
78
  # Keywords whose values are data, never subschemas. ``required`` is a list of
69
79
  # field names and ``enum``/``const`` hold instance values, so a name such as
@@ -84,7 +94,10 @@ def dialect_problems(schema: Mapping[str, Any], dialect: SchemaDialect) -> list[
84
94
  ``additionalProperties: false`` and lists exactly its ``properties`` in
85
95
  ``required`` (an optional value is
86
96
  expressed as nullable instead), the root is not an ``anyOf``, and every
87
- ``$ref`` resolves inside the document.
97
+ ``$ref`` resolves inside the document. Real CLI probes on 2026-10-04
98
+ confirmed both dialects accept ``description``, ``title`` and ``examples``
99
+ on schema nodes. ``STRICT`` rejects them beside ``$ref``; ``OPEN`` accepts
100
+ those siblings and ``oneOf`` variants.
88
101
  """
89
102
  root = dict(schema)
90
103
  walk = _Walk(root=root, dialect=dialect, problems=[])
@@ -125,9 +138,9 @@ class _Walk:
125
138
  def visit(self, node: object, location: str) -> None:
126
139
  """Walk one schema node, appending a problem for every unsupported construct.
127
140
 
128
- A ``$ref`` node terminates the walk: its siblings are annotations the
129
- strict subset ignores, and the target is checked where it is defined,
130
- since every local target is inside the document this walk covers.
141
+ A ``$ref`` node terminates the walk after checking its metadata. The
142
+ target is checked where it is defined, since every local target is
143
+ inside the document this walk covers.
131
144
  """
132
145
  if isinstance(node, list):
133
146
  for index, value in enumerate(cast("list[object]", node)):
@@ -140,6 +153,7 @@ class _Walk:
140
153
  reference = mapping.get("$ref")
141
154
  if reference is not None:
142
155
  self._check_ref(reference, location)
156
+ self._check_ref_metadata(mapping, location)
143
157
  return
144
158
 
145
159
  if not self._check_properties_shape(mapping, location):
@@ -157,6 +171,17 @@ class _Walk:
157
171
  if self.dialect is SchemaDialect.STRICT and not _resolves(self.root, reference):
158
172
  self.problems.append(f"{location} has a $ref {reference!r} that resolves to nothing")
159
173
 
174
+ def _check_ref_metadata(self, mapping: dict[str, Any], location: str) -> None:
175
+ """Check metadata siblings without traversing the referenced target twice."""
176
+ for key in mapping:
177
+ if key not in _METADATA_KEYWORDS:
178
+ continue
179
+ if (
180
+ key in _REJECTED_METADATA[self.dialect]
181
+ or not _REF_ANNOTATIONS_ALLOWED[self.dialect]
182
+ ):
183
+ self.problems.append(f"{location} uses unsupported keyword {key!r} beside '$ref'")
184
+
160
185
  def _check_properties_shape(self, mapping: dict[str, Any], location: str) -> bool:
161
186
  """Check that ``properties``, if present, is an object.
162
187
 
@@ -220,9 +245,9 @@ class _Walk:
220
245
  for key, value in mapping.items():
221
246
  if key in _SUBSCHEMA_MAPS:
222
247
  self._visit_subschema_map(value, f"{location}/{key}")
223
- elif key in UNSUPPORTED_KEYWORDS:
224
- if self.dialect is not SchemaDialect.STRICT and key in _OPEN_DIALECT_TOLERATES:
225
- continue
248
+ elif (
249
+ key in _DIALECT_UNSUPPORTED[self.dialect] or key in _REJECTED_METADATA[self.dialect]
250
+ ):
226
251
  self.problems.append(f"{location} uses unsupported keyword {key!r}")
227
252
  elif key in _METADATA_KEYWORDS or key in _NON_SCHEMA_KEYWORDS:
228
253
  # Annotations or plain values: nothing below them is a schema.
@@ -269,10 +294,13 @@ def normalize(schema: Mapping[str, Any], dialect: SchemaDialect) -> dict[str, An
269
294
  Generators such as Pydantic omit defaulted properties from ``required``
270
295
  and leave ``additionalProperties`` unset, which the CLIs read as "any
271
296
  subset of these keys, plus anything else". This closes every object,
272
- requires every declared property, drops ``default``, strips the annotation
273
- siblings of a ``$ref`` that the strict subset forbids, and removes the
274
- document metadata (``$schema``, ``$id``, ``title``, ``description``,
275
- ``examples``) that ``dialect_problems`` reports under ``STRICT``. An open
297
+ requires every declared property and drops ``default``. Descriptions,
298
+ titles and examples are retained at every schema node: Codex and Claude
299
+ accepted them in real CLI probes on 2026-10-04. For ``STRICT``, annotations
300
+ beside ``$ref`` move to an ``anyOf`` wrapper containing the reference;
301
+ ``OPEN`` keeps them as siblings.
302
+ Only metadata rejected by the dialect is removed (``$schema`` and ``$id``
303
+ under ``STRICT``). Other ``$ref`` siblings are stripped as before. An open
276
304
  map (``additionalProperties`` a schema or ``true``) is kept, since closing
277
305
  it would change what the schema accepts; so ``dialect_problems`` on the
278
306
  normalized schema reports exactly what the dialect cannot express.
@@ -290,10 +318,10 @@ def _normalized(node: object, dialect: SchemaDialect) -> object:
290
318
 
291
319
  reference = mapping.get("$ref")
292
320
  if isinstance(reference, str):
293
- return {"$ref": reference}
321
+ return _normalized_ref(mapping, dialect, reference)
294
322
 
295
323
  mapping.pop("default", None)
296
- for annotation in _METADATA_KEYWORDS:
324
+ for annotation in _REJECTED_METADATA[dialect]:
297
325
  mapping.pop(annotation, None)
298
326
  properties = mapping.get("properties")
299
327
  if isinstance(properties, dict):
@@ -305,6 +333,8 @@ def _normalized(node: object, dialect: SchemaDialect) -> object:
305
333
  mapping["required"] = []
306
334
 
307
335
  for key, value in list(mapping.items()):
336
+ if key in _METADATA_KEYWORDS or key in _NON_SCHEMA_KEYWORDS:
337
+ continue
308
338
  if key in _SUBSCHEMA_MAPS and isinstance(value, dict):
309
339
  mapping[key] = {
310
340
  name: _normalized(subschema, dialect)
@@ -315,6 +345,20 @@ def _normalized(node: object, dialect: SchemaDialect) -> object:
315
345
  return mapping
316
346
 
317
347
 
348
+ def _normalized_ref(
349
+ mapping: dict[str, Any], dialect: SchemaDialect, reference: str
350
+ ) -> dict[str, Any]:
351
+ """Keep accepted annotations without introducing forbidden $ref siblings."""
352
+ annotations = {
353
+ key: value
354
+ for key, value in mapping.items()
355
+ if key in _METADATA_KEYWORDS - _REJECTED_METADATA[dialect]
356
+ }
357
+ if annotations and not _REF_ANNOTATIONS_ALLOWED[dialect]:
358
+ return {"anyOf": [{"$ref": reference}], **annotations}
359
+ return {"$ref": reference, **annotations}
360
+
361
+
318
362
  def _close_object(mapping: dict[str, Any], dialect: SchemaDialect) -> None:
319
363
  """Close an object whose extra keys are merely unspecified.
320
364
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agentshim"
3
- version = "0.14.0"
3
+ version = "0.14.1"
4
4
  description = "Provider-agnostic coding-agent CLI shims"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
File without changes
File without changes