muxi 0.20260324.0 → 0.20260408.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: 1b7a5f6ea9042b22c4d281c5b3f03e12b691c0b9cfd9dda8406eaff2ae2cd1b0
4
- data.tar.gz: 32a561512b719203e28083122f756a45b06647d2769df08ab9ded4e0119d645a
3
+ metadata.gz: 1741eb77395142b7aa929981ed4d43ebc2a82cbd67b2ededb3ab12a20692290d
4
+ data.tar.gz: a7457ec97f4bea234c3a27be22ff4f5f0f83d582027c5792472555608af26273
5
5
  SHA512:
6
- metadata.gz: 15cc7e1d48c34a8be769f64f78537a27492a7c115f1de74f3dd5d2ca7842d8fe0a29f980bf260965958936c700d373c12750da2a49f29dd36ffb4f0d3f3486b6
7
- data.tar.gz: 69e6062c9d85a3be135f3c405712a8042e989efc7e54ca01f255e1bdead06edcdf37207e7ccea144921a67f1f3818aaf956802c49bdb60f3307a60b551df5d01
6
+ metadata.gz: 5f8d7fa638ae3ef2eb16accacda65ff417f1f241980069d626f4e08a3b2f0aea47654957b07b4dd1e1b058bd6c3dc1d71f03663d884437c813d792b4a0094cff
7
+ data.tar.gz: dcaf114006f7deb58db5de30e48ea2dc631220b953bef9a07964ed70bbdbe70c3cd9cac9fc31ec071f97cb02d1a6396777dd4f2509e211e06d9cb9639639a894
@@ -98,34 +98,99 @@ module Muxi
98
98
 
99
99
  event = nil
100
100
  data_parts = []
101
+ buffer = +""
101
102
 
102
103
  http.request(request) do |response|
103
104
  response.read_body do |chunk|
104
- chunk.each_line do |raw_line|
105
- line = raw_line.chomp
106
- next if line.start_with?(":")
107
-
108
- if line.empty?
109
- if data_parts.any?
110
- yield({ "event" => event || "message", "data" => data_parts.join("\n") })
111
- end
112
- event = nil
113
- data_parts = []
114
- next
115
- end
116
-
117
- if line.start_with?("event:")
118
- event = line[6..].strip
119
- elsif line.start_with?("data:")
120
- data_parts << line[5..].strip
121
- end
105
+ buffer << chunk
106
+ while (newline = buffer.index("\n"))
107
+ raw_line = buffer.slice!(0..newline)
108
+ line = raw_line.delete_suffix("\n").delete_suffix("\r")
109
+ parsed = consume_sse_line(line, event, data_parts)
110
+ event = parsed[:event]
111
+ data_parts = parsed[:data_parts]
112
+ next unless parsed[:emitted]
113
+
114
+ throw_if_route_error(parsed[:emitted])
115
+ yield(parsed[:emitted])
122
116
  end
123
117
  end
124
118
  end
119
+
120
+ unless buffer.empty?
121
+ parsed = consume_sse_line(buffer.delete_suffix("\r"), event, data_parts)
122
+ event = parsed[:event]
123
+ data_parts = parsed[:data_parts]
124
+ if parsed[:emitted]
125
+ throw_if_route_error(parsed[:emitted])
126
+ yield(parsed[:emitted])
127
+ end
128
+ end
129
+
130
+ emitted = flush_sse_event(event, data_parts)
131
+ if emitted
132
+ throw_if_route_error(emitted)
133
+ yield(emitted)
134
+ end
125
135
  end
126
136
 
127
137
  private
128
138
 
139
+ def consume_sse_line(line, event, data_parts)
140
+ return { event: event, data_parts: data_parts, emitted: nil } if line.start_with?(":")
141
+
142
+ if line.empty?
143
+ return {
144
+ event: nil,
145
+ data_parts: [],
146
+ emitted: flush_sse_event(event, data_parts)
147
+ }
148
+ end
149
+
150
+ field, value = split_sse_field(line)
151
+ if field == "event"
152
+ event = value
153
+ elsif field == "data"
154
+ data_parts = data_parts + [value]
155
+ end
156
+
157
+ { event: event, data_parts: data_parts, emitted: nil }
158
+ end
159
+
160
+ def flush_sse_event(event, data_parts)
161
+ return nil if event.nil? && data_parts.empty?
162
+
163
+ { "event" => event || "message", "data" => data_parts.join("\n") }
164
+ end
165
+
166
+ def throw_if_route_error(event)
167
+ return unless event["event"] == "error"
168
+
169
+ code = "STREAM_ERROR"
170
+ message = event["data"].to_s.empty? ? "stream error" : event["data"].to_s
171
+ details = nil
172
+
173
+ begin
174
+ parsed = JSON.parse(event["data"].to_s)
175
+ if parsed.is_a?(Hash)
176
+ details = parsed
177
+ code = parsed["type"] || parsed["code"] || parsed["error"] || code
178
+ message = parsed["error"] || parsed["message"] || message
179
+ end
180
+ rescue JSON::ParserError
181
+ end
182
+
183
+ raise MuxiError.new(code, message, 0, details)
184
+ end
185
+
186
+ def split_sse_field(line)
187
+ field, value = line.split(":", 2)
188
+ value ||= ""
189
+ value = value[1..] if value.start_with?(" ")
190
+ [field, value]
191
+ end
192
+ private
193
+
129
194
  def build_url(path, params)
130
195
  rel_path = path.start_with?("/") ? path : "/#{path}"
131
196
  query = params&.compact&.map { |k, v| "#{k}=#{URI.encode_www_form_component(v.to_s)}" }&.join("&")
data/lib/muxi/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Muxi
4
- VERSION = "0.20260324.0"
4
+ VERSION = "0.20260408.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: muxi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.20260324.0
4
+ version: 0.20260408.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - MUXI AI
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-03-24 00:00:00.000000000 Z
11
+ date: 2026-04-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64
@@ -103,7 +103,6 @@ extensions: []
103
103
  extra_rdoc_files: []
104
104
  files:
105
105
  - ".rubocop.yml"
106
- - CHANGELOG.md
107
106
  - README.md
108
107
  - Rakefile
109
108
  - SECURITY.md
data/CHANGELOG.md DELETED
@@ -1,14 +0,0 @@
1
- # Changelog
2
-
3
- All notable changes to this project will be documented in this file.
4
-
5
- ## [Unreleased]
6
-
7
- ### Added
8
- - `mode` parameter in `FormationConfig` for draft/live URL routing
9
- - `mode="live"` (default): Uses `/api/{id}/v1` prefix
10
- - `mode="draft"`: Uses `/draft/{id}/v1` prefix for local development with `muxi up`
11
- - SDK version update notifications (via `X-Muxi-SDK-Latest` response header)
12
- - Notifies when newer SDK version available (max once per 12 hours)
13
- - Disable with `MUXI_SDK_VERSION_NOTIFICATION=0`
14
- - Console telemetry support via internal `_app` parameter