ask-agent 0.38.0 → 0.39.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/lib/ask/agent/chat.rb +42 -4
- data/lib/ask/agent/loop.rb +2 -2
- data/lib/ask/agent/session.rb +23 -6
- data/lib/ask/agent/version.rb +1 -1
- data/lib/ask/agent.rb +1 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e2b75f968977ca6b41df200a5dbba36cc73d799bc74e91ad3a201a3b9a2c78e7
|
|
4
|
+
data.tar.gz: 19fe24495b1f70b44a320c98b77ee8c2fdf9d83a5642e4bf58c071f8e16ec45c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5c9057c8b69cbe470beb46d9c656bf56ea15abf1d5328a93bc1a66cbef9c8f69f77aa0cfeb9f64cce264c60278f4df0c9290e3d89b71c42816ff2755d91b3e5e
|
|
7
|
+
data.tar.gz: 6df2b868b1430e00c9f2fab030ab48b1128de64b23a29b22f9c70963b977c777e20aaf75d5da529f772998116c453763b935ca92d4495efa21bd04efe309caf4
|
data/lib/ask/agent/chat.rb
CHANGED
|
@@ -53,8 +53,9 @@ module Ask
|
|
|
53
53
|
@prompt_caching = prompt_caching.nil? ? config.prompt_caching : prompt_caching
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
-
def ask(message = nil, &block)
|
|
57
|
-
|
|
56
|
+
def ask(message = nil, attachments: nil, &block)
|
|
57
|
+
validate_attachment_modalities!(attachments)
|
|
58
|
+
@messages << Ask::Message.new(role: :user, content: merge_attachments(message, attachments)) if message || attachments
|
|
58
59
|
|
|
59
60
|
stream = block_given?
|
|
60
61
|
tool_defs = @tools.map { |t| Ask::ToolDef.from_tool(t) }
|
|
@@ -79,10 +80,11 @@ module Ask
|
|
|
79
80
|
response_msg
|
|
80
81
|
end
|
|
81
82
|
|
|
82
|
-
def add_message(role:, content: nil, tool_call_id: nil, tool_calls: nil)
|
|
83
|
+
def add_message(role:, content: nil, tool_call_id: nil, tool_calls: nil, attachments: nil)
|
|
84
|
+
validate_attachment_modalities!(attachments) if role == :user
|
|
83
85
|
@messages << Ask::Message.new(
|
|
84
86
|
role: role,
|
|
85
|
-
content: content,
|
|
87
|
+
content: merge_attachments(content, attachments),
|
|
86
88
|
tool_call_id: tool_call_id,
|
|
87
89
|
tool_calls: tool_calls
|
|
88
90
|
)
|
|
@@ -112,6 +114,42 @@ module Ask
|
|
|
112
114
|
|
|
113
115
|
MAX_CHAT_RETRIES = 3
|
|
114
116
|
|
|
117
|
+
# Merge attachments into message content as content blocks: a plain
|
|
118
|
+
# string becomes a Text block followed by the attachment blocks;
|
|
119
|
+
# Array content has the blocks appended. Returns the content
|
|
120
|
+
# unchanged when there are no attachments.
|
|
121
|
+
def merge_attachments(content, attachments)
|
|
122
|
+
return content if attachments.nil? || attachments.empty?
|
|
123
|
+
|
|
124
|
+
blocks = content.is_a?(Array) ? content.dup : (content ? [Ask::Content::Text.new(content.to_s)] : [])
|
|
125
|
+
Ask::Attachment.wrap_all(attachments).each do |item|
|
|
126
|
+
blocks << (item.is_a?(Ask::Attachment) ? item.to_content : item)
|
|
127
|
+
end
|
|
128
|
+
blocks
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# :inline attachments must be within the model's input modalities.
|
|
132
|
+
# :context attachments are plain text (a manifest line) and always
|
|
133
|
+
# supported. The check is skipped when the catalog has no modality
|
|
134
|
+
# info for the model.
|
|
135
|
+
def validate_attachment_modalities!(attachments)
|
|
136
|
+
return if attachments.nil? || attachments.empty?
|
|
137
|
+
|
|
138
|
+
inline = Ask::Attachment.wrap_all(attachments).select { |a| a.is_a?(Ask::Attachment) && a.inline? }
|
|
139
|
+
return if inline.empty?
|
|
140
|
+
|
|
141
|
+
modalities = @model_info.respond_to?(:modalities) ? @model_info.modalities : nil
|
|
142
|
+
supported = modalities.is_a?(Hash) ? Array(modalities[:input] || modalities["input"]) : []
|
|
143
|
+
return if supported.empty? || supported.include?("*")
|
|
144
|
+
|
|
145
|
+
unsupported = inline.reject { |a| supported.include?(a.type.to_s) }
|
|
146
|
+
return if unsupported.empty?
|
|
147
|
+
|
|
148
|
+
raise Ask::Agent::UnsupportedAttachmentError,
|
|
149
|
+
"Model #{@model_id} cannot receive #{unsupported.map(&:type).uniq.join(', ')} attachments " \
|
|
150
|
+
"(supported input modalities: #{supported.join(', ')})"
|
|
151
|
+
end
|
|
152
|
+
|
|
115
153
|
def provider
|
|
116
154
|
@test_provider || @provider ||= build_provider
|
|
117
155
|
end
|
data/lib/ask/agent/loop.rb
CHANGED
|
@@ -17,12 +17,12 @@ module Ask
|
|
|
17
17
|
@max_consecutive_tool_turns = max_consecutive_tool_turns
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
def run_turn(chat:, message:, tools:, tool_executor:, compactor:, hooks:, event_emitter:, session_id: nil, persist: nil, tool_call_repair: nil, steer_source: nil)
|
|
20
|
+
def run_turn(chat:, message:, tools:, tool_executor:, compactor:, hooks:, event_emitter:, session_id: nil, persist: nil, tool_call_repair: nil, steer_source: nil, attachments: nil)
|
|
21
21
|
raise MaxTurnsExceeded if @turn_count >= @max_turns
|
|
22
22
|
|
|
23
23
|
event_emitter.emit(Events::TurnStart.new)
|
|
24
24
|
|
|
25
|
-
response = chat.ask(message) do |chunk|
|
|
25
|
+
response = chat.ask(message, attachments: attachments) do |chunk|
|
|
26
26
|
if chunk.content.to_s.strip.length > 0
|
|
27
27
|
event_emitter.emit(Events::TextDelta.new(content: chunk.content))
|
|
28
28
|
end
|
data/lib/ask/agent/session.rb
CHANGED
|
@@ -196,7 +196,7 @@ module Ask
|
|
|
196
196
|
# (only when the +artifacts:+ option is enabled)
|
|
197
197
|
attr_reader :artifact_store
|
|
198
198
|
|
|
199
|
-
def run(message, tools: nil, reset: true)
|
|
199
|
+
def run(message, tools: nil, reset: true, attachments: nil)
|
|
200
200
|
raise "Session deleted" if @deleted
|
|
201
201
|
raise "Session already running" if @running
|
|
202
202
|
|
|
@@ -230,6 +230,7 @@ module Ask
|
|
|
230
230
|
response = @loop.run_turn(
|
|
231
231
|
chat: @chat,
|
|
232
232
|
message: message,
|
|
233
|
+
attachments: attachments,
|
|
233
234
|
tools: active_tools,
|
|
234
235
|
tool_executor: @tool_executor,
|
|
235
236
|
compactor: @compactor,
|
|
@@ -443,7 +444,7 @@ module Ask
|
|
|
443
444
|
data[:messages].each do |msg|
|
|
444
445
|
session.chat.add_message(
|
|
445
446
|
role: msg[:role].to_sym,
|
|
446
|
-
content: msg[:content],
|
|
447
|
+
content: deserialize_content(msg[:content]),
|
|
447
448
|
tool_call_id: msg[:tool_call_id]
|
|
448
449
|
)
|
|
449
450
|
end
|
|
@@ -662,8 +663,11 @@ end
|
|
|
662
663
|
# @param message [String]
|
|
663
664
|
# @param expected_turn_id [Integer, nil] the turn id the caller
|
|
664
665
|
# believes is current; nil skips the check
|
|
666
|
+
# @param attachments [Array<Ask::Attachment>, nil] files to attach
|
|
667
|
+
# (applied when the session is idle; queued steers keep the
|
|
668
|
+
# message text only — the loop resolves queued messages as text)
|
|
665
669
|
# @return [Hash] {status: :stale|:queued|:steered, turn_id: Integer}
|
|
666
|
-
def steer(message, expected_turn_id: nil)
|
|
670
|
+
def steer(message, expected_turn_id: nil, attachments: nil)
|
|
667
671
|
@steer_mutex.synchronize do
|
|
668
672
|
if expected_turn_id && expected_turn_id != @turn_id
|
|
669
673
|
return { status: :stale, turn_id: @turn_id }
|
|
@@ -673,7 +677,7 @@ end
|
|
|
673
677
|
return { status: :queued, turn_id: @turn_id }
|
|
674
678
|
end
|
|
675
679
|
end
|
|
676
|
-
@chat.add_message(role: :user, content: message.to_s)
|
|
680
|
+
@chat.add_message(role: :user, content: message.to_s, attachments: attachments)
|
|
677
681
|
{ status: :steered, turn_id: @turn_id }
|
|
678
682
|
end
|
|
679
683
|
|
|
@@ -959,7 +963,7 @@ end
|
|
|
959
963
|
data[:messages].each do |msg|
|
|
960
964
|
target.chat.add_message(
|
|
961
965
|
role: msg[:role].to_sym,
|
|
962
|
-
content: msg[:content],
|
|
966
|
+
content: self.class.deserialize_content(msg[:content]),
|
|
963
967
|
tool_call_id: msg[:tool_call_id]
|
|
964
968
|
)
|
|
965
969
|
end
|
|
@@ -977,13 +981,26 @@ end
|
|
|
977
981
|
@tools.reject { |t| t.is_a?(Ask::Skills::LoadSkillTool) }
|
|
978
982
|
end
|
|
979
983
|
|
|
984
|
+
# Persist content blocks as their +to_h+ hashes so attachments
|
|
985
|
+
# survive save/load; plain messages stay strings.
|
|
986
|
+
def self.serialize_content(message)
|
|
987
|
+
message.content_blocks ? message.content_blocks.map(&:to_h) : message.content.to_s
|
|
988
|
+
end
|
|
989
|
+
|
|
990
|
+
# Rebuild message content from a persisted value: block hashes are
|
|
991
|
+
# reconstructed via Ask::Content.from_h (deep_symbolize_keys may have
|
|
992
|
+
# symbol keys — from_h normalizes).
|
|
993
|
+
def self.deserialize_content(content)
|
|
994
|
+
content.is_a?(Array) ? content.map { |block| Ask::Content.from_h(block) } : content
|
|
995
|
+
end
|
|
996
|
+
|
|
980
997
|
def persist!
|
|
981
998
|
payload = {
|
|
982
999
|
id: @id,
|
|
983
1000
|
messages: @chat.messages.map { |m|
|
|
984
1001
|
{
|
|
985
1002
|
role: m.role,
|
|
986
|
-
content:
|
|
1003
|
+
content: self.class.serialize_content(m),
|
|
987
1004
|
tool_call_id: m.tool_call_id,
|
|
988
1005
|
created_at: Time.now.iso8601
|
|
989
1006
|
}
|
data/lib/ask/agent/version.rb
CHANGED
data/lib/ask/agent.rb
CHANGED