ag-ui-protocol 0.1.4 → 0.2.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.
@@ -21,11 +21,10 @@ module AgUiProtocol
21
21
  # ```ruby
22
22
  #
23
23
  # AgUiProtocol::Core::Types::Role
24
- # # => ["developer", "system", "assistant", "user", "tool", "activity"]
24
+ # # => ["developer", "system", "assistant", "user", "tool", "activity", "reasoning"]
25
25
  #
26
26
  # ```
27
- # @category Message Types
28
- Role = ["developer", "system", "assistant", "user", "tool", "activity"].freeze
27
+ Role = ["developer", "system", "assistant", "user", "tool", "activity", "reasoning"].freeze
29
28
 
30
29
  # Base model for protocol entities.
31
30
  #
@@ -41,7 +40,7 @@ module AgUiProtocol
41
40
  # @return [Hash<Symbol, Object>, raise NotImplementedError]
42
41
  sig { returns(T::Hash[Symbol, T.untyped]) }
43
42
  def to_h
44
- raise NotImplementedError, 'Implement this method by concrect class'
43
+ raise NotImplementedError, "Implement this method in a concrete subclass"
45
44
  end
46
45
 
47
46
  # Returns a JSON-ready representation.
@@ -119,14 +118,23 @@ module AgUiProtocol
119
118
  sig { returns(FunctionCall) }
120
119
  attr_reader :function
121
120
 
121
+ sig { returns(T.nilable(String)) }
122
+ attr_reader :encrypted_value
123
+
122
124
  # @param id [String] Unique identifier for the tool call
123
125
  # @param function [FunctionCall, Hash] Function name and arguments
124
126
  # @param type [String] Type of the tool call
125
- sig { params(id: String, function: T.untyped, type: String).void }
126
- def initialize(id:, function:, type: 'function')
127
+ # @param encrypted_value [String] Encrypted tool call value for zero-data-retention mode
128
+ sig { params(id: String, function: T.untyped, type: String, encrypted_value: T.nilable(String)).void }
129
+ def initialize(id:, function:, type: 'function', encrypted_value: nil)
127
130
  @id = id
128
131
  @type = type
129
- @function = function.is_a?(FunctionCall) ? function : FunctionCall.new(**function)
132
+ @function = if function.is_a?(FunctionCall)
133
+ function
134
+ else
135
+ FunctionCall.new(**function.transform_keys(&:to_sym))
136
+ end
137
+ @encrypted_value = encrypted_value
130
138
  end
131
139
 
132
140
  sig { returns(T::Hash[Symbol, T.untyped]) }
@@ -134,7 +142,8 @@ module AgUiProtocol
134
142
  {
135
143
  id: @id,
136
144
  type: @type,
137
- function: @function
145
+ function: @function,
146
+ encrypted_value: @encrypted_value
138
147
  }
139
148
  end
140
149
  end
@@ -147,22 +156,35 @@ module AgUiProtocol
147
156
  sig { returns(String) }
148
157
  attr_reader :role
149
158
 
150
- sig { returns(T.nilable(T.any(String, T::Array[T.any(TextInputContent, BinaryInputContent)]))) }
159
+ sig { returns(T.nilable(T.any(String, T::Array[T.any(TextInputContent, BinaryInputContent, ImageInputContent, AudioInputContent, VideoInputContent, DocumentInputContent)]))) }
151
160
  attr_reader :content
152
161
 
153
162
  sig { returns(T.nilable(String)) }
154
163
  attr_reader :name
155
164
 
165
+ sig { returns(T.nilable(String)) }
166
+ attr_reader :encrypted_value
167
+
156
168
  # @param id [String] Unique identifier for the message
157
169
  # @param role [String] Role of the message sender
158
170
  # @param content [Object] Text content of the message
159
171
  # @param name [String] Optional name of the sender
160
- sig { params(id: String, role: String, content: T.nilable(T.any(String, T::Array[T.any(TextInputContent, BinaryInputContent)])), name: T.nilable(String)).void }
161
- def initialize(id:, role:, content: nil, name: nil)
172
+ # @param encrypted_value [String] Encrypted content for zero-data-retention mode
173
+ sig do
174
+ params(
175
+ id: String,
176
+ role: String,
177
+ content: T.nilable(T.any(String, T::Array[T.any(TextInputContent, BinaryInputContent, ImageInputContent, AudioInputContent, VideoInputContent, DocumentInputContent)])),
178
+ name: T.nilable(String),
179
+ encrypted_value: T.nilable(String)
180
+ ).void
181
+ end
182
+ def initialize(id:, role:, content: nil, name: nil, encrypted_value: nil)
162
183
  @id = id
163
184
  @role = role
164
185
  @content = content
165
186
  @name = name
187
+ @encrypted_value = encrypted_value
166
188
  end
167
189
 
168
190
  sig { returns(T::Hash[Symbol, T.untyped]) }
@@ -171,7 +193,8 @@ module AgUiProtocol
171
193
  id: @id,
172
194
  role: @role,
173
195
  content: @content,
174
- name: @name
196
+ name: @name,
197
+ encrypted_value: @encrypted_value
175
198
  }
176
199
  end
177
200
  end
@@ -247,13 +270,23 @@ module AgUiProtocol
247
270
 
248
271
  # @param id [String] Unique identifier for the message
249
272
  # @param content [Object] Text content of the message
250
- # @param tool_calls [Array<ToolCall, Hash>] Tool calls made in this message
273
+ # @param tool_calls [Array<ToolCall, Hash>] Tool calls made in this message; Hashes are normalized to ToolCall instances.
251
274
  # @param name [String] Name of the sender
252
- sig { params(id: String, content: T.untyped, tool_calls: T.nilable(T::Array[ToolCall]), name: T.nilable(String)).void }
253
- def initialize(id:, content: nil, tool_calls: nil, name: nil)
254
- super(id: id, role: "assistant", content: content, name: name)
275
+ # @param encrypted_value [String] Encrypted content for zero-data-retention mode
276
+ sig do
277
+ params(
278
+ id: String,
279
+ content: T.untyped,
280
+ tool_calls: T.nilable(T::Array[T.any(ToolCall, T::Hash[T.any(Symbol, String), T.untyped])]),
281
+ name: T.nilable(String),
282
+ encrypted_value: T.nilable(String)
283
+ ).void
284
+ end
285
+ def initialize(id:, content: nil, tool_calls: nil, name: nil, encrypted_value: nil)
286
+ super(id: id, role: "assistant", content: content, name: name, encrypted_value: encrypted_value)
255
287
  @tool_calls = tool_calls&.map do |tc|
256
- tc.is_a?(ToolCall) ? tc : ToolCall.new(**tc)
288
+ next tc if tc.is_a?(ToolCall)
289
+ ToolCall.new(**tc.transform_keys(&:to_sym))
257
290
  end
258
291
  end
259
292
 
@@ -344,8 +377,8 @@ module AgUiProtocol
344
377
  ).void
345
378
  end
346
379
  def initialize(mime_type:, type: "binary", id: nil, url: nil, data: nil, filename: nil)
347
- if [id, url, data].all?(&:nil?)
348
- raise ArgumentError, "BinaryInputContent requires id, url, or data to be provided."
380
+ if [id, url, data].none? { |v| v.is_a?(String) && !v.empty? }
381
+ raise ArgumentError, "BinaryInputContent requires at least one of id, url, or data to be a non-empty string"
349
382
  end
350
383
 
351
384
  @type = type
@@ -384,34 +417,47 @@ module AgUiProtocol
384
417
  # ```
385
418
  # @category Message Types
386
419
  class UserMessage < BaseMessage
387
- sig { returns(String) }
388
- attr_reader :id
389
-
390
- sig { returns(String) }
391
- attr_reader :role
392
-
393
- sig { returns(T.any(String, T::Array[T.any(TextInputContent, BinaryInputContent)])) }
394
- attr_reader :content
395
-
396
- sig { returns(T.nilable(String)) }
397
- attr_reader :name
398
-
399
420
  # @param id [String] Unique identifier for the message
400
- # @param content [String, Array<TextInputContent | BinaryInputContent>] Either a plain text string or an ordered list of multimodal fragments
421
+ # @param content [String, Array<TextInputContent | BinaryInputContent | ImageInputContent | AudioInputContent | VideoInputContent | DocumentInputContent | Hash>] Accepted shapes: a String for plain text, or an Array whose elements are either *InputContent Models (TextInputContent, BinaryInputContent, ImageInputContent, AudioInputContent, VideoInputContent, DocumentInputContent) OR Hashes with a `type:` key (`text`, `binary`, `image`, `audio`, `video`, `document`); Hash entries are normalized internally to the corresponding Model.
401
422
  # @param name [String] Optional name of the sender
402
- sig { params(id: String, content: T.any(String, T::Array[T.any(TextInputContent, BinaryInputContent)]), name: T.nilable(String)).void }
403
- def initialize(id:, content:, name: nil)
404
- super(id: id, role: "user", content: normalize_user_content(content), name: name)
423
+ # @param encrypted_value [String] Encrypted content for zero-data-retention mode
424
+ sig do
425
+ params(
426
+ id: String,
427
+ content: T.any(
428
+ String,
429
+ T::Array[T.any(
430
+ TextInputContent, BinaryInputContent, ImageInputContent, AudioInputContent, VideoInputContent, DocumentInputContent,
431
+ T::Hash[T.any(Symbol, String), T.untyped]
432
+ )]
433
+ ),
434
+ name: T.nilable(String),
435
+ encrypted_value: T.nilable(String)
436
+ ).void
437
+ end
438
+ def initialize(id:, content:, name: nil, encrypted_value: nil)
439
+ super(id: id, role: "user", content: normalize_user_content(content), name: name, encrypted_value: encrypted_value)
405
440
  end
406
441
 
407
- sig { params(content: T.any(String, T::Array[T.any(TextInputContent, BinaryInputContent)])).returns(T.any(String, T::Array[T.any(TextInputContent, BinaryInputContent)])) }
442
+ sig do
443
+ params(
444
+ content: T.any(
445
+ String,
446
+ T::Array[T.any(
447
+ TextInputContent, BinaryInputContent, ImageInputContent, AudioInputContent, VideoInputContent, DocumentInputContent,
448
+ T::Hash[T.any(Symbol, String), T.untyped]
449
+ )]
450
+ )
451
+ ).returns(T.any(String, T::Array[T.any(TextInputContent, BinaryInputContent, ImageInputContent, AudioInputContent, VideoInputContent, DocumentInputContent)]))
452
+ end
408
453
  def normalize_user_content(content)
409
454
  if content.is_a?(Array)
410
455
  content.map do |c|
411
456
  if c.is_a?(Model)
412
457
  c
413
458
  elsif c.is_a?(Hash)
414
- case c[:type] || c["type"]
459
+ src_type = c[:type] || c["type"]
460
+ case src_type
415
461
  when "text"
416
462
  TextInputContent.new(text: c[:text] || c["text"])
417
463
  when "binary"
@@ -420,13 +466,21 @@ module AgUiProtocol
420
466
  id: c[:id] || c["id"],
421
467
  url: c[:url] || c["url"],
422
468
  data: c[:data] || c["data"],
423
- filename: c[:filename] || c["filename"]
469
+ filename: c[:filename] || c["filename"] || c[:fileName] || c["fileName"]
424
470
  )
471
+ when "image"
472
+ ImageInputContent.new(source: c[:source] || c["source"], metadata: c[:metadata] || c["metadata"])
473
+ when "audio"
474
+ AudioInputContent.new(source: c[:source] || c["source"], metadata: c[:metadata] || c["metadata"])
475
+ when "video"
476
+ VideoInputContent.new(source: c[:source] || c["source"], metadata: c[:metadata] || c["metadata"])
477
+ when "document"
478
+ DocumentInputContent.new(source: c[:source] || c["source"], metadata: c[:metadata] || c["metadata"])
425
479
  else
426
- c
480
+ raise ArgumentError, "Unknown content type: #{src_type.inspect}"
427
481
  end
428
482
  else
429
- c
483
+ raise ArgumentError, "Unknown content type: #{c.class}"
430
484
  end
431
485
  end
432
486
  else
@@ -434,15 +488,6 @@ module AgUiProtocol
434
488
  end
435
489
  end
436
490
 
437
- sig { returns(T::Hash[Symbol, T.untyped]) }
438
- def to_h
439
- {
440
- id: @id,
441
- role: @role,
442
- content: @content,
443
- name: @name
444
- }
445
- end
446
491
  end
447
492
 
448
493
  # Tool result message.
@@ -458,15 +503,6 @@ module AgUiProtocol
458
503
  # ```
459
504
  # @category Message Types
460
505
  class ToolMessage < BaseMessage
461
- sig { returns(String) }
462
- attr_reader :id
463
-
464
- sig { returns(String) }
465
- attr_reader :role
466
-
467
- sig { returns(String) }
468
- attr_reader :content
469
-
470
506
  sig { returns(String) }
471
507
  attr_reader :tool_call_id
472
508
 
@@ -477,27 +513,30 @@ module AgUiProtocol
477
513
  # @param content [String] Tool result content.
478
514
  # @param tool_call_id [String] ID of the tool call this message responds to.
479
515
  # @param error [String] Error payload if the tool call failed.
480
- sig { params(id: String, content: String, tool_call_id: String, error: T.nilable(String)).void }
481
- def initialize(id:, content:, tool_call_id:, error: nil)
482
- super(id: id, role: "tool", content: content)
516
+ # @param encrypted_value [String] Encrypted tool message value for zero-data-retention mode
517
+ sig { params(id: String, content: String, tool_call_id: String, error: T.nilable(String), encrypted_value: T.nilable(String)).void }
518
+ def initialize(id:, content:, tool_call_id:, error: nil, encrypted_value: nil)
519
+ super(id: id, role: "tool", content: content, encrypted_value: encrypted_value)
483
520
  @tool_call_id = tool_call_id
484
521
  @error = error
485
522
  end
486
523
 
487
524
  sig { returns(T::Hash[Symbol, T.untyped]) }
488
525
  def to_h
489
- {
490
- id: @id,
491
- role: @role,
492
- content: @content,
526
+ super.merge(
493
527
  tool_call_id: @tool_call_id,
494
528
  error: @error
495
- }
529
+ )
496
530
  end
497
531
  end
498
532
 
499
533
  # Represents structured activity progress emitted between chat messages.
500
534
  #
535
+ # ActivityMessage intentionally does NOT inherit from BaseMessage because its
536
+ # `content` is a structured Hash rather than text/multimodal, which is
537
+ # incompatible with BaseMessage#content's type. It still appears alongside
538
+ # other messages in the stream but is modeled as its own shape.
539
+ #
501
540
  # ```ruby
502
541
  #
503
542
  # msg = AgUiProtocol::Core::Types::ActivityMessage.new(
@@ -508,7 +547,7 @@ module AgUiProtocol
508
547
  #
509
548
  # ```
510
549
  # @category Message Types
511
- class ActivityMessage < BaseMessage
550
+ class ActivityMessage < Model
512
551
  sig { returns(String) }
513
552
  attr_reader :id
514
553
 
@@ -527,7 +566,7 @@ module AgUiProtocol
527
566
  sig { params(id: String, activity_type: String, content: T::Hash[T.any(Symbol, String), T.untyped]).void }
528
567
  def initialize(id:, activity_type:, content:)
529
568
  @id = id
530
- @role = 'activity'
569
+ @role = "activity"
531
570
  @activity_type = activity_type
532
571
  @content = content
533
572
  end
@@ -538,7 +577,8 @@ module AgUiProtocol
538
577
  id: @id,
539
578
  role: @role,
540
579
  activity_type: @activity_type,
541
- content: @content
580
+ # `content` is an arbitrary user-supplied payload — preserve keys verbatim on the wire.
581
+ content: @content.nil? ? nil : AgUiProtocol::Util::Opaque.new(@content)
542
582
  }
543
583
  end
544
584
  end
@@ -610,7 +650,453 @@ module AgUiProtocol
610
650
  {
611
651
  name: @name,
612
652
  description: @description,
613
- parameters: @parameters
653
+ # `parameters` is a JSON Schema document supplied by the user — preserve keys verbatim.
654
+ parameters: @parameters.nil? ? nil : AgUiProtocol::Util::Opaque.new(@parameters)
655
+ }
656
+ end
657
+ end
658
+
659
+ # Represents a data source for multimodal input content using base64-encoded data.
660
+ #
661
+ # ```ruby
662
+ #
663
+ # source = AgUiProtocol::Core::Types::InputContentDataSource.new(
664
+ # value: "base64encoded...",
665
+ # mime_type: "image/png"
666
+ # )
667
+ #
668
+ # ```
669
+ class InputContentDataSource < Model
670
+ sig { returns(String) }
671
+ attr_reader :type
672
+
673
+ sig { returns(String) }
674
+ attr_reader :value
675
+
676
+ sig { returns(String) }
677
+ attr_reader :mime_type
678
+
679
+ # @param value [String] Base64 encoded content
680
+ # @param mime_type [String] MIME type of the content
681
+ # @param type [String] Identifies the source type
682
+ sig { params(value: String, mime_type: String, type: String).void }
683
+ def initialize(value:, mime_type:, type: "data")
684
+ @type = type
685
+ @value = value
686
+ @mime_type = mime_type
687
+ end
688
+
689
+ sig { returns(T::Hash[Symbol, T.untyped]) }
690
+ def to_h
691
+ {
692
+ type: @type,
693
+ value: @value,
694
+ mime_type: @mime_type
695
+ }
696
+ end
697
+ end
698
+
699
+ # Represents a URL source for multimodal input content.
700
+ #
701
+ # ```ruby
702
+ #
703
+ # source = AgUiProtocol::Core::Types::InputContentUrlSource.new(
704
+ # value: "https://example.com/image.png",
705
+ # mime_type: "image/png"
706
+ # )
707
+ #
708
+ # ```
709
+ class InputContentUrlSource < Model
710
+ sig { returns(String) }
711
+ attr_reader :type
712
+
713
+ sig { returns(String) }
714
+ attr_reader :value
715
+
716
+ sig { returns(T.nilable(String)) }
717
+ attr_reader :mime_type
718
+
719
+ # @param value [String] URL string
720
+ # @param mime_type [String] Optional MIME type
721
+ # @param type [String] Identifies the source type
722
+ sig { params(value: String, mime_type: T.nilable(String), type: String).void }
723
+ def initialize(value:, mime_type: nil, type: "url")
724
+ @type = type
725
+ @value = value
726
+ @mime_type = mime_type
727
+ end
728
+
729
+ sig { returns(T::Hash[Symbol, T.untyped]) }
730
+ def to_h
731
+ {
732
+ type: @type,
733
+ value: @value,
734
+ mime_type: @mime_type
735
+ }
736
+ end
737
+ end
738
+
739
+ # Represents an image content fragment inside a multimodal user message.
740
+ #
741
+ # ```ruby
742
+ #
743
+ # content = AgUiProtocol::Core::Types::ImageInputContent.new(
744
+ # source: { type: "url", value: "https://example.com/cat.png", mime_type: "image/png" }
745
+ # )
746
+ #
747
+ # ```
748
+ class ImageInputContent < Model
749
+ sig { returns(String) }
750
+ attr_reader :type
751
+
752
+ sig { returns(T.any(InputContentDataSource, InputContentUrlSource)) }
753
+ attr_reader :source
754
+
755
+ sig { returns(T.untyped) }
756
+ attr_reader :metadata
757
+
758
+ # @param source [InputContentDataSource, InputContentUrlSource, Hash] Content source
759
+ # @param metadata [Object] Optional metadata
760
+ # @param type [String] Identifies the fragment type
761
+ sig { params(source: T.untyped, metadata: T.untyped, type: String).void }
762
+ def initialize(source:, metadata: nil, type: "image")
763
+ @type = type
764
+ @source = source.is_a?(Hash) ? build_source(source) : source
765
+ @metadata = metadata
766
+ end
767
+
768
+ sig { returns(T::Hash[Symbol, T.untyped]) }
769
+ def to_h
770
+ {
771
+ type: @type,
772
+ source: @source,
773
+ metadata: @metadata
774
+ }
775
+ end
776
+
777
+ private
778
+
779
+ def build_source(hash)
780
+ src_type = hash[:type] || hash["type"]
781
+ value = hash[:value] || hash["value"]
782
+ mime_type = hash[:mime_type] || hash["mime_type"] || hash[:mimeType] || hash["mimeType"]
783
+ case src_type
784
+ when "data"
785
+ InputContentDataSource.new(value: value, mime_type: mime_type)
786
+ when "url"
787
+ InputContentUrlSource.new(value: value, mime_type: mime_type)
788
+ else
789
+ raise ArgumentError, "Unknown source type: #{src_type.inspect}"
790
+ end
791
+ end
792
+ end
793
+
794
+ # Represents an audio content fragment inside a multimodal user message.
795
+ #
796
+ # ```ruby
797
+ #
798
+ # content = AgUiProtocol::Core::Types::AudioInputContent.new(
799
+ # source: { type: "url", value: "https://example.com/audio.mp3", mime_type: "audio/mp3" }
800
+ # )
801
+ #
802
+ # ```
803
+ class AudioInputContent < Model
804
+ sig { returns(String) }
805
+ attr_reader :type
806
+
807
+ sig { returns(T.any(InputContentDataSource, InputContentUrlSource)) }
808
+ attr_reader :source
809
+
810
+ sig { returns(T.untyped) }
811
+ attr_reader :metadata
812
+
813
+ # @param source [InputContentDataSource, InputContentUrlSource, Hash] Content source
814
+ # @param metadata [Object] Optional metadata
815
+ # @param type [String] Identifies the fragment type
816
+ sig { params(source: T.untyped, metadata: T.untyped, type: String).void }
817
+ def initialize(source:, metadata: nil, type: "audio")
818
+ @type = type
819
+ @source = source.is_a?(Hash) ? build_source(source) : source
820
+ @metadata = metadata
821
+ end
822
+
823
+ sig { returns(T::Hash[Symbol, T.untyped]) }
824
+ def to_h
825
+ {
826
+ type: @type,
827
+ source: @source,
828
+ metadata: @metadata
829
+ }
830
+ end
831
+
832
+ private
833
+
834
+ def build_source(hash)
835
+ src_type = hash[:type] || hash["type"]
836
+ value = hash[:value] || hash["value"]
837
+ mime_type = hash[:mime_type] || hash["mime_type"] || hash[:mimeType] || hash["mimeType"]
838
+ case src_type
839
+ when "data"
840
+ InputContentDataSource.new(value: value, mime_type: mime_type)
841
+ when "url"
842
+ InputContentUrlSource.new(value: value, mime_type: mime_type)
843
+ else
844
+ raise ArgumentError, "Unknown source type: #{src_type.inspect}"
845
+ end
846
+ end
847
+ end
848
+
849
+ # Represents a video content fragment inside a multimodal user message.
850
+ #
851
+ # ```ruby
852
+ #
853
+ # content = AgUiProtocol::Core::Types::VideoInputContent.new(
854
+ # source: { type: "url", value: "https://example.com/video.mp4", mime_type: "video/mp4" }
855
+ # )
856
+ #
857
+ # ```
858
+ class VideoInputContent < Model
859
+ sig { returns(String) }
860
+ attr_reader :type
861
+
862
+ sig { returns(T.any(InputContentDataSource, InputContentUrlSource)) }
863
+ attr_reader :source
864
+
865
+ sig { returns(T.untyped) }
866
+ attr_reader :metadata
867
+
868
+ # @param source [InputContentDataSource, InputContentUrlSource, Hash] Content source
869
+ # @param metadata [Object] Optional metadata
870
+ # @param type [String] Identifies the fragment type
871
+ sig { params(source: T.untyped, metadata: T.untyped, type: String).void }
872
+ def initialize(source:, metadata: nil, type: "video")
873
+ @type = type
874
+ @source = source.is_a?(Hash) ? build_source(source) : source
875
+ @metadata = metadata
876
+ end
877
+
878
+ sig { returns(T::Hash[Symbol, T.untyped]) }
879
+ def to_h
880
+ {
881
+ type: @type,
882
+ source: @source,
883
+ metadata: @metadata
884
+ }
885
+ end
886
+
887
+ private
888
+
889
+ def build_source(hash)
890
+ src_type = hash[:type] || hash["type"]
891
+ value = hash[:value] || hash["value"]
892
+ mime_type = hash[:mime_type] || hash["mime_type"] || hash[:mimeType] || hash["mimeType"]
893
+ case src_type
894
+ when "data"
895
+ InputContentDataSource.new(value: value, mime_type: mime_type)
896
+ when "url"
897
+ InputContentUrlSource.new(value: value, mime_type: mime_type)
898
+ else
899
+ raise ArgumentError, "Unknown source type: #{src_type.inspect}"
900
+ end
901
+ end
902
+ end
903
+
904
+ # Represents a document content fragment inside a multimodal user message.
905
+ #
906
+ # ```ruby
907
+ #
908
+ # content = AgUiProtocol::Core::Types::DocumentInputContent.new(
909
+ # source: { type: "url", value: "https://example.com/doc.pdf", mime_type: "application/pdf" }
910
+ # )
911
+ #
912
+ # ```
913
+ class DocumentInputContent < Model
914
+ sig { returns(String) }
915
+ attr_reader :type
916
+
917
+ sig { returns(T.any(InputContentDataSource, InputContentUrlSource)) }
918
+ attr_reader :source
919
+
920
+ sig { returns(T.untyped) }
921
+ attr_reader :metadata
922
+
923
+ # @param source [InputContentDataSource, InputContentUrlSource, Hash] Content source
924
+ # @param metadata [Object] Optional metadata
925
+ # @param type [String] Identifies the fragment type
926
+ sig { params(source: T.untyped, metadata: T.untyped, type: String).void }
927
+ def initialize(source:, metadata: nil, type: "document")
928
+ @type = type
929
+ @source = source.is_a?(Hash) ? build_source(source) : source
930
+ @metadata = metadata
931
+ end
932
+
933
+ sig { returns(T::Hash[Symbol, T.untyped]) }
934
+ def to_h
935
+ {
936
+ type: @type,
937
+ source: @source,
938
+ metadata: @metadata
939
+ }
940
+ end
941
+
942
+ private
943
+
944
+ def build_source(hash)
945
+ src_type = hash[:type] || hash["type"]
946
+ value = hash[:value] || hash["value"]
947
+ mime_type = hash[:mime_type] || hash["mime_type"] || hash[:mimeType] || hash["mimeType"]
948
+ case src_type
949
+ when "data"
950
+ InputContentDataSource.new(value: value, mime_type: mime_type)
951
+ when "url"
952
+ InputContentUrlSource.new(value: value, mime_type: mime_type)
953
+ else
954
+ raise ArgumentError, "Unknown source type: #{src_type.inspect}"
955
+ end
956
+ end
957
+ end
958
+
959
+ # Represents a reasoning message from an agent with chain-of-thought content.
960
+ #
961
+ # ```ruby
962
+ #
963
+ # msg = AgUiProtocol::Core::Types::ReasoningMessage.new(
964
+ # id: "reason_1",
965
+ # content: "Let me think through this step by step...",
966
+ # encrypted_value: nil
967
+ # )
968
+ #
969
+ # ```
970
+ class ReasoningMessage < BaseMessage
971
+
972
+ sig { returns(String) }
973
+ attr_reader :content
974
+
975
+ # @param id [String] Unique identifier for the message
976
+ # @param content [String] Reasoning content (plaintext when not encrypted)
977
+ # @param encrypted_value [String] Encrypted reasoning content when in zero-data-retention mode
978
+ sig { params(id: String, content: String, encrypted_value: T.nilable(String)).void }
979
+ def initialize(id:, content:, encrypted_value: nil)
980
+ super(id: id, role: "reasoning", content: content, encrypted_value: encrypted_value)
981
+ end
982
+ end
983
+
984
+ # Represents an interrupt that occurred during agent execution for human-in-the-loop workflows.
985
+ #
986
+ # ```ruby
987
+ #
988
+ # interrupt = AgUiProtocol::Core::Types::Interrupt.new(
989
+ # id: "int_1",
990
+ # reason: "input_required",
991
+ # message: "Please provide additional information"
992
+ # )
993
+ #
994
+ # ```
995
+ class Interrupt < Model
996
+ sig { returns(String) }
997
+ attr_reader :id
998
+
999
+ sig { returns(String) }
1000
+ attr_reader :reason
1001
+
1002
+ sig { returns(T.nilable(String)) }
1003
+ attr_reader :message
1004
+
1005
+ sig { returns(T.nilable(String)) }
1006
+ attr_reader :tool_call_id
1007
+
1008
+ sig { returns(T.untyped) }
1009
+ attr_reader :response_schema
1010
+
1011
+ sig { returns(T.nilable(String)) }
1012
+ attr_reader :expires_at
1013
+
1014
+ sig { returns(T.untyped) }
1015
+ attr_reader :metadata
1016
+
1017
+ # @param id [String] Unique identifier
1018
+ # @param reason [String] Reason for the interrupt
1019
+ # @param message [String] Human-readable message
1020
+ # @param tool_call_id [String] Associated tool call if applicable
1021
+ # @param response_schema [Object] JSON schema for response
1022
+ # @param expires_at [String] ISO timestamp when interrupt expires
1023
+ # @param metadata [Object] Arbitrary metadata
1024
+ sig do
1025
+ params(
1026
+ id: String,
1027
+ reason: String,
1028
+ message: T.nilable(String),
1029
+ tool_call_id: T.nilable(String),
1030
+ response_schema: T.untyped,
1031
+ expires_at: T.nilable(String),
1032
+ metadata: T.untyped
1033
+ ).void
1034
+ end
1035
+ def initialize(id:, reason:, message: nil, tool_call_id: nil, response_schema: nil, expires_at: nil, metadata: nil)
1036
+ @id = id
1037
+ @reason = reason
1038
+ @message = message
1039
+ @tool_call_id = tool_call_id
1040
+ @response_schema = response_schema
1041
+ @expires_at = expires_at
1042
+ @metadata = metadata
1043
+ end
1044
+
1045
+ sig { returns(T::Hash[Symbol, T.untyped]) }
1046
+ def to_h
1047
+ {
1048
+ id: @id,
1049
+ reason: @reason,
1050
+ message: @message,
1051
+ tool_call_id: @tool_call_id,
1052
+ # `response_schema` is a JSON Schema document supplied by the user — preserve keys verbatim.
1053
+ response_schema: @response_schema.nil? ? nil : AgUiProtocol::Util::Opaque.new(@response_schema),
1054
+ expires_at: @expires_at,
1055
+ # `metadata` is arbitrary user-defined key/value data — preserve keys verbatim.
1056
+ metadata: @metadata.nil? ? nil : AgUiProtocol::Util::Opaque.new(@metadata)
1057
+ }
1058
+ end
1059
+ end
1060
+
1061
+ # Represents an entry for resuming an interrupted run.
1062
+ #
1063
+ # ```ruby
1064
+ #
1065
+ # entry = AgUiProtocol::Core::Types::ResumeEntry.new(
1066
+ # interrupt_id: "int_1",
1067
+ # status: "resolved",
1068
+ # payload: { "answer" => "42" }
1069
+ # )
1070
+ #
1071
+ # ```
1072
+ class ResumeEntry < Model
1073
+ sig { returns(String) }
1074
+ attr_reader :interrupt_id
1075
+
1076
+ # Free-form string; protocol values are "resolved" or "cancelled" but not enforced by this SDK.
1077
+ sig { returns(T.nilable(String)) }
1078
+ attr_reader :status
1079
+
1080
+ sig { returns(T.untyped) }
1081
+ attr_reader :payload
1082
+
1083
+ # @param interrupt_id [String] ID of the interrupt being resolved
1084
+ # @param status [String] Resolution status (free-form; protocol values are "resolved" or "cancelled")
1085
+ # @param payload [Object] Response payload for the interrupt
1086
+ sig { params(interrupt_id: String, status: T.nilable(String), payload: T.untyped).void }
1087
+ def initialize(interrupt_id:, status: nil, payload: nil)
1088
+ @interrupt_id = interrupt_id
1089
+ @status = status
1090
+ @payload = payload
1091
+ end
1092
+
1093
+ sig { returns(T::Hash[Symbol, T.untyped]) }
1094
+ def to_h
1095
+ {
1096
+ interrupt_id: @interrupt_id,
1097
+ status: @status,
1098
+ # `payload` is the user's resume response — preserve keys verbatim.
1099
+ payload: @payload.nil? ? nil : AgUiProtocol::Util::Opaque.new(@payload)
614
1100
  }
615
1101
  end
616
1102
  end
@@ -644,7 +1130,7 @@ module AgUiProtocol
644
1130
  sig { returns(T.untyped) }
645
1131
  attr_reader :state
646
1132
 
647
- sig { returns(T::Array[BaseMessage]) }
1133
+ sig { returns(T::Array[T.any(BaseMessage, ActivityMessage)]) }
648
1134
  attr_reader :messages
649
1135
 
650
1136
  sig { returns(T::Array[Tool]) }
@@ -656,30 +1142,35 @@ module AgUiProtocol
656
1142
  sig { returns(T.untyped) }
657
1143
  attr_reader :forwarded_props
658
1144
 
1145
+ sig { returns(T.nilable(T::Array[ResumeEntry])) }
1146
+ attr_reader :resume
1147
+
659
1148
  # @param thread_id [String] ID of the conversation thread
660
1149
  # @param run_id [String] ID of the current run
661
1150
  # @param state [Object] Current state of the agent
662
- # @param messages [Array<BaseMessage>] List of messages in the conversation
1151
+ # @param messages [Array<BaseMessage, ActivityMessage>] List of messages in the conversation
663
1152
  # @param tools [Array<Tool>] List of tools available to the agent
664
1153
  # @param context [Array<Context>] List of context objects provided to the agent
665
1154
  # @param forwarded_props [Object] Additional properties forwarded to the agent
666
1155
  # @param parent_run_id [String] Lineage pointer for branching/time travel
667
- # @raise [ArgumentError] if messages is not an Array of BaseMessage
1156
+ # @param resume [Array<ResumeEntry>] Entries for resuming interrupted runs
1157
+ # @raise [ArgumentError] if messages is not an Array of BaseMessage or ActivityMessage
668
1158
  sig do
669
1159
  params(
670
1160
  thread_id: String,
671
1161
  run_id: String,
672
1162
  state: T.untyped,
673
- messages: T::Array[BaseMessage],
1163
+ messages: T::Array[T.any(BaseMessage, ActivityMessage)],
674
1164
  tools: T::Array[Tool],
675
1165
  context: T::Array[Context],
676
1166
  forwarded_props: T.untyped,
677
- parent_run_id: T.nilable(String)
1167
+ parent_run_id: T.nilable(String),
1168
+ resume: T.nilable(T::Array[ResumeEntry])
678
1169
  ).void.checked(:always)
679
1170
  end
680
- def initialize(thread_id:, run_id:, state:, messages:, tools:, context:, forwarded_props:, parent_run_id: nil)
681
- unless messages.is_a?(Array) && messages.all? { |m| m.is_a?(BaseMessage) }
682
- raise ArgumentError, "messages must be an Array of BaseMessage"
1171
+ def initialize(thread_id:, run_id:, state:, messages:, tools:, context:, forwarded_props:, parent_run_id: nil, resume: nil)
1172
+ unless messages.is_a?(Array) && messages.all? { |m| m.is_a?(BaseMessage) || m.is_a?(ActivityMessage) }
1173
+ raise ArgumentError, "messages must be an Array of BaseMessage or ActivityMessage"
683
1174
  end
684
1175
  unless tools.is_a?(Array) && tools.all? { |m| m.is_a?(Tool) }
685
1176
  raise ArgumentError, "tools must be an Array of Tool"
@@ -687,6 +1178,9 @@ module AgUiProtocol
687
1178
  unless context.is_a?(Array) && context.all? { |m| m.is_a?(Context) }
688
1179
  raise ArgumentError, "context must be an Array of Context"
689
1180
  end
1181
+ unless resume.nil? || (resume.is_a?(Array) && resume.all? { |r| r.is_a?(ResumeEntry) })
1182
+ raise ArgumentError, "resume must be an Array of ResumeEntry"
1183
+ end
690
1184
 
691
1185
  @thread_id = thread_id
692
1186
  @run_id = run_id
@@ -696,6 +1190,7 @@ module AgUiProtocol
696
1190
  @tools = tools
697
1191
  @context = context
698
1192
  @forwarded_props = forwarded_props
1193
+ @resume = resume
699
1194
  end
700
1195
 
701
1196
  sig { returns(T::Hash[Symbol, T.untyped]) }
@@ -704,11 +1199,14 @@ module AgUiProtocol
704
1199
  thread_id: @thread_id,
705
1200
  run_id: @run_id,
706
1201
  parent_run_id: @parent_run_id,
707
- state: @state,
1202
+ # `state` is the agent's user-defined state object — preserve keys verbatim.
1203
+ state: @state.nil? ? nil : AgUiProtocol::Util::Opaque.new(@state),
708
1204
  messages: @messages,
709
1205
  tools: @tools,
710
1206
  context: @context,
711
- forwarded_props: @forwarded_props
1207
+ # `forwarded_props` is arbitrary user-defined key/value data — preserve keys verbatim.
1208
+ forwarded_props: @forwarded_props.nil? ? nil : AgUiProtocol::Util::Opaque.new(@forwarded_props),
1209
+ resume: @resume
712
1210
  }
713
1211
  end
714
1212
  end