fast-completions 0.7.0 → 0.9.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 296c0ef9c66b4933e440f14845ecca83e7ab18e96f125a572adda19e853be9b7
4
- data.tar.gz: 12fe00808afa0c4c81a9b36dc3d898803c63dd570fcb29d86292d6c526b96557
3
+ metadata.gz: ca8f4d428afa218f2fdbec75c7f74206d3cb34d7a49234781ec1c1b4aba73cd1
4
+ data.tar.gz: 77bb70c53ed40fee638038da6996c39bd0d7231937fe4867f9e9e45a5c27a781
5
5
  SHA512:
6
- metadata.gz: 63b785f46a6a15265ddf700f6039d920670059a5a0c39b4b40c92892462577fc9bc1b02c0dc33645be44a28fc991961bdbd437568ed32188081e6b62105f9ba4
7
- data.tar.gz: c02fdca12bb4133152fc6f41a4c2c2157e011937b93f2eb2dff319c18bda3124836d2a588386ddf96a00022488f13b31b1623edb609c86b7f0c29ea0d77f33c8
6
+ metadata.gz: 28c2471eef9b43aab6067de99a68a994453b1a63dd4fbc02568a9a8ce753e20349c1e66b1ac9289547a3a3223c1ae8e7278ea03881dbc19273de9a522b7d2dd0
7
+ data.tar.gz: b21faba437020f06cb2267e7bd219911880794c8f7b31d9311151c95811c26a1092dd8ac0d3cf4b92f5628b0602927b2d4d79b75000061e5238d7e5e13c205e1
data/CHANGELOG.md CHANGED
@@ -3,6 +3,39 @@
3
3
  Notable changes to the fast-completions gem. The format is prose-first,
4
4
  one section per released version, newest first.
5
5
 
6
+ ## 0.9.0 (2026-11-15)
7
+
8
+ - Tool-args finalize honesty (I06): an arguments scratch that fails the
9
+ finalize whole-buffer parse — even after the control-character repair
10
+ walk — no longer degrades to a silent `arguments = {}` (pi's
11
+ partial-json last resort, which made a gateway double-emit look like
12
+ model incompetence: the muse-glimmer lineage). It now surfaces as the
13
+ loud in-band marker `{error: <message>, raw_arguments: <the raw
14
+ scratch>}` on the tool_call block's `:arguments` (and therefore on the
15
+ `:tool_call_end` event), constructed by
16
+ `Fast::Completions::ToolArgumentsFailure.marker(raw)` and detectable
17
+ with `ToolArgumentsFailure.marker?(value)`. The marker's keys are
18
+ SYMBOLS — `JSON.parse` only yields String keys — so it can never
19
+ collide with legitimate parsed arguments. Nothing raises:
20
+ `StreamAccumulator#finish` still returns, the stream outcome is
21
+ unchanged (a completed `tool_calls` stream still finalizes `[:done,
22
+ :tool_use]`), and consumers should turn the marker into an `{error:}`
23
+ tool result. Unchanged paths: an EMPTY scratch still finalizes `{}`
24
+ (the honest zero-arg / bytes-never-arrived case; the chunk-seam drop
25
+ path counts its own drops), a repairable control-char scratch still
26
+ repairs to valid arguments, and a well-formed stream is byte-for-byte
27
+ unchanged. The wire format is untouched. Consumers executing
28
+ arguments sight-unseen (tart pre-pin-bump) will now splat the marker's
29
+ symbol keys and fail loudly at the kwargs boundary instead of
30
+ executing a bogus zero-arg call — the intended honesty gain.
31
+
32
+ ## 0.8.0 (2026-10-30)
33
+
34
+ - Ruby 3.3 support dropped: `required_ruby_version` moves from
35
+ `>= 3.3` to `>= 3.4`, and the monorepo CI matrix narrows to 3.4/4.0
36
+ to match. No other platform change — no new dependencies, no API
37
+ movement.
38
+
6
39
  ## 0.7.0 (2026-10-25)
7
40
 
8
41
  - Telemetry sink interface: `Client.new` accepts `telemetry:` — any
@@ -59,6 +59,37 @@ module Fast
59
59
  end
60
60
  end
61
61
 
62
+ # I06 serving-stack tool honesty: the loud in-band failure surface
63
+ # for tool-call arguments that fail the finalize parse even after the
64
+ # control-character repair walk (Protocol.parse_streaming_json). NEVER
65
+ # raised — a raise out of StreamAccumulator#finish would kill
66
+ # consuming agent sessions (the pre-tart-#19 crash lineage); the
67
+ # marker rides block[:arguments] itself, so every surface that
68
+ # already reads arguments (message blocks, :tool_call_end events)
69
+ # carries it with no new block keys. Collision-proof by construction:
70
+ # the marker's keys are SYMBOLS, and JSON.parse only ever yields
71
+ # String keys — no legitimate arguments hash can contain :error or
72
+ # :raw_arguments.
73
+ module ToolArgumentsFailure
74
+ MESSAGE = "tool call arguments are unparseable JSON at stream finalize " \
75
+ "(duplicated or truncated arguments stream); the raw argument " \
76
+ "bytes are carried in raw_arguments"
77
+
78
+ module_function
79
+
80
+ # The marker Hash: {error: MESSAGE, raw_arguments: raw}.
81
+ def marker(raw)
82
+ {error: MESSAGE, raw_arguments: raw}
83
+ end
84
+
85
+ # The consumer-side predicate (tart's chat.rb): true only for the
86
+ # marker — legitimate parsed arguments have String keys and can
87
+ # never match.
88
+ def marker?(value)
89
+ value.is_a?(Hash) && value.key?(:error) && value.key?(:raw_arguments)
90
+ end
91
+ end
92
+
62
93
  class CancelledError < StandardError; end # NOT < Error (D8)
63
94
 
64
95
  # I63 constrained sampling (pi api/constrained-sampling.ts). INTERNAL:
@@ -212,10 +212,16 @@ module Fast
212
212
  end
213
213
 
214
214
  # OQ-4: control-character repair (pi utils/json-parse.ts repairJson
215
- # :20-77), {} as the last resort (pi's partial-json fallback). The
216
- # repaired re-parse gets its OWN method — a rescue body's exception
217
- # is not caught by a later rescue clause of the same method (the
218
- # 0.4.0 leak: a delta boundary splitting an escape sequence left the
215
+ # :20-77); I06: a NON-EMPTY scratch that still fails the parse no
216
+ # longer degrades to pi's silent {} partial-json fallback — it
217
+ # surfaces the loud in-band ToolArgumentsFailure marker instead (a
218
+ # parse failure must never masquerade as a deliberate zero-arg
219
+ # call). An EMPTY scratch stays {}: the honest representation of a
220
+ # genuine zero-arg call (or argument bytes that never arrived —
221
+ # the chunk-seam drop path counts those separately). The repaired
222
+ # re-parse gets its OWN method — a rescue body's exception is not
223
+ # caught by a later rescue clause of the same method (the 0.4.0
224
+ # leak: a delta boundary splitting an escape sequence left the
219
225
  # scratch buffer mid-string with a trailing backslash; repair "fixed"
220
226
  # it, the re-parse raised, and the step died). pi's outer try/catch
221
227
  # absorbs this; so do we.
@@ -225,13 +231,15 @@ module Fast
225
231
  JSON.parse(partial)
226
232
  rescue JSON::ParserError
227
233
  repaired = repair_json(partial)
228
- repaired == partial ? {} : parse_repaired_json(repaired)
234
+ repaired == partial ? ToolArgumentsFailure.marker(partial) : parse_repaired_json(repaired, partial)
229
235
  end
230
236
 
231
- def parse_repaired_json(repaired)
237
+ # `raw` is the ORIGINAL scratch: the marker reports the bytes the
238
+ # wire delivered, never our own repair transform of them.
239
+ def parse_repaired_json(repaired, raw)
232
240
  JSON.parse(repaired)
233
241
  rescue JSON::ParserError
234
- {}
242
+ ToolArgumentsFailure.marker(raw)
235
243
  end
236
244
 
237
245
  # Escape raw control characters inside strings; double backslashes
@@ -482,9 +490,11 @@ module Fast
482
490
  raw = streamer ? streamer.scratch : "" # no-fragment blocks finalize an empty scratch
483
491
  # Finalize is the SOLE authority (L9): one whole-buffer parse
484
492
  # of the raw bytes — the JSON.parse fast path when the stream
485
- # completed, the repair walk + {} last resort otherwise
486
- # (pi :453 / utils/json-parse.ts :20-90). The live Hash the
487
- # machine grew is replaced here, exactly once.
493
+ # completed, the repair walk otherwise, and (I06) the loud
494
+ # in-band ToolArgumentsFailure marker when a NON-EMPTY
495
+ # scratch still will not parse — never pi's silent {}
496
+ # last resort. The live Hash the machine grew is replaced
497
+ # here, exactly once.
488
498
  block[:arguments] = Protocol.parse_streaming_json(raw)
489
499
  events << Event.new(type: :tool_call_end, content_index: index,
490
500
  tool_call: {id: block[:id], name: block[:name], arguments: block[:arguments]},
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Fast
4
4
  module Completions
5
- VERSION = "0.7.0"
5
+ VERSION = "0.9.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fast-completions
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Jacobs
@@ -63,7 +63,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
63
63
  requirements:
64
64
  - - ">="
65
65
  - !ruby/object:Gem::Version
66
- version: '3.3'
66
+ version: '3.4'
67
67
  required_rubygems_version: !ruby/object:Gem::Requirement
68
68
  requirements:
69
69
  - - ">="