ag-ui 0.2.0 → 1.0.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 +4 -4
- data/data/ag_ui.json +1 -1
- data/lib/ag_ui/a2ui/recovery.rb +19 -12
- data/lib/ag_ui/a2ui/validate.rb +150 -89
- data/lib/ag_ui/event_bridge.rb +144 -44
- data/lib/ag_ui/messages.rb +20 -22
- data/lib/ag_ui/middleware/a2ui.rb +109 -82
- data/lib/ag_ui/middleware/forwarded_props.rb +1 -1
- data/lib/ag_ui/middleware/state.rb +366 -0
- data/lib/ag_ui/middleware/system_prompt.rb +1 -1
- data/lib/ag_ui/middleware/tool_router.rb +63 -44
- data/lib/ag_ui/protocol/json_schema/definition.rb +5 -5
- data/lib/ag_ui/protocol/json_schema.rb +18 -14
- data/lib/ag_ui/run_input.rb +1 -1
- data/lib/ag_ui/server/info.rb +10 -10
- data/lib/ag_ui/server/middleware/sse_stream.rb +3 -3
- data/lib/ag_ui/server/sse/event_encoder.rb +11 -11
- data/lib/ag_ui/server/sse/stream.rb +5 -1
- data/lib/ag_ui/server/triage.rb +11 -8
- data/lib/ag_ui/server.rb +14 -10
- data/lib/ag_ui/terminals/ruby_llm.rb +68 -39
- data/lib/ag_ui/version.rb +1 -1
- data/lib/ag_ui.rb +1 -0
- metadata +45 -9
- data/data/generate-ag-ui-schema.py +0 -50
data/lib/ag_ui/server/info.rb
CHANGED
|
@@ -26,19 +26,19 @@ module AgUi
|
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
base = {
|
|
29
|
-
"version"
|
|
30
|
-
"agents"
|
|
29
|
+
"version" => VERSION_PARITY,
|
|
30
|
+
"agents" => { agent_id => agent },
|
|
31
31
|
"audioFileTranscriptionEnabled" => false,
|
|
32
|
-
"mode"
|
|
33
|
-
"threadEndpoints"
|
|
34
|
-
"list"
|
|
35
|
-
"inspect"
|
|
36
|
-
"mutations"
|
|
32
|
+
"mode" => "sse",
|
|
33
|
+
"threadEndpoints" => {
|
|
34
|
+
"list" => false,
|
|
35
|
+
"inspect" => false,
|
|
36
|
+
"mutations" => false,
|
|
37
37
|
"realtimeMetadata" => false,
|
|
38
38
|
},
|
|
39
|
-
"a2uiEnabled"
|
|
40
|
-
"openGenerativeUIEnabled"
|
|
41
|
-
"telemetryDisabled"
|
|
39
|
+
"a2uiEnabled" => a2ui_enabled,
|
|
40
|
+
"openGenerativeUIEnabled" => false,
|
|
41
|
+
"telemetryDisabled" => true,
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
if a2ui_enabled
|
|
@@ -67,9 +67,9 @@ module AgUi
|
|
|
67
67
|
on_event: nil, on_finish: nil, on_task: nil, &block)
|
|
68
68
|
stream = SSE::Stream.new(
|
|
69
69
|
thread_id: thread_id,
|
|
70
|
-
run_id:
|
|
71
|
-
validate:
|
|
72
|
-
on_event:
|
|
70
|
+
run_id: run_id,
|
|
71
|
+
validate: validate,
|
|
72
|
+
on_event: on_event,
|
|
73
73
|
)
|
|
74
74
|
|
|
75
75
|
@env["ag_ui.stream"] = stream
|
|
@@ -23,20 +23,20 @@ module AgUi
|
|
|
23
23
|
|
|
24
24
|
private
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
def strip_nils(value)
|
|
27
|
+
case value
|
|
28
|
+
when Hash
|
|
29
|
+
value.each_with_object({}) do |(k, v), out|
|
|
30
|
+
unless v.nil?
|
|
31
|
+
out[k] = strip_nils(v)
|
|
32
|
+
end
|
|
32
33
|
end
|
|
34
|
+
when Array
|
|
35
|
+
value.map { |v| strip_nils(v) }
|
|
36
|
+
else
|
|
37
|
+
value
|
|
33
38
|
end
|
|
34
|
-
when Array
|
|
35
|
-
value.map { |v| strip_nils(v) }
|
|
36
|
-
else
|
|
37
|
-
value
|
|
38
39
|
end
|
|
39
|
-
end
|
|
40
40
|
end
|
|
41
41
|
end
|
|
42
42
|
end
|
|
@@ -90,7 +90,11 @@ module AgUi
|
|
|
90
90
|
property_defaults = AgUi::Protocol::JsonSchema.raw_schema
|
|
91
91
|
.dig("definitions", definition_name, "properties")
|
|
92
92
|
.each_with_object({}) do |(camel, prop), defaults|
|
|
93
|
-
|
|
93
|
+
if prop["default"].nil?
|
|
94
|
+
value = prop["const"]
|
|
95
|
+
else
|
|
96
|
+
value = prop["default"]
|
|
97
|
+
end
|
|
94
98
|
unless value.nil?
|
|
95
99
|
defaults[camel] = value
|
|
96
100
|
end
|
data/lib/ag_ui/server/triage.rb
CHANGED
|
@@ -45,7 +45,7 @@ module AgUi
|
|
|
45
45
|
@app.call(
|
|
46
46
|
env.merge(
|
|
47
47
|
"ag_ui.operation" => "stop",
|
|
48
|
-
"ag_ui.agent_id"
|
|
48
|
+
"ag_ui.agent_id" => m[:agent_id],
|
|
49
49
|
"ag_ui.thread_id" => m[:thread_id],
|
|
50
50
|
),
|
|
51
51
|
)
|
|
@@ -66,8 +66,8 @@ module AgUi
|
|
|
66
66
|
@app.call(
|
|
67
67
|
env.merge(
|
|
68
68
|
"ag_ui.operation" => "run",
|
|
69
|
-
"ag_ui.agent_id"
|
|
70
|
-
"ag_ui.input"
|
|
69
|
+
"ag_ui.agent_id" => "default",
|
|
70
|
+
"ag_ui.input" => input,
|
|
71
71
|
),
|
|
72
72
|
)
|
|
73
73
|
rescue RunInput::InvalidError => e
|
|
@@ -80,8 +80,8 @@ module AgUi
|
|
|
80
80
|
@app.call(
|
|
81
81
|
env.merge(
|
|
82
82
|
"ag_ui.operation" => match[:action],
|
|
83
|
-
"ag_ui.agent_id"
|
|
84
|
-
"ag_ui.input"
|
|
83
|
+
"ag_ui.agent_id" => match[:agent_id],
|
|
84
|
+
"ag_ui.input" => input,
|
|
85
85
|
),
|
|
86
86
|
)
|
|
87
87
|
rescue RunInput::InvalidError => e
|
|
@@ -109,17 +109,20 @@ module AgUi
|
|
|
109
109
|
"access-control-allow-origin" => "*",
|
|
110
110
|
"access-control-allow-methods" => "GET, POST, OPTIONS",
|
|
111
111
|
"access-control-allow-headers" => "*",
|
|
112
|
-
}, []
|
|
112
|
+
}, []
|
|
113
|
+
]
|
|
113
114
|
end
|
|
114
115
|
|
|
115
116
|
def bad_request(details)
|
|
116
117
|
[400, { "content-type" => "application/json" },
|
|
117
|
-
[JSON.generate({ "error" => "Invalid request body", "details" => details })]
|
|
118
|
+
[JSON.generate({ "error" => "Invalid request body", "details" => details })]
|
|
119
|
+
]
|
|
118
120
|
end
|
|
119
121
|
|
|
120
122
|
def not_found
|
|
121
123
|
[404, { "content-type" => "application/json" },
|
|
122
|
-
[JSON.generate({ "error" => "Not found" })]
|
|
124
|
+
[JSON.generate({ "error" => "Not found" })]
|
|
125
|
+
]
|
|
123
126
|
end
|
|
124
127
|
end
|
|
125
128
|
end
|
data/lib/ag_ui/server.rb
CHANGED
|
@@ -45,10 +45,10 @@ module AgUi
|
|
|
45
45
|
|
|
46
46
|
@agent_id = agent_id
|
|
47
47
|
@info = Info.payload(
|
|
48
|
-
agent_id:
|
|
49
|
-
description:
|
|
48
|
+
agent_id: agent_id,
|
|
49
|
+
description: description,
|
|
50
50
|
a2ui_enabled: a2ui_enabled,
|
|
51
|
-
overrides:
|
|
51
|
+
overrides: info_overrides,
|
|
52
52
|
)
|
|
53
53
|
@validate = validate
|
|
54
54
|
@store = store
|
|
@@ -116,8 +116,8 @@ module AgUi
|
|
|
116
116
|
thread_id = input&.thread_id.to_s
|
|
117
117
|
stream = SSE::Stream.new(
|
|
118
118
|
thread_id: thread_id,
|
|
119
|
-
run_id:
|
|
120
|
-
validate:
|
|
119
|
+
run_id: input&.run_id.to_s,
|
|
120
|
+
validate: false,
|
|
121
121
|
)
|
|
122
122
|
|
|
123
123
|
if @store
|
|
@@ -150,7 +150,11 @@ module AgUi
|
|
|
150
150
|
# Cancel the thread's in-flight run (the store stops its Async
|
|
151
151
|
# task; the stream closes via the builder's ensure) and ack.
|
|
152
152
|
def respond_stop(env)
|
|
153
|
-
|
|
153
|
+
if @store
|
|
154
|
+
stopped = @store.stop(env["ag_ui.thread_id"].to_s)
|
|
155
|
+
else
|
|
156
|
+
stopped = false
|
|
157
|
+
end
|
|
154
158
|
[200, JSON_HEADERS.dup, [JSON.generate({ "stopped" => stopped })]]
|
|
155
159
|
end
|
|
156
160
|
|
|
@@ -166,11 +170,11 @@ module AgUi
|
|
|
166
170
|
|
|
167
171
|
@builder.open(
|
|
168
172
|
thread_id: thread_id,
|
|
169
|
-
run_id:
|
|
170
|
-
validate:
|
|
171
|
-
on_event:
|
|
173
|
+
run_id: run_id,
|
|
174
|
+
validate: validate,
|
|
175
|
+
on_event: ->(payload) { @store.record(thread_id, payload) },
|
|
172
176
|
on_finish: -> { @store.finish_run(thread_id) },
|
|
173
|
-
on_task:
|
|
177
|
+
on_task: ->(task) { @store.attach_task(thread_id, task) },
|
|
174
178
|
&block
|
|
175
179
|
)
|
|
176
180
|
end
|
|
@@ -20,7 +20,7 @@ module AgUi
|
|
|
20
20
|
# One assistant turn per call: seeds the chat from env[:messages]
|
|
21
21
|
# (including prior tool-call turns), registers env[:tools] as
|
|
22
22
|
# schema-only CLIENT tools (they halt — never execute server-side),
|
|
23
|
-
# streams text deltas
|
|
23
|
+
# streams text deltas as AG-UI events through the env, then appends either the
|
|
24
24
|
# assistant text or the assistant tool-call message to env[:messages]
|
|
25
25
|
# for the ToolRouter to route.
|
|
26
26
|
#
|
|
@@ -44,7 +44,7 @@ module AgUi
|
|
|
44
44
|
apply_tool_choice(chat, env)
|
|
45
45
|
seed(chat, env[:messages])
|
|
46
46
|
|
|
47
|
-
emitter = TurnEmitter.new(env
|
|
47
|
+
emitter = TurnEmitter.new(env)
|
|
48
48
|
chat.complete do |chunk|
|
|
49
49
|
thinking = chunk.thinking
|
|
50
50
|
if thinking&.text
|
|
@@ -89,9 +89,13 @@ module AgUi
|
|
|
89
89
|
# until its first non-empty delta (tool-call-only turns produce no
|
|
90
90
|
# empty bubbles), and reasoning closes as soon as text begins —
|
|
91
91
|
# providers stream thinking strictly before the answer.
|
|
92
|
+
# Turns a provider's chunk stream into the AG-UI text/reasoning
|
|
93
|
+
# vocabulary, emitted through the env's hooks: a reasoning phase opens
|
|
94
|
+
# lazily and closes before any text, and the text phase starts on its
|
|
95
|
+
# first non-empty delta.
|
|
92
96
|
class TurnEmitter
|
|
93
|
-
def initialize(
|
|
94
|
-
@
|
|
97
|
+
def initialize(env)
|
|
98
|
+
@env = env
|
|
95
99
|
@text_id = SecureRandom.uuid
|
|
96
100
|
@reasoning_id = SecureRandom.uuid
|
|
97
101
|
@text_started = false
|
|
@@ -101,10 +105,10 @@ module AgUi
|
|
|
101
105
|
def thinking_delta(text)
|
|
102
106
|
unless text.empty?
|
|
103
107
|
open_reasoning
|
|
104
|
-
@
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
+
@env.emit(
|
|
109
|
+
:reasoning_message_content,
|
|
110
|
+
{ message_id: @reasoning_id, delta: text },
|
|
111
|
+
)
|
|
108
112
|
end
|
|
109
113
|
end
|
|
110
114
|
|
|
@@ -112,20 +116,17 @@ module AgUi
|
|
|
112
116
|
unless text.empty?
|
|
113
117
|
close_reasoning
|
|
114
118
|
unless @text_started
|
|
115
|
-
@
|
|
119
|
+
@env.emit(:text_message_start, { message_id: @text_id })
|
|
116
120
|
@text_started = true
|
|
117
121
|
end
|
|
118
|
-
@
|
|
119
|
-
type: :text_message_content,
|
|
120
|
-
data: { message_id: @text_id, delta: text },
|
|
121
|
-
}
|
|
122
|
+
@env.emit(:text_message_content, { message_id: @text_id, delta: text })
|
|
122
123
|
end
|
|
123
124
|
end
|
|
124
125
|
|
|
125
126
|
def finish
|
|
126
127
|
close_reasoning
|
|
127
128
|
if @text_started
|
|
128
|
-
@
|
|
129
|
+
@env.emit(:text_message_end, { message_id: @text_id })
|
|
129
130
|
end
|
|
130
131
|
end
|
|
131
132
|
|
|
@@ -133,16 +134,16 @@ module AgUi
|
|
|
133
134
|
|
|
134
135
|
def open_reasoning
|
|
135
136
|
unless @reasoning_open
|
|
136
|
-
@
|
|
137
|
-
@
|
|
137
|
+
@env.emit(:reasoning_start, { message_id: @reasoning_id })
|
|
138
|
+
@env.emit(:reasoning_message_start, { message_id: @reasoning_id })
|
|
138
139
|
@reasoning_open = true
|
|
139
140
|
end
|
|
140
141
|
end
|
|
141
142
|
|
|
142
143
|
def close_reasoning
|
|
143
144
|
if @reasoning_open
|
|
144
|
-
@
|
|
145
|
-
@
|
|
145
|
+
@env.emit(:reasoning_message_end, { message_id: @reasoning_id })
|
|
146
|
+
@env.emit(:reasoning_end, { message_id: @reasoning_id })
|
|
146
147
|
@reasoning_open = false
|
|
147
148
|
end
|
|
148
149
|
end
|
|
@@ -168,8 +169,16 @@ module AgUi
|
|
|
168
169
|
# force copilotkitSuggest.
|
|
169
170
|
def apply_tool_choice(chat, env)
|
|
170
171
|
props = env[:forwarded_props]
|
|
171
|
-
|
|
172
|
-
|
|
172
|
+
if props.is_a?(Hash)
|
|
173
|
+
choice = props["toolChoice"]
|
|
174
|
+
else
|
|
175
|
+
choice = nil
|
|
176
|
+
end
|
|
177
|
+
if choice.is_a?(Hash)
|
|
178
|
+
name = choice.dig("function", "name")
|
|
179
|
+
else
|
|
180
|
+
name = nil
|
|
181
|
+
end
|
|
173
182
|
if name
|
|
174
183
|
chat.with_tools(choice: name)
|
|
175
184
|
end
|
|
@@ -179,9 +188,9 @@ module AgUi
|
|
|
179
188
|
(tools || []).each do |tool|
|
|
180
189
|
chat.with_tool(
|
|
181
190
|
ClientTool.new(
|
|
182
|
-
name:
|
|
191
|
+
name: dig_tool(tool, "name"),
|
|
183
192
|
description: dig_tool(tool, "description"),
|
|
184
|
-
parameters:
|
|
193
|
+
parameters: dig_tool(tool, "parameters"),
|
|
185
194
|
),
|
|
186
195
|
)
|
|
187
196
|
end
|
|
@@ -212,8 +221,8 @@ module AgUi
|
|
|
212
221
|
seed_assistant(chat, message)
|
|
213
222
|
when :tool
|
|
214
223
|
chat.add_message(
|
|
215
|
-
role:
|
|
216
|
-
content:
|
|
224
|
+
role: :tool,
|
|
225
|
+
content: message.content.to_s,
|
|
217
226
|
tool_call_id: message.tool_call_id,
|
|
218
227
|
)
|
|
219
228
|
end
|
|
@@ -283,8 +292,8 @@ module AgUi
|
|
|
283
292
|
|
|
284
293
|
if assistant&.tool_call?
|
|
285
294
|
env[:messages] << Brute::Message.new(
|
|
286
|
-
role:
|
|
287
|
-
content:
|
|
295
|
+
role: :assistant,
|
|
296
|
+
content: assistant.content.to_s,
|
|
288
297
|
tool_calls: assistant.tool_calls.each_value.map do |tc|
|
|
289
298
|
{ id: tc.id, name: tc.name, arguments: tc.arguments }
|
|
290
299
|
end,
|
|
@@ -366,21 +375,38 @@ describe "AgUi::Terminals::RubyLLM" do
|
|
|
366
375
|
end
|
|
367
376
|
end
|
|
368
377
|
|
|
369
|
-
|
|
370
|
-
|
|
378
|
+
# Brute 6 replaced the env[:events] sink with the hooks registry, so a test
|
|
379
|
+
# that wants to see what the terminal streamed subscribes instead of reading
|
|
380
|
+
# an array off the env. `events` is where the turn's deltas land.
|
|
381
|
+
TURN_EVENTS = %i[
|
|
382
|
+
text_message_start text_message_content text_message_end
|
|
383
|
+
reasoning_start reasoning_message_start reasoning_message_content
|
|
384
|
+
reasoning_message_end reasoning_end
|
|
385
|
+
].freeze
|
|
386
|
+
|
|
387
|
+
build_env = ->(into = []) do
|
|
388
|
+
hooks = Brute::Hooks::Registry.new
|
|
389
|
+
TURN_EVENTS.each do |event|
|
|
390
|
+
hooks.on(event) { |_env, data, _trace| into << { type: event, data: data } }
|
|
391
|
+
end
|
|
392
|
+
Brute::Hooks::Trace.new(
|
|
393
|
+
{ messages: Brute.log, metadata: {}, current_iteration: 1 },
|
|
394
|
+
hooks: hooks,
|
|
395
|
+
)
|
|
371
396
|
end
|
|
372
397
|
|
|
373
|
-
it "streams deltas
|
|
398
|
+
it "streams deltas as AG-UI events and appends the assistant message" do
|
|
374
399
|
fake = fake_chat_class.new(chunks: ["Hel", "", "lo"], final: "Hello")
|
|
375
400
|
terminal = AgUi::Terminals::RubyLLM.new(chat_factory: ->(**) { fake })
|
|
376
401
|
|
|
377
|
-
|
|
402
|
+
events = []
|
|
403
|
+
env = build_env.(events)
|
|
378
404
|
env[:messages].user("hi")
|
|
379
405
|
terminal.call(env)
|
|
380
406
|
|
|
381
|
-
types =
|
|
407
|
+
types = events.map { |e| e[:type] }
|
|
382
408
|
types.should == [:text_message_start, :text_message_content, :text_message_content, :text_message_end]
|
|
383
|
-
|
|
409
|
+
events[1][:data][:delta].should == "Hel"
|
|
384
410
|
|
|
385
411
|
env[:messages].last.role.should == :assistant
|
|
386
412
|
env[:messages].last.content.should == "Hello"
|
|
@@ -410,12 +436,13 @@ describe "AgUi::Terminals::RubyLLM" do
|
|
|
410
436
|
fake = fake_chat_class.new(final: nil, tool_calls: tool_calls)
|
|
411
437
|
terminal = AgUi::Terminals::RubyLLM.new(chat_factory: ->(**) { fake })
|
|
412
438
|
|
|
413
|
-
|
|
439
|
+
events = []
|
|
440
|
+
env = build_env.(events)
|
|
414
441
|
env[:messages].user("go to data")
|
|
415
442
|
terminal.call(env)
|
|
416
443
|
|
|
417
444
|
# No text events for a tool-only turn (lazy start).
|
|
418
|
-
|
|
445
|
+
events.should == []
|
|
419
446
|
|
|
420
447
|
last = env[:messages].last
|
|
421
448
|
last.tool_call?.should.be.true
|
|
@@ -466,21 +493,22 @@ describe "AgUi::Terminals::RubyLLM" do
|
|
|
466
493
|
)
|
|
467
494
|
terminal = AgUi::Terminals::RubyLLM.new(thinking: { budget: 1024 }, chat_factory: ->(**) { fake })
|
|
468
495
|
|
|
469
|
-
|
|
496
|
+
events = []
|
|
497
|
+
env = build_env.(events)
|
|
470
498
|
env[:messages].user("why?")
|
|
471
499
|
terminal.call(env)
|
|
472
500
|
|
|
473
501
|
fake.thinking_config.should == { budget: 1024 }
|
|
474
|
-
|
|
502
|
+
events.map { |e| e[:type] }.should == %i[
|
|
475
503
|
reasoning_start reasoning_message_start
|
|
476
504
|
reasoning_message_content reasoning_message_content
|
|
477
505
|
reasoning_message_end reasoning_end
|
|
478
506
|
text_message_start text_message_content text_message_end
|
|
479
507
|
]
|
|
480
508
|
|
|
481
|
-
reasoning_ids =
|
|
509
|
+
reasoning_ids = events.first(6).map { |e| e[:data][:message_id] }.uniq
|
|
482
510
|
reasoning_ids.length.should == 1
|
|
483
|
-
|
|
511
|
+
events.last[:data][:message_id].should.not == reasoning_ids.first
|
|
484
512
|
end
|
|
485
513
|
|
|
486
514
|
it "seeds multimodal user content as ruby_llm Content with attachments" do
|
|
@@ -536,7 +564,8 @@ describe "AgUi::Terminals::RubyLLM" do
|
|
|
536
564
|
.run(terminal)
|
|
537
565
|
env["ag_ui.stream"].open(thread_id: input.thread_id, run_id: input.run_id) do |stream|
|
|
538
566
|
stream.run_started
|
|
539
|
-
|
|
567
|
+
AgUi::EventBridge.new(stream).subscribe(agent)
|
|
568
|
+
agent.start(AgUi::Messages.to_brute(input.messages))
|
|
540
569
|
stream.run_finished
|
|
541
570
|
rescue => e
|
|
542
571
|
stream.run_error(message: e.message, code: e.class.name)
|
data/lib/ag_ui/version.rb
CHANGED
data/lib/ag_ui.rb
CHANGED
|
@@ -25,6 +25,7 @@ require "ag_ui/event_bridge"
|
|
|
25
25
|
require "ag_ui/middleware/system_prompt"
|
|
26
26
|
require "ag_ui/middleware/forwarded_props"
|
|
27
27
|
require "ag_ui/middleware/tool_router"
|
|
28
|
+
require "ag_ui/middleware/state"
|
|
28
29
|
require "ag_ui/a2ui/catalog"
|
|
29
30
|
require "ag_ui/a2ui/validate"
|
|
30
31
|
require "ag_ui/a2ui/recovery"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ag-ui
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- AgUi Contributors
|
|
@@ -66,25 +66,47 @@ dependencies:
|
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
67
|
version: '2.5'
|
|
68
68
|
- !ruby/object:Gem::Dependency
|
|
69
|
-
name:
|
|
69
|
+
name: hana
|
|
70
70
|
requirement: !ruby/object:Gem::Requirement
|
|
71
71
|
requirements:
|
|
72
72
|
- - "~>"
|
|
73
73
|
- !ruby/object:Gem::Version
|
|
74
|
-
version: '3
|
|
75
|
-
|
|
74
|
+
version: '1.3'
|
|
75
|
+
type: :runtime
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
76
80
|
- !ruby/object:Gem::Version
|
|
77
|
-
version: 3
|
|
81
|
+
version: '1.3'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: brute
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '6.0'
|
|
78
89
|
type: :runtime
|
|
79
90
|
prerelease: false
|
|
80
91
|
version_requirements: !ruby/object:Gem::Requirement
|
|
81
92
|
requirements:
|
|
82
93
|
- - "~>"
|
|
83
94
|
- !ruby/object:Gem::Version
|
|
84
|
-
version: '
|
|
85
|
-
|
|
95
|
+
version: '6.0'
|
|
96
|
+
- !ruby/object:Gem::Dependency
|
|
97
|
+
name: gem_kit
|
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - "~>"
|
|
86
101
|
- !ruby/object:Gem::Version
|
|
87
|
-
version:
|
|
102
|
+
version: '0.2'
|
|
103
|
+
type: :runtime
|
|
104
|
+
prerelease: false
|
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - "~>"
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '0.2'
|
|
88
110
|
- !ruby/object:Gem::Dependency
|
|
89
111
|
name: ruby_llm
|
|
90
112
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -183,6 +205,20 @@ dependencies:
|
|
|
183
205
|
- - "~>"
|
|
184
206
|
- !ruby/object:Gem::Version
|
|
185
207
|
version: '2.1'
|
|
208
|
+
- !ruby/object:Gem::Dependency
|
|
209
|
+
name: gem_kit-release
|
|
210
|
+
requirement: !ruby/object:Gem::Requirement
|
|
211
|
+
requirements:
|
|
212
|
+
- - "~>"
|
|
213
|
+
- !ruby/object:Gem::Version
|
|
214
|
+
version: '0.3'
|
|
215
|
+
type: :development
|
|
216
|
+
prerelease: false
|
|
217
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
218
|
+
requirements:
|
|
219
|
+
- - "~>"
|
|
220
|
+
- !ruby/object:Gem::Version
|
|
221
|
+
version: '0.3'
|
|
186
222
|
description: Ruby server implementation of the AG-UI protocol (SSE event streaming,
|
|
187
223
|
run loop, client-tool round-trips, A2UI) for driving CopilotKit frontends from Rack/Falcon.
|
|
188
224
|
executables: []
|
|
@@ -191,7 +227,6 @@ extra_rdoc_files: []
|
|
|
191
227
|
files:
|
|
192
228
|
- LICENSE
|
|
193
229
|
- data/ag_ui.json
|
|
194
|
-
- data/generate-ag-ui-schema.py
|
|
195
230
|
- lib/ag_ui.rb
|
|
196
231
|
- lib/ag_ui/a2ui/catalog.rb
|
|
197
232
|
- lib/ag_ui/a2ui/recovery.rb
|
|
@@ -200,6 +235,7 @@ files:
|
|
|
200
235
|
- lib/ag_ui/messages.rb
|
|
201
236
|
- lib/ag_ui/middleware/a2ui.rb
|
|
202
237
|
- lib/ag_ui/middleware/forwarded_props.rb
|
|
238
|
+
- lib/ag_ui/middleware/state.rb
|
|
203
239
|
- lib/ag_ui/middleware/system_prompt.rb
|
|
204
240
|
- lib/ag_ui/middleware/tool_router.rb
|
|
205
241
|
- lib/ag_ui/protocol/json_schema.rb
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
# Generates data/ag_ui.json from the reference Python SDK's pydantic models
|
|
2
|
-
# (clone github.com/ag-ui-protocol/ag-ui and point AG_UI_SDK at sdks/python).
|
|
3
|
-
#
|
|
4
|
-
# Run: uv run --with pydantic data/generate-ag-ui-schema.py [/path/to/ag-ui/sdks/python]
|
|
5
|
-
#
|
|
6
|
-
# Output shape mirrors a2a's data/a2a.json: a single top-level "definitions"
|
|
7
|
-
# map keyed by model class name, refs rewritten to #/definitions/<Model>.
|
|
8
|
-
# Wire keys are camelCase (by_alias), matching @ag-ui/core exactly.
|
|
9
|
-
|
|
10
|
-
import inspect
|
|
11
|
-
import json
|
|
12
|
-
import os
|
|
13
|
-
import sys
|
|
14
|
-
|
|
15
|
-
sdk = sys.argv[1] if len(sys.argv) > 1 else os.environ.get("AG_UI_SDK", "/tmp/ag-ui/sdks/python")
|
|
16
|
-
sys.path.insert(0, sdk)
|
|
17
|
-
|
|
18
|
-
from pydantic import BaseModel # noqa: E402
|
|
19
|
-
from pydantic.json_schema import models_json_schema # noqa: E402
|
|
20
|
-
|
|
21
|
-
from ag_ui.core import capabilities, events, types # noqa: E402
|
|
22
|
-
|
|
23
|
-
models = []
|
|
24
|
-
for mod in (types, events, capabilities):
|
|
25
|
-
for _name, obj in sorted(vars(mod).items()):
|
|
26
|
-
if (
|
|
27
|
-
inspect.isclass(obj)
|
|
28
|
-
and issubclass(obj, BaseModel)
|
|
29
|
-
and obj.__module__ == mod.__name__
|
|
30
|
-
and obj.__name__ != "ConfiguredBaseModel"
|
|
31
|
-
):
|
|
32
|
-
models.append(obj)
|
|
33
|
-
|
|
34
|
-
_, top = models_json_schema(
|
|
35
|
-
[(m, "validation") for m in models],
|
|
36
|
-
by_alias=True,
|
|
37
|
-
ref_template="#/definitions/{model}",
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
out = {
|
|
41
|
-
"$comment": f"Generated by data/generate-ag-ui-schema.py from ag-ui sdks/python ({len(models)} models). Do not edit.",
|
|
42
|
-
"definitions": top["$defs"],
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
dest = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ag_ui.json")
|
|
46
|
-
with open(dest, "w") as f:
|
|
47
|
-
json.dump(out, f, indent=2, sort_keys=True)
|
|
48
|
-
f.write("\n")
|
|
49
|
-
|
|
50
|
-
print(f"wrote {dest}: {len(out['definitions'])} definitions")
|