ruby_llm-ag_ui 0.1.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.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +48 -0
  3. data/CONTRIBUTING.md +56 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +146 -0
  6. data/SECURITY.md +30 -0
  7. data/docs/_advanced/rails-integration.md +199 -0
  8. data/docs/_advanced/security-and-deployment.md +204 -0
  9. data/docs/_core_features/approval-and-resume.md +62 -0
  10. data/docs/_core_features/input-validation.md +64 -0
  11. data/docs/_core_features/reconnects.md +42 -0
  12. data/docs/_core_features/streaming-runs.md +49 -0
  13. data/docs/_getting_started/configuration.md +57 -0
  14. data/docs/_getting_started/getting-started.md +52 -0
  15. data/docs/_getting_started/overview.md +189 -0
  16. data/docs/_getting_started/whats-new-in-0-1.md +41 -0
  17. data/docs/_reference/api-reference.md +26 -0
  18. data/docs/_reference/compatibility.md +130 -0
  19. data/docs/_reference/protocol-coverage.md +175 -0
  20. data/docs/_reference/release-history.md +22 -0
  21. data/docs/_reference/releasing.md +54 -0
  22. data/docs/_reference/third-party-notices.md +82 -0
  23. data/docs/_reference/upgrading.md +42 -0
  24. data/docs/index.md +53 -0
  25. data/lib/generators/ruby_llm/ag_ui/install_generator.rb +37 -0
  26. data/lib/generators/ruby_llm/ag_ui/templates/create_ruby_llm_ag_ui_runs.rb.tt +24 -0
  27. data/lib/generators/ruby_llm/ag_ui/templates/initializer.rb.tt +24 -0
  28. data/lib/ruby_llm/ag_ui/canonical.rb +40 -0
  29. data/lib/ruby_llm/ag_ui/configuration.rb +113 -0
  30. data/lib/ruby_llm/ag_ui/coverage.rb +163 -0
  31. data/lib/ruby_llm/ag_ui/emitter.rb +119 -0
  32. data/lib/ruby_llm/ag_ui/error.rb +69 -0
  33. data/lib/ruby_llm/ag_ui/error_sanitizer.rb +69 -0
  34. data/lib/ruby_llm/ag_ui/input.rb +299 -0
  35. data/lib/ruby_llm/ag_ui/interrupt.rb +207 -0
  36. data/lib/ruby_llm/ag_ui/mapper.rb +333 -0
  37. data/lib/ruby_llm/ag_ui/message_parser.rb +127 -0
  38. data/lib/ruby_llm/ag_ui/protocol.rb +130 -0
  39. data/lib/ruby_llm/ag_ui/rails/active_record_store.rb +119 -0
  40. data/lib/ruby_llm/ag_ui/rails/railtie.rb +29 -0
  41. data/lib/ruby_llm/ag_ui/rails/record.rb +51 -0
  42. data/lib/ruby_llm/ag_ui/rails/streaming.rb +166 -0
  43. data/lib/ruby_llm/ag_ui/reconnect.rb +86 -0
  44. data/lib/ruby_llm/ag_ui/redaction.rb +58 -0
  45. data/lib/ruby_llm/ag_ui/redemption.rb +62 -0
  46. data/lib/ruby_llm/ag_ui/reservation.rb +56 -0
  47. data/lib/ruby_llm/ag_ui/resume.rb +108 -0
  48. data/lib/ruby_llm/ag_ui/resume_entry.rb +49 -0
  49. data/lib/ruby_llm/ag_ui/run.rb +177 -0
  50. data/lib/ruby_llm/ag_ui/run_record.rb +229 -0
  51. data/lib/ruby_llm/ag_ui/sse_writer.rb +119 -0
  52. data/lib/ruby_llm/ag_ui/store/base.rb +123 -0
  53. data/lib/ruby_llm/ag_ui/store/memory_store.rb +123 -0
  54. data/lib/ruby_llm/ag_ui/validation.rb +63 -0
  55. data/lib/ruby_llm/ag_ui/version.rb +8 -0
  56. data/lib/ruby_llm/ag_ui.rb +65 -0
  57. metadata +223 -0
@@ -0,0 +1,189 @@
1
+ ---
2
+ layout: default
3
+ title: "How RubyLLM AG-UI Works"
4
+ nav_order: 2
5
+ description: One RubyLLM chat turn becomes one ordered AG-UI run with a single recorded outcome.
6
+ ---
7
+
8
+ # {{ page.title }}
9
+
10
+ {{ page.description }}
11
+ {: .fs-6 .fw-300 }
12
+
13
+ After reading this guide, you will know:
14
+
15
+ * Which objects parse input, drive a chat, write events, and store run state.
16
+ * How the event stream and terminal outcome are ordered.
17
+ * Which responsibilities stay with the host application.
18
+
19
+ One RubyLLM chat turn is one AG-UI run. A run has one identity, one ordered
20
+ stream of events and exactly one outcome, and the parts that decide those things
21
+ are small enough to name:
22
+
23
+ ```
24
+ POST RunAgentInput
25
+ │
26
+ ▼
27
+ ┌──────────────────┐ bounded, validated, typed ┌────────────────┐
28
+ │ Input.parse │──────────────────────────────▶│ Run │
29
+ └──────────────────┘ + canonical digest └────────────────┘
30
+ │ │ │ │
31
+ reserve (scope, thread, run) │ │ │ │
32
+ ┌─────────────────┘ │ │ │
33
+ ▼ │ │ │
34
+ ┌───────────┐ │ │ │
35
+ │ RunStore │◀─────────────┘ │ │
36
+ └───────────┘ save/attach │ │
37
+ │ │
38
+ chat.complete { chunk } ──▶ ┌─────────┐ events │ │
39
+ chat.before_tool_call ──▶ │ Mapper │─────────┘ │
40
+ chat.after_message ──▶ └─────────┘ │
41
+ ▼
42
+ ┌──────────┐ one frame ┌──────────┐
43
+ │ Emitter │─────────────▶│ SSEWriter│──▶ IO
44
+ └──────────┘ per event └──────────┘
45
+ record: sequence, status, terminal
46
+ ```
47
+
48
+ ## The boot contract
49
+
50
+ ```ruby
51
+ require 'ruby_llm/ag_ui'
52
+ ```
53
+
54
+ loads RubyLLM, `ag-ui-protocol`, Zeitwerk and the standard library, and nothing
55
+ else. It does not load Rails, open a network connection or read provider
56
+ credentials, and the test suite asserts that in a subprocess rather than by
57
+ inspection (`spec/integration/core_load_spec.rb`).
58
+
59
+ Two things are deliberately eager:
60
+
61
+ - **Errors** (`lib/ruby_llm/ag_ui/error.rb`) are ignored by Zeitwerk and
62
+ `require`d directly, so a host can `rescue RubyLLM::AgUI::InputError` before
63
+ anything has been autoloaded.
64
+ - **The namespace itself** (`RubyLLM::AgUI`, with `configure`, `config` and
65
+ `reset_configuration!`) lives in `lib/ruby_llm/ag_ui.rb`, because everything
66
+ else hangs off it.
67
+
68
+ The Rails adapter under `lib/ruby_llm/ag_ui/rails/` is ignored by the loader and
69
+ loaded only when `Rails::Railtie` is already defined. A pure-Ruby host never
70
+ loads ActiveRecord or ActionController.
71
+
72
+ `RubyLLM::AgUI` does not define or reopen the top-level `AgUi` namespace: that
73
+ belongs to the `ag-ui` gem, and this gem talks to the protocol SDK as
74
+ `::AgUiProtocol`. Inside the namespace the framework is `::Rails` for the same
75
+ reason.
76
+
77
+ ## The pieces
78
+
79
+ | Component | Owns | Does not own |
80
+ |---|---|---|
81
+ | `Input` / `Parser` / `MessageParser` | Bounding the body, decoding JSON, stripping unknown members, validating shapes, computing the canonical `digest`. | Trust. Nothing a client sends grants a privilege. |
82
+ | `Run` | The turn: reserve identity, drive the chat, project callbacks, close and report the outcome. | Authentication, authorization, transport, chat construction. |
83
+ | `Mapper` | Which AG-UI events a RubyLLM signal becomes, and the ordering between them. Returns arrays; writes nothing. | Sequence numbers, persistence, the terminal event. |
84
+ | `Emitter` | Writing events, advancing the run record's sequence, persisting a terminal outcome before writing it, closing open content first. | Which events; the outcome. |
85
+ | `SSEWriter` | Framing events as `text/event-stream` on any object with `#write`. | The connection, buffering, keep-alive policy. |
86
+ | `Protocol` | The two 1.0 shapes `ag-ui-protocol` 0.2.0 cannot express, plus factories shared by the live and reconnect paths. | Schema evolution. |
87
+ | `Store::Base` / `MemoryStore` / `Rails::ActiveRecordStore` | Run identity, interrupts, redemption. | Chat history, message ids, tool output. |
88
+ | `RunRecord` / `Interrupt` | An immutable value per run and per approval pause, with legal transitions only. | Wire shapes. |
89
+ | `Resume` / `Reconnect` | Answering a previous run's interrupts; serving a run that already ended. | Permission. A resume is a request. |
90
+ | `Redaction` / `ErrorSanitizer` | What may appear on the wire: payload shapes without content, fixed error messages and codes. | Deciding what the host considers secret; that is the host's policy. |
91
+ | `Coverage` | The 31-type audit against the pinned schema. | What the SDK supports. |
92
+
93
+ ## The run, step by step
94
+
95
+ 1. **Parse.** `Input.parse` bounds the body (`config.max_input_bytes`), decodes
96
+ it, strips top-level members the pinned schema does not define (warnings are
97
+ logged; `strict: true` rejects instead), validates message/tool/context
98
+ counts and shapes, and freezes the result with a canonical digest.
99
+ 2. **Reserve.** `Run` asks the store for `(scope, threadId, runId)`. The scope
100
+ must be a non-empty String the host built from authenticated identity. A
101
+ fresh key reserves; the same key with the same digest attaches; the same key
102
+ with a different digest is a conflict; a non-terminal record that is not
103
+ attachable is refused (`StreamError`) rather than streamed twice.
104
+ 3. **Open.** `RUN_STARTED` carries `protocolVersion` and the accepted input, so
105
+ the client can see exactly what the host accepted.
106
+ 4. **Drive.** `Run` registers one `before_tool_call`, one `after_message` and
107
+ one streaming block on the chat, then calls `Chat#complete`. RubyLLM owns the
108
+ agent loop: model calls, tool execution, approval pauses, cancellation
109
+ checkpoints.
110
+ 5. **Project.** Each callback hands the mapper its RubyLLM value and gets back
111
+ the events to write. The mapper is a state machine: a text message opens on
112
+ its first non-empty chunk and closes before a tool call, a phase change or the
113
+ terminal event; a tool call opens when its id and name are both known, streams
114
+ its argument deltas as received, and closes before it runs; one step per phase
115
+ (`completion`, `tool`) so consecutive steps never overlap.
116
+ 6. **Write.** The emitter writes the events as SSE, advancing the run record's
117
+ sequence by one per event.
118
+ 7. **Close and report.** Open content is closed, then the outcome is persisted
119
+ and only then written: `RUN_FINISHED` with `success`, `interrupt` or
120
+ `cancelled`, or `RUN_ERROR` with a sanitized message and code. A chat paused
121
+ on anything the protocol cannot express is a failure, not a success.
122
+
123
+ ## Invariants
124
+
125
+ - **One outcome per run.** `RunRecord` transitions from `reserved` to `started`
126
+ and then to exactly one of `succeeded`, `interrupted`, `cancelled`, `failed`.
127
+ A terminal record accepts no new start, and `finish` records the first failure
128
+ and keeps it.
129
+ - **Content before terminal.** No run ends with a text message, tool call or step
130
+ still open: a client that never sees `*_END` waits forever.
131
+ - **Terminal before transport.** The outcome is stored before it is written, so
132
+ a socket that dies after the decision cannot cause a second execution.
133
+ - **Sequence, not wall clock.** Every event advances the record's sequence; a
134
+ reconnect orders what it serves by that number.
135
+ - **Delta replay is forbidden.** A reconnect serves `RUN_STARTED`,
136
+ `STATE_SNAPSHOT` (when recorded) and the terminal event. Never text or tool
137
+ deltas: replaying them draws text twice, and re-running a tool repeats the
138
+ side effect.
139
+ - **Closed is final.** RubyLLM's callbacks are additive and cannot be removed, so
140
+ the callbacks a run registered stay attached to the chat. A mapper that has
141
+ closed its run therefore maps nothing further; the next turn is a different run
142
+ with its own ids, sequence and record.
143
+
144
+ ## Concurrency
145
+
146
+ RubyLLM may run local tools on worker threads and appends tool results in the
147
+ caller's thread, so `Run` maps and writes under one mutex. That keeps the event
148
+ order consistent with the mapper's state; it does not make a run parallel.
149
+
150
+ Two streams for one live run are refused, not merged: relaying one run to two
151
+ clients is a transport the gem does not own. Two requests for one run identity
152
+ are resolved by the store, and the store's redemption step is the authoritative
153
+ race guard for a resume. The loser is told the answer was already recorded
154
+ instead of running the tool twice.
155
+
156
+ ## Storage
157
+
158
+ A record is a value object; stores keep values. `RunRecord#to_h` is the reporting
159
+ shape (no interrupt argument digests, no creation times), and `#to_storage` is
160
+ the durable shape a store round-trips through JSON. `ruby_llm_ag_ui_runs` (the
161
+ Rails store) is one row per run: scope, thread, run, digest, status, sequence,
162
+ snapshot, failure, interrupts.
163
+
164
+ Why the scope must be a String: an arbitrary object hashes by identity, so the
165
+ same actor would produce a different key on the next request and a retry would
166
+ execute the run again. A String built from authenticated identity is stable
167
+ across requests and processes, which is the property the reservation needs.
168
+
169
+ ## Extension points
170
+
171
+ | Need | Hook |
172
+ |---|---|
173
+ | Durable or shared run identity | `config.run_store`, or `store:` per run. Implement the `Store::Base` interface. |
174
+ | A different redaction policy | `config.redact`, or `redact:` per run. |
175
+ | Deterministic time in tests | `config.clock`, or `clock:` per run. |
176
+ | A shorter/longer approval window | `config.interrupt_ttl`, or `expires_in:` per run. |
177
+ | A different transport | Write to your own object: the gem needs `#write`. |
178
+ | A different server | Any Rack server; the Rails concern is one way to get a live response. |
179
+
180
+ ## Non-goals
181
+
182
+ The gem is not an AG-UI SDK, a schema fork, a chat UI, a CopilotKit runtime, or a
183
+ second agent framework. It does not own conversation history, message ids, tool
184
+ authorization, or the decision to run a tool. It does not open connections, spawn
185
+ jobs or buffer frames.
186
+
187
+ See [Protocol Coverage]({% link _reference/protocol-coverage.md %}) for the wire events,
188
+ [Security and Deployment]({% link _advanced/security-and-deployment.md %}) for the threat model, and
189
+ [Rails Integration]({% link _advanced/rails-integration.md %}) for the adapter.
@@ -0,0 +1,41 @@
1
+ ---
2
+ layout: default
3
+ title: "What's New in 0.1"
4
+ nav_order: 0
5
+ description: Use the first RubyLLM AG-UI release to stream, resume, cancel, and reconnect agent runs.
6
+ ---
7
+
8
+ # {{ page.title }}
9
+
10
+ {{ page.description }}
11
+ {: .fs-6 .fw-300 }
12
+
13
+ After reading this guide, you will know:
14
+
15
+ * How to install version 0.1.
16
+ * Which run lifecycle features it provides.
17
+ * Which compatibility checks still need a real application.
18
+
19
+ ## Install the First Release
20
+
21
+ ```ruby
22
+ gem 'ruby_llm-ag_ui', '~> 0.1.0'
23
+ ```
24
+
25
+ Use Ruby 3.2 or later, RubyLLM 2.1 or later within version 2, and `ag-ui-protocol` 0.2. Follow [Getting Started]({% link _getting_started/getting-started.md %}) to create your first run.
26
+
27
+ ## Stream One Chat Turn as One Run
28
+
29
+ The gem validates `RunAgentInput` and maps RubyLLM text, tool, and lifecycle callbacks to AG-UI events. An SSE writer sends each encoded event to the client. See [Streaming Runs]({% link _core_features/streaming-runs.md %}) and [Protocol Coverage]({% link _reference/protocol-coverage.md %}).
30
+
31
+ ## Pause, Resume, Cancel, and Reconnect
32
+
33
+ Tool approval pauses use interrupts bound to their tool calls. Your host authorizes the actor and records the decision before resuming. Cancellation produces the cancelled outcome. A retry for a completed run replays its recorded state and terminal event. See [Approval and Resume]({% link _core_features/approval-and-resume.md %}) and [Reconnects]({% link _core_features/reconnects.md %}).
34
+
35
+ ## Add the Optional Rails Adapter
36
+
37
+ The adapter supplies a streaming controller concern, an ActiveRecord run store, and an installation generator. The pure-Ruby entrypoint does not load Rails. Follow [Rails Integration]({% link _advanced/rails-integration.md %}).
38
+
39
+ ## Check Your Application's Compatibility
40
+
41
+ Offline checks cover the mapping, schema, lifecycle, store, and adapter behavior. A release does not establish interoperability with every AG-UI client or live provider. Read [Compatibility]({% link _reference/compatibility.md %}) and record your application checks before making those claims.
@@ -0,0 +1,26 @@
1
+ ---
2
+ layout: default
3
+ title: API Reference
4
+ nav_order: 1
5
+ guide: false
6
+ description: Browse RDoc pages generated from classes and methods in the gem source.
7
+ ---
8
+
9
+ # {{ page.title }}
10
+
11
+ {{ page.description }}
12
+ {: .fs-6 .fw-300 }
13
+
14
+ The [API reference]({{ '/api/' | relative_url }}) opens on the `RubyLLM` module. The generated pages follow the RDoc comments in `lib/`.
15
+
16
+ | Build with | Reference |
17
+ | --- | --- |
18
+ | Configure defaults | [`RubyLLM::AgUI::Configuration`]({{ '/api/RubyLLM/AgUI/Configuration.html' | relative_url }}) |
19
+ | Parse a request | [`RubyLLM::AgUI::Input`]({{ '/api/RubyLLM/AgUI/Input.html' | relative_url }}) |
20
+ | Stream a chat turn | [`RubyLLM::AgUI::Run`]({{ '/api/RubyLLM/AgUI/Run.html' | relative_url }}) |
21
+ | Inspect event coverage | [`RubyLLM::AgUI::Coverage`]({{ '/api/RubyLLM/AgUI/Coverage.html' | relative_url }}) |
22
+ | Store run state | [`RubyLLM::AgUI::Store::MemoryStore`]({{ '/api/RubyLLM/AgUI/Store/MemoryStore.html' | relative_url }}) |
23
+
24
+ For Markdown, start with the [class index]({{ '/api/index.md' | relative_url }}). Every HTML class page has a matching `.md` page.
25
+
26
+ The reference is generated from source comments with RDoc. Build it locally with `docs/bin/build-api.sh tmp/api`.
@@ -0,0 +1,130 @@
1
+ ---
2
+ layout: default
3
+ title: "Compatibility"
4
+ nav_order: 2
5
+ description: Check the dependency versions and behaviors that this repository verifies.
6
+ ---
7
+
8
+ # {{ page.title }}
9
+
10
+ {{ page.description }}
11
+ {: .fs-6 .fw-300 }
12
+
13
+ After reading this guide, you will know:
14
+
15
+ * Which runtime and protocol dependency ranges the gem supports.
16
+ * What the local and CI suites assert.
17
+ * Which client and provider claims remain unverified.
18
+
19
+ What this gem is checked against, what it is not, and what changes when a
20
+ dependency does.
21
+
22
+ ## Supported versions
23
+
24
+ | Component | Range | Verified |
25
+ |---|---|---|
26
+ | Ruby | `>= 3.2` | 3.2, 3.3, 3.4 in CI; 3.3.5 locally |
27
+ | RubyLLM | `>= 2.1`, `< 3.0` | 2.1.1 (callback, approval and cancellation behavior read from its source) |
28
+ | `ag-ui-protocol` | `~> 0.2.0` | 0.2.0 |
29
+ | Rails (adapter only) | `railties`, `actionpack`, `activerecord` `>= 8.0` | 8.0.x and 8.1.x in CI |
30
+ | AG-UI schema | pinned commit `89cedd3fd549feeee41e50e1d7c6d373e549241a` | vendored, SHA-256 recorded in [AG-UI Protocol Coverage]({% link _reference/protocol-coverage.md %}) |
31
+
32
+ The pure-Ruby core depends only on `ruby_llm` and `ag-ui-protocol`. Rails,
33
+ `ToolViews`, `OpenUI` and `OddlyDock` are not runtime dependencies, and the Rails
34
+ adapter is only loaded when `Rails::Railtie` is already defined.
35
+
36
+ ## What is asserted, and how
37
+
38
+ | Claim | How it is checked |
39
+ |---|---|
40
+ | `require 'ruby_llm/ag_ui'` loads no Rails, opens no connection, reads no credentials | `spec/integration/core_load_spec.rb` runs the require in a subprocess and inspects what it loaded |
41
+ | Every emitted event is a valid AG-UI 1.0 event | mapper and run specs validate each encoded event against the vendored schema (`SchemaHelper`) |
42
+ | The 31-type coverage map is the schema's own union | `spec/ruby_llm/ag_ui/coverage_spec.rb` compares the pinned list with `Coverage`, and the SDK enum at runtime |
43
+ | The SDK's five legacy `THINKING_*` constants are never counted as 1.0 | the same spec asserts `LEGACY_SDK_ONLY` is absent from the map |
44
+ | Text, tool and lifecycle sequences are ordered and closed | mapper specs, including partial arguments, a nil chunk, a tool that raises, and a thinking-only chunk |
45
+ | One outcome per run, and the outcome matches what happened | run specs: success, interrupt, cancelled, sanitized failure, unsupported pause |
46
+ | `requires_approval` → interrupt → resume executes the tool once | run specs drive a fake chat through the pause, approve, deny, replay and expiry cases |
47
+ | Identity is reserved, replayed and never re-executed | store specs (memory and ActiveRecord), run specs for conflict, reconnect and live-run refusal |
48
+ | The Rails adapter serves a run and refuses invalid requests before committing | adapter specs with a controller double, and an end-to-end `spec/integration/rails_adapter_spec.rb` |
49
+ | The installer resolves the documented command | `spec/ruby_llm/ag_ui/generators/install_generator_spec.rb` |
50
+ | The installed gem generates a working migration and initializer | `bin/verify-package --rails` generates files and migrates the table up and down in SQLite |
51
+ | `ruby_llm-ag_ui` installs outside the checkout | `bin/verify-package` resolves dependencies in a fresh gem home and loads the packaged core |
52
+
53
+ The RSpec suite runs offline with fakes and no provider credentials. Package verification uses the network only to install dependencies, then runs its core and Rails checks without a provider. Its advisory check includes the actual default-library gem versions activated by the isolated core install.
54
+
55
+ ## What is not asserted
56
+
57
+ - **Real AG-UI clients** (CopilotKit, `@ag-ui/client`, any other consumer). No
58
+ client has been recorded against this producer. The events are schema-valid and
59
+ the ordering rules the protocol documents are enforced, but "a specific client
60
+ renders this correctly" is a separate claim, and it is not made here.
61
+ - **Live providers.** `spec/live/live_run_spec.rb` is the opt-in check
62
+ (`RUBY_LLM_LIVE=1`, plus a provider key); it is skipped in the default suite.
63
+ Provider behavior in 2.1.1 is derived from its source, not from a recorded
64
+ session.
65
+ - **Every RubyLLM patch release in the `>= 2.1, < 3.0` range.** The upper bound
66
+ is enforced by the gemspec; the verified version is 2.1.1.
67
+ - **Rails versions before 8.0.** The adapter uses `Rails.setup!` and an
68
+ initializer, and its specs run on 8.0/8.1. It may work on 7.2 and it is not
69
+ tested there.
70
+ - **Databases other than SQLite** for the ActiveRecord store. The store uses a
71
+ unique index and `with_lock`; PostgreSQL and MySQL should behave the same, and
72
+ that is untested.
73
+
74
+ ## Deliberate behavior worth knowing
75
+
76
+ These are choices, not gaps, and each has a reason:
77
+
78
+ - **Tool results are `[redacted]` by default.** Redaction keeps the payload's
79
+ shape and withholds its content until a host opts out. A client will see
80
+ `[redacted]` until you configure a policy.
81
+ - **A resume run emits `TOOL_CALL_RESULT` without repeating
82
+ `TOOL_CALL_START`.** The call was already reported in the run that interrupted;
83
+ repeating it would duplicate the call in the client's conversation.
84
+ - **`MESSAGES_SNAPSHOT` is never produced.** History is the host's, and the gem
85
+ will not re-id it. `STATE_SNAPSHOT` is produced, on a reconnect.
86
+ - **No `ACTIVITY_*`, `REASONING_*`, `RAW` or `SUBAGENT_*` events.** RubyLLM 2.x
87
+ exposes no data for them on the supported path, and the gem does not invent
88
+ events. A thinking-only chunk produces nothing.
89
+ - **Message ids are the gem's** (`run-<id>:a<n>`), scoped to the run, because
90
+ RubyLLM streaming chunks carry no message id.
91
+ - **Steps are per phase** (`completion`, `tool`), named `step-<n>` per run.
92
+ - **Client-supplied tools are never registered.** `input.tools` is reported and
93
+ ignored.
94
+ - **Two live streams for one run are refused**, not merged; a run that already
95
+ ended is served by reconnect instead.
96
+
97
+ ## When a dependency moves
98
+
99
+ **A new `ag-ui-protocol` 0.2.x** that adds a schema type will fail the coverage
100
+ spec's runtime comparison, which is the point: the map has to be updated
101
+ deliberately, with a status and a reason.
102
+
103
+ **`ag-ui-protocol` 0.3+** would need `Protocol` re-checked first: the gem
104
+ subclasses `Model` and `BaseEvent` for the cancelled outcome, the versioned
105
+ `RUN_STARTED` and the run-finished shape. The gaps listed in
106
+ the [protocol coverage guide]({% link _reference/protocol-coverage.md %}) may be closed upstream, in which case
107
+ the local subclasses should be removed rather than kept.
108
+
109
+ **A new RubyLLM 2.x minor** can change the callback contract (`before_tool_call`,
110
+ `after_message`, `complete`'s streaming block), approval state (`waiting?`,
111
+ `awaiting_approval?`, `pending_approvals`, `approve`, `deny`) or the shape of
112
+ chunks and messages. The mapper and run specs pin all of it, so an upgrade fails
113
+ loudly instead of silently dropping content. A 3.0 would need a new adapter
114
+ layer; the gemspec refuses it.
115
+
116
+ **A new AG-UI schema commit** is a deliberate change: vendor the file, update
117
+ `Protocol::SCHEMA_COMMIT` and the SHA-256 in
118
+ the [protocol coverage guide]({% link _reference/protocol-coverage.md %}), then let the coverage and schema
119
+ specs tell you what moved.
120
+
121
+ ## Reporting an incompatibility
122
+
123
+ Open an issue with the versions from `RubyLLM::AgUI.config.to_h`, the event types
124
+ that went wrong, and whether a real client or a live provider was involved. A
125
+ redacted trace is enough; do not paste credentials or raw tool payloads.
126
+
127
+ ## Next Steps
128
+
129
+ * [AG-UI Protocol Coverage]({% link _reference/protocol-coverage.md %}) - inspect event support.
130
+ * [Upgrading]({% link _reference/upgrading.md %}) - record breaking dependency changes.
@@ -0,0 +1,175 @@
1
+ ---
2
+ layout: default
3
+ title: "AG-UI Protocol Coverage"
4
+ nav_order: 1
5
+ description: See how this producer handles each event in the pinned AG-UI 1.0 schema.
6
+ ---
7
+
8
+ # {{ page.title }}
9
+
10
+ {{ page.description }}
11
+ {: .fs-6 .fw-300 }
12
+
13
+ After reading this guide, you will know:
14
+
15
+ * Which event types the gem produces, leaves to the host, or does not emit.
16
+ * How success, interruption, cancellation, failure, and reconnect map to events.
17
+ * Which protocol gaps the pinned Ruby SDK does not express.
18
+
19
+ `ruby_llm-ag_ui` speaks AG-UI 1.0. This page is the audit of what it produces
20
+ against that schema: every one of the 31 event types the 1.0 `Event` union
21
+ defines, with the status this gem gives it and why.
22
+
23
+ A producer is not required to manufacture events it has no data for. What it must
24
+ never do is emit an event the schema does not define, or claim a run succeeded
25
+ when it paused, failed or was cancelled.
26
+
27
+ ## The pinned schema
28
+
29
+ | | |
30
+ |---|---|
31
+ | Repository | <https://github.com/ag-ui-protocol/ag-ui> |
32
+ | Commit | `89cedd3fd549feeee41e50e1d7c6d373e549241a` |
33
+ | File | `docs/spec/1.0/schema.json` |
34
+ | Vendored copy | `spec/fixtures/schema/ag-ui-1.0.schema.json` |
35
+ | SHA-256 | `4b5c93226838a0e72d88e6c5df20633c686c49fcb75d9be2815c6cbf9e48e71a` |
36
+
37
+ The vendored schema is what the test suite validates against, so a mapping
38
+ change fails CI rather than a client. `RubyLLM::AgUI::Protocol::SCHEMA_COMMIT`
39
+ records the commit, and `schema_valid!` in the mapper and run specs checks every
40
+ emitted event against the vendored file.
41
+
42
+ ## The 31 event types
43
+
44
+ Legend:
45
+
46
+ - **produced:** emitted from real RubyLLM data on the supported path.
47
+ - **optional:** emitted only when the host opts in and real data exists.
48
+ - **host-owned:** the host owns the payload; this gem does not produce it.
49
+ - **not produced:** the producer has no data for it and never fabricates it.
50
+
51
+ ### Produced (13)
52
+
53
+ | Event type | Family | When |
54
+ |---|---|---|
55
+ | `RUN_STARTED` | run | Opens a reserved run; declares `protocolVersion` and echoes the accepted input. |
56
+ | `RUN_FINISHED` | run | Success, interrupt or cancelled outcome; never a raw partial. |
57
+ | `RUN_ERROR` | run | Sanitized message and code; never raw exception text. |
58
+ | `STEP_STARTED` | run | One step per RubyLLM phase (completion, tool round). |
59
+ | `STEP_FINISHED` | run | Closed before the terminal event. |
60
+ | `TEXT_MESSAGE_START` | text | Opened on the first non-empty assistant chunk. |
61
+ | `TEXT_MESSAGE_CONTENT` | text | One delta per non-empty `chunk.content`, in order. |
62
+ | `TEXT_MESSAGE_END` | text | Closed before a tool call, a phase change or the terminal event. |
63
+ | `TOOL_CALL_START` | tool | Emitted when a call is observed before it runs. |
64
+ | `TOOL_CALL_ARGS` | tool | Arguments as received; never re-synthesized. |
65
+ | `TOOL_CALL_END` | tool | Closed before the call runs or the run ends. |
66
+ | `TOOL_CALL_RESULT` | tool | The tool result, after the host redaction policy. |
67
+ | `STATE_SNAPSHOT` | state | On a reconnect: the state the host gave the run. |
68
+
69
+ ### Optional (1)
70
+
71
+ | Event type | Family | When |
72
+ |---|---|---|
73
+ | `CUSTOM` | raw/custom | Only safe, documented host extensions. Nothing in the gem emits one today. |
74
+
75
+ ### Host-owned, not produced (2)
76
+
77
+ | Event type | Family | Why the gem does not produce it |
78
+ |---|---|---|
79
+ | `MESSAGES_SNAPSHOT` | state | The conversation belongs to the host. Producing one would mean re-synthesizing message ids the gem did not mint, which a client would then render as new messages. |
80
+ | `STATE_DELTA` | state | Deltas describe a change the host made. The gem never invents a patch, and it has no API that publishes one. |
81
+
82
+ ### Not produced (15)
83
+
84
+ | Event type | Family | Why |
85
+ |---|---|---|
86
+ | `TEXT_MESSAGE_CHUNK` | text | Convenience form of the explicit triad; the gem emits `START`/`CONTENT`/`END`. |
87
+ | `TOOL_CALL_CHUNK` | tool | Convenience form; the gem emits the explicit triad. |
88
+ | `ACTIVITY_SNAPSHOT` | activity | No `ACTIVITY_*` projection yet; RubyLLM's progress signals are not mapped. |
89
+ | `ACTIVITY_DELTA` | activity | Same: no progress projection, and no invented patches. |
90
+ | `RAW` | raw/custom | The gem never passes provider payloads through. |
91
+ | `REASONING_START` | reasoning | Private reasoning is not emitted until a host opts in, and there is no opt-in yet. |
92
+ | `REASONING_MESSAGE_START` | reasoning | A chunk that carries thinking is ignored. |
93
+ | `REASONING_MESSAGE_CONTENT` | reasoning | Same. |
94
+ | `REASONING_MESSAGE_END` | reasoning | Nothing opens a reasoning block to close. |
95
+ | `REASONING_MESSAGE_CHUNK` | reasoning | Convenience form; not emitted. |
96
+ | `REASONING_END` | reasoning | Nothing opens a reasoning block to close. |
97
+ | `REASONING_ENCRYPTED_VALUE` | reasoning | RubyLLM 2.x exposes no encrypted reasoning value. |
98
+ | `SUBAGENT_STARTED` | subagents | RubyLLM 2.x runs one chat; there is no subagent data. |
99
+ | `SUBAGENT_FINISHED` | subagents | Same. |
100
+ | `SUBAGENT_ERROR` | subagents | Same. |
101
+
102
+ Totals: 13 produced + 1 optional + 2 host-owned + 15 not produced = **31**.
103
+
104
+ ## Terminal events
105
+
106
+ One run ends exactly once, and the outcome is part of the run record before it is
107
+ written:
108
+
109
+ | Situation | Terminal event |
110
+ |---|---|
111
+ | The chat completed | `RUN_FINISHED` with `{"type":"success"}` |
112
+ | The chat paused on `requires_approval` | `RUN_FINISHED` with `{"type":"interrupt","interrupts":[...]}` |
113
+ | The chat was cancelled (`Chat#cancel`) | `RUN_FINISHED` with `{"type":"cancelled"}` |
114
+ | The run failed | `RUN_ERROR` with a sanitized `message` and a `code` |
115
+ | The chat paused on something the protocol cannot express | `RUN_ERROR` (`unsupported_run_state`), never a success |
116
+
117
+ A paused run is never a success. A cancelled run is never a success. A transport
118
+ that dies mid-run reports nothing on the wire; the record stays non-terminal and
119
+ the client can reconnect, and it is never evidence that the work finished.
120
+
121
+ ## Reconnect
122
+
123
+ A client that re-sends the same `(threadId, runId)` and the same input for a run
124
+ that already ended gets:
125
+
126
+ 1. `RUN_STARTED` (with `protocolVersion` and the input echo),
127
+ 2. `STATE_SNAPSHOT` when the run recorded one,
128
+ 3. the recorded terminal event.
129
+
130
+ It does not get the run's text or tool deltas again: replaying them would draw
131
+ the text twice, and re-running a tool would duplicate the side effect. That is
132
+ also why `MESSAGES_SNAPSHOT` is not used because the client's own conversation is the
133
+ authority, and the gem will not hand it a second, re-id'd copy.
134
+
135
+ ## Where this differs from `ag-ui-protocol` 0.2.0
136
+
137
+ The community Ruby SDK (`ag-ui-protocol` 0.2.0) is what this gem encodes with, so
138
+ its gaps are visible here. Issue drafts for these SDK gaps are kept under
139
+ `docs/.internal/upstream/` in the repository.
140
+
141
+ | ID | Gap | This gem's handling |
142
+ |---|---|---|
143
+ | SDK-GAP-01 | No `cancelled` run outcome. | `RubyLLM::AgUI::Protocol::CancelledOutcome`, a schema-valid `cancelled` outcome. |
144
+ | SDK-GAP-02 | `RunStartedEvent` has no `protocolVersion`. | `RubyLLM::AgUI::Protocol::RunStartedEvent` adds it, defaulting to `"1.0"`. |
145
+ | SDK-GAP-03 | No JSON → `RunAgentInput` parser. | `RubyLLM::AgUI::Input.parse`, with size and schema bounds. |
146
+ | SDK-GAP-04 | No `SUBAGENT_STARTED`/`_FINISHED`/`_ERROR` constants. | Not produced: RubyLLM 2.x has no subagent data to report. |
147
+ | SDK-GAP-05 | Five legacy `THINKING_*` constants that 1.0 does not define. | Never emitted. `Coverage` excludes them from the 31 and the suite asserts it. |
148
+
149
+ The SDK defines 33 event-type constants; the 1.0 schema defines 31. The two sets
150
+ differ by exactly the five `THINKING_*` constants (SDK-only) and the three
151
+ `SUBAGENT_*` types (schema-only), and `RubyLLM::AgUI::Coverage` computes that
152
+ difference at runtime rather than hardcoding it, so an SDK upgrade that closes a
153
+ gap shows up as a failing expectation instead of a stale document.
154
+
155
+ ## Checking It Yourself
156
+
157
+ ```ruby
158
+ require 'ruby_llm/ag_ui'
159
+
160
+ RubyLLM::AgUI::Coverage.entries.size # => 31
161
+ RubyLLM::AgUI::Coverage.sdk_only_event_types
162
+ # => ["THINKING_TEXT_MESSAGE_START", "THINKING_TEXT_MESSAGE_CONTENT", "THINKING_TEXT_MESSAGE_END", "THINKING_START", "THINKING_END"]
163
+ RubyLLM::AgUI::Coverage.schema_only_event_types
164
+ # => ["SUBAGENT_STARTED", "SUBAGENT_FINISHED", "SUBAGENT_ERROR"]
165
+ ```
166
+
167
+ Every event the mapper and the run emit is validated against the vendored schema
168
+ in the spec suite (`bundle exec rspec`), including the negative cases: partial
169
+ tool arguments, a nil chunk, a tool that raises, and a thinking-only chunk that
170
+ must produce no event at all.
171
+
172
+ ## Next Steps
173
+
174
+ * [Streaming Runs]({% link _core_features/streaming-runs.md %}) - follow event order and outcomes.
175
+ * [Compatibility]({% link _reference/compatibility.md %}) - see what this repository verifies.
@@ -0,0 +1,22 @@
1
+ ---
2
+ layout: default
3
+ title: Release History
4
+ nav_order: 9
5
+ guide: false
6
+ description: Review RubyLLM AG-UI releases, newest first, with highlights and upgrade guidance.
7
+ ---
8
+
9
+ # {{ page.title }}
10
+
11
+ {{ page.description }}
12
+ {: .fs-6 .fw-300 }
13
+
14
+ {% if site.docs_unreleased %}This build follows `main`. The latest stable release is `{{ site.version }}`.
15
+ {% else %}This documentation describes version `{{ site.version }}`. Use its tag and release notes to identify the exact source.
16
+ {% endif %}
17
+
18
+ | Release | Date | Highlights |
19
+ | --- | --- | --- |
20
+ | [0.1.0](https://github.com/yorzi/ruby_llm-ag-ui/releases/tag/v0.1.0) | 2026-10-18 | [First release]({% link _getting_started/whats-new-in-0-1.md %}): AG-UI event mapping, approval and resume, cancellation, reconnect, and the optional Rails adapter |
21
+
22
+ To move an application from one release to the next, follow [Upgrading]({% link _reference/upgrading.md %}).
@@ -0,0 +1,54 @@
1
+ ---
2
+ layout: default
3
+ title: Releasing
4
+ nav_order: 10
5
+ description: Verify an installed gem artifact and publish its matching version tag through RubyGems trusted publishing.
6
+ ---
7
+
8
+ # {{ page.title }}
9
+
10
+ {{ page.description }}
11
+ {: .fs-6 .fw-300 }
12
+
13
+ After reading this guide, you will know:
14
+
15
+ * How to check a release artifact outside the checkout.
16
+ * Which settings bind RubyGems to the release workflow.
17
+ * How a version tag publishes the gem and its documentation.
18
+
19
+ ## Verify the Package
20
+
21
+ ```bash
22
+ bundle exec rake
23
+ bundle exec bundle-audit check --update
24
+ bundle exec rake build
25
+ bundle exec ruby bin/verify-package --rails --audit
26
+ docs/bin/build.sh
27
+ ```
28
+
29
+ The package check installs dependencies in a fresh temporary gem home and requires the installed artifact from outside the checkout. It verifies the core entrypoint, shipped templates and guides, and input parsing. With `--rails`, it invokes the documented generator and migrates its table up and down in SQLite. With `--audit`, it checks the actually activated core dependencies against the advisory database.
30
+
31
+ These checks make no live provider call. Keep provider and client interoperability evidence in [Compatibility]({% link _reference/compatibility.md %}).
32
+
33
+ ## Configure Trusted Publishing
34
+
35
+ Before the first tag, register a pending trusted publisher on RubyGems with gem name `ruby_llm-ag_ui`, GitHub owner `yorzi`, repository `ruby_llm-ag-ui`, workflow `release.yml`, and environment `release`. The workflow uses a short-lived GitHub OIDC identity instead of a stored RubyGems API key.
36
+
37
+ ## Prepare the Release Commit
38
+
39
+ Match the version in `lib/ruby_llm/ag_ui/version.rb` and `docs/_config.yml`. Set `released: true`, add a dated CHANGELOG section, update [Release History]({% link _reference/release-history.md %}), and record upgrade steps in [Upgrading]({% link _reference/upgrading.md %}). For a minor release, add its What's New guide.
40
+
41
+ Push the completed commit to `main` and wait for CI to pass. Tag that exact commit:
42
+
43
+ ```bash
44
+ git tag -a v{{ site.version }} -m "Release {{ site.version }}"
45
+ git push origin v{{ site.version }}
46
+ ```
47
+
48
+ Replace the example tag with the version you are releasing. The `Release Gem` workflow rejects a tag that differs from the gem or docs version, or lacks a CHANGELOG section. It runs offline checks, the advisory audit, and isolated package verification before publishing.
49
+
50
+ ## Confirm Publication
51
+
52
+ On success, the workflow creates a GitHub Release with the gem, checksum, and CHANGELOG notes. The Docs workflow runs after the release workflow succeeds. It publishes the tagged version at `/` and current `main` at `/next/`.
53
+
54
+ Check the version on RubyGems, the GitHub Release assets, and the documentation installation command. If a publish attempt fails, inspect its logs before retrying. A published RubyGems version cannot be overwritten.