opencode-ruby 0.0.1.alpha6 → 0.0.1.alpha9

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: ebac8d73ce010347ee830b23a1280b45a6ff71effd1109aad787743ec60fae45
4
- data.tar.gz: c815940b8e87bcd5724535e987833e360ee9a725081f753cd451f5ae6938f70d
3
+ metadata.gz: d0723db2b0d77a65c6718f0dcc49b67d298a62c33996169a697eb47c9436980a
4
+ data.tar.gz: c0446dbb4539a3432e6d0b71f4790b6c16377ac8aad32406c6f178b04c1dbf54
5
5
  SHA512:
6
- metadata.gz: 902785c4c78de0f30affcb638bec979844f2e1481cabbb4ce3a2a0fc23ca8d384ed6e2cdbd1e49da7050599728dfb619b1eb7b7c3f011e7b18a793a00e00ca40
7
- data.tar.gz: 7e7cc7a596386dd3846dbe1baa91f3cbeba049e46bc8748e05823375ddd07b145849a217ef249d97ff732d3e9f90c3e3e2de1ee7b6ba2021ef30e8cd04bf362d
6
+ metadata.gz: 73205b52ce2f279b8daee9f7274f52d3ff2382cba757f8af2cfce0d2addb0e67c1bf7b684f6575b9161af53678bccfd0ff40c154afe2336b4202fff5f2085f7a
7
+ data.tar.gz: 98d7e60949dcad273ab8533c4652902084f0df117cb52ec522ee7a9cdf8bf35906093479634350b395ea1159d9d881e1b683c70a53fd7be5f5815d4d96cb0d84
data/CHANGELOG.md CHANGED
@@ -1,5 +1,49 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.0.1.alpha9 - 2026-07-20
4
+
5
+ ### Fixed
6
+
7
+ - Parse SSE events with CRLF, CR-only, or LF framing; accept `data:` fields
8
+ with or without the optional leading space; join multiple `data:` fields;
9
+ ignore the stream's optional leading UTF-8 BOM and comment fields; and decode
10
+ correctly when a transport chunk splits any byte of the event framing.
11
+ - Keep request instrumentation free of query credentials and private workspace
12
+ paths.
13
+ - Load non-empty todo events and written-file artifacts in standalone Ruby
14
+ clients.
15
+ - Bound reconnects after an interactive prompt loses its event stream, handle
16
+ DNS failures as transient stream errors, and map non-404 4xx responses to
17
+ `BadRequestError` consistently.
18
+ - Retry the Rails recovery recipe with its replacement session and keep
19
+ exception details out of user-facing message content.
20
+
21
+ ### Changed
22
+
23
+ - Test Ruby 3.2, 3.3, 3.4, and 4.0; pin every third-party CI and release
24
+ action to an exact reviewed commit; and use Ruby 4.0 for release builds.
25
+ - Fail the trusted-publishing job before release when the pushed tag does not
26
+ match `Opencode::VERSION`.
27
+
28
+ ## 0.0.1.alpha8 - 2026-07-20
29
+
30
+ ### Yanked
31
+
32
+ - Published from the unrepaired alpha8 source by mistake and yanked the same
33
+ day. Use alpha9, which contains the intended alpha8 fixes and release gates.
34
+
35
+ ## 0.0.1.alpha7 - 2026-07-18
36
+
37
+ ### Fixed
38
+
39
+ - Add an at-most-once `on_subscribed` hook to the lower-level
40
+ `Opencode::Client#stream_events` API. Higher-level orchestrators can now
41
+ wait for `server.connected` before submitting `prompt_async` without
42
+ abandoning their own Reply observers, persistence, or recovery pipeline.
43
+ - Propagate subscription-hook failures directly and never invoke the hook
44
+ again on SSE reconnect. Ambiguous prompt transport failures therefore
45
+ cannot silently become duplicate turns.
46
+
3
47
  ## 0.0.1.alpha6 - 2026-07-18
4
48
 
5
49
  ### Fixed
@@ -88,5 +132,7 @@ First public alpha. HTTP + SSE client for OpenCode REST API.
88
132
  ### Compatibility
89
133
 
90
134
  - Ruby ≥ 3.2
91
- - OpenCode server 1.15 (tested against the message bus schema in `packages/opencode/src/session/message-v2.ts`)
135
+ - OpenCode targeted the then-current 1.15 message-bus shape. This historical
136
+ target was not a blanket SemVer compatibility guarantee; use the README's
137
+ current compatibility evidence for deployment decisions.
92
138
  - Runtime dependency: `activesupport (>= 6.1)` for `blank?`/`present?`/`presence`/`truncate`/`duplicable?`/`megabytes`. ActiveSupport is *not* Rails — it's a standalone helpers gem.
data/README.md CHANGED
@@ -67,7 +67,9 @@ child = client.create_session(
67
67
 
68
68
  Model strings use OpenCode's `provider/model` form; a preformatted model hash
69
69
  with `providerID` and `id` keys is also accepted. These configured-session
70
- fields require OpenCode 1.16.1 or newer.
70
+ fields first appeared in OpenCode 1.16.1. That is an API-introduction note,
71
+ not evidence that every later server is compatible; use the
72
+ [certified compatibility evidence](#compatibility) for deployment choices.
71
73
 
72
74
  ### Streaming (the headline)
73
75
 
@@ -106,8 +108,9 @@ client.update_session(session_id, permissions: permission_rules)
106
108
 
107
109
  OpenCode appends PATCHed permission rules and evaluates the last matching
108
110
  rule. Hosts should send a complete ordered policy and fingerprint it so the
109
- same policy is not appended on every turn. This endpoint requires OpenCode
110
- 1.16.1 or newer; the rest of the client remains compatible with 1.15.
111
+ same policy is not appended on every turn. This endpoint first appeared in
112
+ OpenCode 1.16.1; that version floor describes endpoint availability, not
113
+ compatibility with every later server.
111
114
 
112
115
  ### Lower-level event firehose
113
116
 
@@ -119,6 +122,23 @@ client.stream_events(session_id: session_id) do |event|
119
122
  end
120
123
  ```
121
124
 
125
+ Orchestrators that submit their own async prompt must do so through
126
+ `on_subscribed`; the callback runs only after the first `server.connected`
127
+ frame and at most once across automatic reconnects:
128
+
129
+ ```ruby
130
+ client.stream_events(
131
+ session_id: session_id,
132
+ on_subscribed: -> { client.send_message_async(session_id, prompt) }
133
+ ) do |event|
134
+ reply.apply(event)
135
+ end
136
+ ```
137
+
138
+ If the callback raises, `stream_events` propagates that error and does not
139
+ retry it. This is intentional: a timed-out prompt response is ambiguous, so
140
+ reposting could duplicate the model turn and its cost.
141
+
122
142
  ### Interactive prompts
123
143
 
124
144
  When the agent uses the `question` or `permission` tools, opencode emits `question.asked` / `permission.asked` events. Answer them via:
@@ -182,8 +202,29 @@ Want every OpenCode endpoint auto-generated from the OpenAPI spec? Use [`opencod
182
202
  ## Compatibility
183
203
 
184
204
  - Ruby ≥ 3.2
185
- - OpenCode server 1.15
186
- - Runtime dependency: `activesupport (>= 6.1)` *not* Rails. ActiveSupport is a standalone helpers gem (`blank?`, `present?`, `presence`, `truncate`, etc.).
205
+ - Runtime dependency: `activesupport (>= 6.1, < 9.0)` — *not* Rails. ActiveSupport is a standalone helpers gem (`blank?`, `present?`, `presence`, `truncate`, etc.).
206
+ - Runtime dependency: `marcel (~> 1.0)` for artifact MIME type detection.
207
+
208
+ OpenCode server compatibility is evidence-based, not an open-ended SemVer
209
+ promise. OpenCode's HTTP, SSE, and runtime behavior can change independently
210
+ of a numeric version floor, so this gem does not infer compatibility from a
211
+ constraint such as `>= 1.15`.
212
+
213
+ Use the [OpenCode compatibility corpus](https://github.com/ajaynomics/opencode-compat)
214
+ as the source of current evidence. Its
215
+ [image matrix](https://github.com/ajaynomics/opencode-compat/blob/main/manifests/image-matrix.json)
216
+ records the exact gem commit and immutable OpenCode OCI digest exercised by
217
+ the `ruby-rest-sse` profile. Its
218
+ [runtime tuples](https://github.com/ajaynomics/opencode-compat/blob/main/manifests/runtime-tuples.json)
219
+ add the exact consumer commit and canary evidence needed for promotion and
220
+ rollback. A version label or image tag is provenance only, never the execution
221
+ coordinate.
222
+
223
+ A combination absent from passing evidence is **unverified**, not necessarily
224
+ incompatible. Run the corpus against the exact gem, image digest, and consumer
225
+ commit before adopting or promoting it. See the
226
+ [certification policy](https://github.com/ajaynomics/opencode-compat/blob/main/docs/certification.md)
227
+ for the full process.
187
228
 
188
229
  ## Development
189
230
 
@@ -196,10 +237,14 @@ The smoke suite covers Client end-to-end against WebMock-stubbed OpenCode
196
237
  endpoints, including subscription-before-prompt ordering and
197
238
  reconnect-without-repost.
198
239
 
199
- Releases use RubyGems trusted publishing. After the repository's
200
- `release.yml` workflow is registered as a trusted publisher with the `release`
201
- environment, pushing a `v*` tag builds, attests, and publishes the gem without
202
- a long-lived RubyGems API key.
240
+ The mistakenly published `0.0.1.alpha8` package contained unrepaired source and
241
+ was yanked; use `0.0.1.alpha9` or later. The repository contains a tag-triggered
242
+ `release.yml` workflow intended for RubyGems trusted publishing, but its
243
+ trusted-publisher registration is not confirmed for alpha9. A `v*` tag push
244
+ therefore does not currently guarantee publication. Before a future release,
245
+ verify the RubyGems registry result explicitly. Once the workflow is registered
246
+ as a trusted publisher for the `release` environment, it can build, attest, and
247
+ publish without a long-lived RubyGems API key.
203
248
 
204
249
  ## License
205
250
 
@@ -128,12 +128,13 @@ class GenerateAssistantReplyJob < ApplicationJob
128
128
  end
129
129
  rescue Opencode::SessionNotFoundError, Opencode::StaleSessionError
130
130
  raise if attempted_recreate
131
- message.conversation.recreate_opencode_session!(client)
131
+ session_id = message.conversation.recreate_opencode_session!(client)
132
132
  attempted_recreate = true
133
133
  retry
134
134
  end
135
135
  rescue StandardError => e
136
- message&.update!(status: :errored, content: "An error occurred: #{e.message.truncate(200)}")
136
+ Rails.logger.error(e.full_message)
137
+ message&.update!(status: :errored, content: "An error occurred. Please try again.")
137
138
  end
138
139
 
139
140
  private
@@ -230,18 +230,12 @@ module Opencode
230
230
  request = Net::HTTP::Get.new(uri)
231
231
  add_auth_header(request)
232
232
 
233
- response = Opencode::Instrumentation.instrument("opencode.request", method: request.method, path: request.path) do
233
+ response = Opencode::Instrumentation.instrument("opencode.request", method: request.method, path: uri.path) do
234
234
  http_client.request(request)
235
235
  end
236
236
 
237
- unless response.code.to_i.between?(200, 299)
238
- raise ServerError, "list_questions failed: HTTP #{response.code} — #{response.body.to_s[0, 200]}"
239
- end
240
-
241
- return [] if response.body.blank?
242
- JSON.parse(response.body, symbolize_names: true)
243
- rescue JSON::ParserError => e
244
- raise ServerError, "list_questions returned invalid JSON: #{e.message}"
237
+ body = handle_response(response)
238
+ body.is_a?(Array) ? body : []
245
239
  rescue Net::OpenTimeout, Net::ReadTimeout, Net::WriteTimeout => e
246
240
  raise TimeoutError, "OpenCode timeout after #{@timeout}s: #{e.message}"
247
241
  rescue Errno::ECONNREFUSED, SocketError => e
@@ -255,6 +249,10 @@ module Opencode
255
249
  end
256
250
 
257
251
  MAX_SSE_BUFFER = 1_048_576 # 1 MB — safety valve against pathological server responses
252
+ # Keep CRLF atomic so it cannot backtrack into CR + LF and look like a
253
+ # blank line. SSE also permits bare CR and LF line endings.
254
+ SSE_EVENT_BOUNDARY = /(?>\r\n|[\r\n]){2}/
255
+ UTF8_BOM = "\xEF\xBB\xBF".b.freeze
258
256
  SSE_RECONNECT_DELAY = 0.1
259
257
  TRANSIENT_SSE_ERRORS = [
260
258
  EOFError,
@@ -263,7 +261,8 @@ module Opencode
263
261
  Net::ReadTimeout,
264
262
  Errno::ECONNREFUSED,
265
263
  Errno::ECONNRESET,
266
- Errno::EPIPE
264
+ Errno::EPIPE,
265
+ SocketError
267
266
  ].freeze
268
267
 
269
268
  # Opens SSE connection to GET /event, yields parsed events filtered by session_id.
@@ -285,6 +284,13 @@ module Opencode
285
284
  # without nuking real reasoning. Callers that know their agent is
286
285
  # short-prompt + fast can pass a lower value.
287
286
  #
287
+ # on_subscribed: optional callable invoked at most once, after the first
288
+ # `server.connected` frame proves the SSE response body is flowing. This
289
+ # is the safe place for higher-level orchestrators to submit prompt_async;
290
+ # reconnects wait for their own connected frame but never invoke it again.
291
+ # A raised callback error is propagated directly and is never treated as
292
+ # a reconnectable SSE transport failure.
293
+ #
288
294
  # idle_stream_timeout: seconds to wait BETWEEN meaningful events once
289
295
  # the session has started producing them. Default nil = no check
290
296
  # (preserves the overall `timeout` ceiling behavior). Opt-in heartbeat
@@ -298,7 +304,7 @@ module Opencode
298
304
  # translate into a user-visible error / retry affordance.
299
305
  def stream_events(session_id:, timeout: 600, first_event_timeout: 120,
300
306
  idle_stream_timeout: nil,
301
- reply: nil, on_activity_tick: nil, &block)
307
+ reply: nil, on_activity_tick: nil, on_subscribed: nil, &block)
302
308
  consume_event_stream(
303
309
  session_id: session_id,
304
310
  timeout: timeout,
@@ -306,6 +312,7 @@ module Opencode
306
312
  idle_stream_timeout: idle_stream_timeout,
307
313
  reply: reply,
308
314
  on_activity_tick: on_activity_tick,
315
+ on_subscribed: on_subscribed,
309
316
  &block
310
317
  )
311
318
  end
@@ -318,10 +325,11 @@ module Opencode
318
325
  first_event_deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + first_event_timeout
319
326
  received_session_event = false
320
327
  last_meaningful_event_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
328
+ subscription_callback_attempted = on_subscribed.nil?
321
329
 
322
330
  loop do
323
331
  now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
324
- deadline = check_deadline_or_suspend(now, deadline, timeout, reply)
332
+ deadline = check_deadline(now, deadline, timeout)
325
333
 
326
334
  # NOTE: first_event_deadline is *not* suspension-eligible. If the agent
327
335
  # never gets started we want to fail fast — a session that's blocked on
@@ -348,9 +356,11 @@ module Opencode
348
356
  http.read_timeout = 30
349
357
 
350
358
  subscription_callback_error = nil
359
+ event_callback_error = nil
351
360
  subscription_ready = on_subscribed.nil?
352
361
  begin
353
362
  buffer = String.new
363
+ first_sse_event = true
354
364
 
355
365
  http.request(request) do |response|
356
366
  unless response.is_a?(Net::HTTPSuccess)
@@ -377,9 +387,14 @@ module Opencode
377
387
  raise ServerError, "SSE buffer exceeded #{MAX_SSE_BUFFER} bytes"
378
388
  end
379
389
 
380
- while (idx = buffer.index("\n\n"))
381
- raw_event = buffer.slice!(0, idx + 2)
382
- event = parse_sse_event(raw_event, session_id)
390
+ while (boundary = buffer.match(SSE_EVENT_BOUNDARY))
391
+ raw_event = buffer.slice!(0, boundary.end(0))
392
+ event = parse_sse_event(
393
+ raw_event,
394
+ session_id,
395
+ allow_bom: first_sse_event
396
+ )
397
+ first_sse_event = false
383
398
  next unless event
384
399
 
385
400
  unless subscription_ready
@@ -390,14 +405,22 @@ module Opencode
390
405
  # available readiness handshake before prompting.
391
406
  next unless event[:type] == "server.connected"
392
407
 
393
- begin
394
- turn_started = on_subscribed.call
395
- rescue StandardError => error
396
- # Prompt submission happens inside the open SSE response.
397
- # Do not mistake its transport failure for an SSE disconnect
398
- # and hide it behind a reconnect/first-event timeout.
399
- subscription_callback_error = error
400
- raise
408
+ turn_started = false
409
+ unless subscription_callback_attempted
410
+ # Mark the attempt before invoking the callback. A timeout
411
+ # can mean the prompt reached OpenCode even though its HTTP
412
+ # response did not reach us; retrying would duplicate the
413
+ # turn. The caller receives the original error instead.
414
+ subscription_callback_attempted = true
415
+ begin
416
+ turn_started = on_subscribed.call
417
+ rescue StandardError => error
418
+ # Prompt submission happens inside the open SSE response.
419
+ # Do not mistake its transport failure for an SSE disconnect
420
+ # and hide it behind a reconnect/first-event timeout.
421
+ subscription_callback_error = error
422
+ raise
423
+ end
401
424
  end
402
425
  if turn_started
403
426
  # Before this fix stream_events began only after the prompt
@@ -421,14 +444,19 @@ module Opencode
421
444
  # that's the whole point: a healthy long wait (user thinking
422
445
  # for 30 minutes) keeps the container warm via heartbeats so
423
446
  # the reaper doesn't kill it mid-wait.
424
- on_activity_tick&.call(event)
425
- block.call(event)
447
+ begin
448
+ on_activity_tick&.call(event)
449
+ block.call(event)
450
+ rescue StandardError => error
451
+ event_callback_error = error
452
+ raise
453
+ end
426
454
  return if terminal_session_event?(event)
427
455
  end
428
456
  end
429
457
  end
430
458
  rescue *TRANSIENT_SSE_ERRORS
431
- raise if subscription_callback_error
459
+ raise if subscription_callback_error || event_callback_error
432
460
 
433
461
  # Treat transport-level SSE disconnects like clean EOF: reconnect
434
462
  # until an idle session event, the overall timeout, or first-event
@@ -487,6 +515,11 @@ module Opencode
487
515
  # the prompt resolves. Otherwise apply the normal deadline check.
488
516
  def check_deadline_or_suspend(now, deadline, timeout, reply)
489
517
  return now + timeout if reply&.prompt_blocked?
518
+
519
+ check_deadline(now, deadline, timeout)
520
+ end
521
+
522
+ def check_deadline(now, deadline, timeout)
490
523
  raise TimeoutError, "SSE stream timed out after #{timeout}s" if now > deadline
491
524
 
492
525
  deadline
@@ -601,7 +634,7 @@ module Opencode
601
634
  add_auth_header(request)
602
635
 
603
636
  response = nil
604
- result = Opencode::Instrumentation.instrument("opencode.request", method: request.method, path: request.path) do
637
+ result = Opencode::Instrumentation.instrument("opencode.request", method: request.method, path: request.uri.path) do
605
638
  response = http_client.request(request)
606
639
  handle_response(response)
607
640
  end
@@ -622,11 +655,22 @@ module Opencode
622
655
  end
623
656
  end
624
657
 
625
- def parse_sse_event(raw, session_id)
626
- data_line = raw.lines.find { |l| l.start_with?("data: ") }
627
- return nil unless data_line
658
+ def parse_sse_event(raw, session_id, allow_bom: false)
659
+ if allow_bom && raw.b.start_with?(UTF8_BOM)
660
+ raw = raw.byteslice(UTF8_BOM.bytesize..)
661
+ end
628
662
 
629
- json = JSON.parse(data_line.sub("data: ", "").strip, symbolize_names: true)
663
+ data = raw.split(/\r\n|\r|\n/, -1).filter_map do |line|
664
+ next if line.start_with?(":")
665
+
666
+ field, value = line.split(":", 2)
667
+ next unless field == "data"
668
+
669
+ (value || "").delete_prefix(" ")
670
+ end
671
+ return nil if data.empty?
672
+
673
+ json = JSON.parse(data.join("\n"), symbolize_names: true)
630
674
 
631
675
  event_session = json.dig(:properties, :sessionID) ||
632
676
  json.dig(:properties, :info, :sessionID) ||
@@ -642,23 +686,30 @@ module Opencode
642
686
  end
643
687
 
644
688
  def handle_response(response)
645
- return {} if response.code.to_i == 204
689
+ status = response.code.to_i
690
+ return {} if status == 204
646
691
 
647
- body = if response.body.present?
648
- JSON.parse(response.body, symbolize_names: true)
649
- else
650
- {}
651
- end
692
+ body = parse_response_body(response, status)
652
693
 
653
- case response.code.to_i
694
+ case status
654
695
  when 200..299 then body
655
- when 400 then raise BadRequestError.new(error_message(body, "Bad request"), response: body)
656
696
  when 404 then raise SessionNotFoundError.new(error_message(body, "Session not found"), response: body)
697
+ when 400..499 then raise BadRequestError.new(error_message(body, "Bad request"), response: body)
657
698
  when 500..599 then raise ServerError.new(error_message(body, "Server error"), response: body)
658
699
  else raise Error.new("Unexpected response: #{response.code}", response: body)
659
700
  end
701
+ end
702
+
703
+ def parse_response_body(response, status)
704
+ return {} if response.body.blank?
705
+
706
+ JSON.parse(response.body, symbolize_names: true)
660
707
  rescue JSON::ParserError
661
- raise ServerError.new("Invalid JSON from OpenCode (HTTP #{response.code}): #{response.body&.truncate(200)}")
708
+ if status.between?(200, 299)
709
+ raise ServerError.new("Invalid JSON from OpenCode (HTTP #{response.code}): #{response.body&.truncate(200)}")
710
+ end
711
+
712
+ { message: response.body.to_s.truncate(200) }
662
713
  end
663
714
 
664
715
  # OpenCode HTTP error bodies use a wrapped shape: { name:, data: { message:, kind?: } }.
@@ -666,6 +717,9 @@ module Opencode
666
717
  # `body[:message]` is no longer populated for errors — only `body[:data][:message]`.
667
718
  # We read both to keep older mock servers working in tests.
668
719
  def error_message(body, fallback)
720
+ return body.truncate(200) if body.is_a?(String) && body.present?
721
+ return fallback unless body.is_a?(Hash)
722
+
669
723
  body.dig(:data, :message) || body[:message] || fallback
670
724
  end
671
725
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Opencode
4
- VERSION = "0.0.1.alpha6"
4
+ VERSION = "0.0.1.alpha9"
5
5
  end
data/lib/opencode-ruby.rb CHANGED
@@ -6,8 +6,10 @@
6
6
  # non-Rails apps.
7
7
  require "active_support/core_ext/object/blank" # provides blank?, present?, presence
8
8
  require "active_support/core_ext/object/duplicable"
9
+ require "active_support/core_ext/hash/keys" # provides Hash#deep_stringify_keys
9
10
  require "active_support/core_ext/string/filters" # provides String#truncate
10
11
  require "active_support/core_ext/numeric/bytes" # provides Integer#megabytes
12
+ require "marcel"
11
13
 
12
14
  require_relative "opencode/version"
13
15
  require_relative "opencode/error"
@@ -30,14 +30,10 @@ Gem::Specification.new do |spec|
30
30
  %w[README.md LICENSE CHANGELOG.md opencode-ruby.gemspec]
31
31
  spec.require_paths = ["lib"]
32
32
 
33
- # The only runtime dependency is ActiveSupport (NOT Rails). ActiveSupport
34
- # is a standalone gem providing the `present?`/`blank?`/`presence`/
35
- # `truncate`/`duplicable?` helpers used in this gem's code. It does NOT
36
- # pull in ActiveRecord, ActionView, ActionController, Turbo, or any other
37
- # Rails-only piece. Most Ruby apps in the wild already have ActiveSupport
38
- # transitively via another gem; in the rare case yours doesn't, ~250 LOC
39
- # of core_ext is added when this gem installs.
33
+ # ActiveSupport supplies the small set of core extensions used by the
34
+ # client without pulling in Rails. Marcel identifies artifact content types.
40
35
  spec.add_runtime_dependency "activesupport", ">= 6.1", "< 9.0"
36
+ spec.add_runtime_dependency "marcel", "~> 1.0"
41
37
 
42
38
  spec.add_development_dependency "minitest", "~> 5.20"
43
39
  spec.add_development_dependency "rake", "~> 13.0"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: opencode-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1.alpha6
4
+ version: 0.0.1.alpha9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ajay Krishnan
@@ -29,6 +29,20 @@ dependencies:
29
29
  - - "<"
30
30
  - !ruby/object:Gem::Version
31
31
  version: '9.0'
32
+ - !ruby/object:Gem::Dependency
33
+ name: marcel
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - "~>"
37
+ - !ruby/object:Gem::Version
38
+ version: '1.0'
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - "~>"
44
+ - !ruby/object:Gem::Version
45
+ version: '1.0'
32
46
  - !ruby/object:Gem::Dependency
33
47
  name: minitest
34
48
  requirement: !ruby/object:Gem::Requirement