ask-agent 0.1.0 → 0.1.2

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: 3a067aefe3faa67280e7ba4cde37994b52f27d5e665e86510e16ee8acf594f80
4
- data.tar.gz: 4d059bcc2cf98b1fa159c4ab7eba622ba1e529063b7d517343fd3a4f59b97180
3
+ metadata.gz: cc1ea1b84a7139944acf9787cfdd3e5a9fad739b759fc4501f59f68bdd3c0b77
4
+ data.tar.gz: 37733f0982e0d991e284d46c9202fe5846fff67a433acae0385a703912c7f898
5
5
  SHA512:
6
- metadata.gz: 4746d44442fbf8bdf9f67b325914cc57401857151caa2e56d5930d2134e19f03ae18d1064039e414e343539efa4c9c6be26ed2badd59898e3c5deb176f7bebe0
7
- data.tar.gz: 1c8319c4cb60213a2de06b7cba0fc8443b217918586559af1c0a61e212d4cd30a58eb1d05784d4a5aca0737af65740dd4042285a8b1dc0976d4957cc426b0a5f
6
+ metadata.gz: 8e4ff593f7856c2fe0da46dfe8091ad38217e29b27aa343e95f6f939ab741b8c249217dbaa397ad609c21b9b4f39e09a781738a556368e69307fa01329fb00d9
7
+ data.tar.gz: fc45d3ae62bc2e325a22dedab88625f312d3e9b07188d9614c5cb2ddfa9683ed31e2bb8473f10344d73ac18f7cef202fcf53cd68272affbab54c208770017dde
@@ -0,0 +1,221 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ask
4
+ module Agent
5
+ # Response message returned by {Chat#ask}.
6
+ # Presents a message-like response interface for ask-agent internal use.
7
+ ResponseMessage = Data.define(:content, :tool_calls, :thinking) do
8
+ def tool_call? = !tool_calls.empty?
9
+ def to_s = content.to_s
10
+ end
11
+
12
+ # Tool call data used in {ResponseMessage} and {ChatChunk}.
13
+ ToolCallInfo = Data.define(:id, :name, :arguments)
14
+
15
+ # Chunk yielded during streaming from {Chat#ask}.
16
+ ChatChunk = Data.define(:content, :tool_calls, :thinking) do
17
+ def tool_call? = !tool_calls.empty?
18
+ end
19
+
20
+ # Thin wrapper around {Ask::Provider} + an internal message array that
21
+ # presents a Chat-like API for ask-agent internal use.
22
+ #
23
+ # Manages conversation history, resolves the correct provider/model,
24
+ # handles streaming chunk accumulation, and normalises tool call
25
+ # formats between Ask::Provider (Array of Hashes) and ask-agent
26
+ # internal usage (Hash of { id => ToolCallInfo }).
27
+ class Chat
28
+ # @return [String] model ID (e.g. "gpt-4o")
29
+ attr_reader :model
30
+
31
+ # @return [Array<Ask::Message>] all messages in the conversation
32
+ attr_reader :messages
33
+
34
+ # @param model [String, #ask] model ID or chat-like object
35
+ # @param tools [Array<Ask::Tool>] tool instances available to the chat
36
+ # @param temperature [Float, nil] sampling temperature
37
+ # @param schema [Ask::Schema, Hash, nil] structured output schema
38
+ def initialize(model:, tools: [], temperature: nil, schema: nil, **)
39
+ @model_id = model.respond_to?(:id) ? model.id : model.to_s
40
+ @model_info = resolve_model(@model_id)
41
+ @tools = tools
42
+ @temperature = temperature
43
+ @schema = schema
44
+ @messages = []
45
+ @provider = nil
46
+ end
47
+
48
+ # Send a user message and get a completion response.
49
+ #
50
+ # @param message [String, nil] user message text
51
+ # @yield [ChatChunk] streaming chunks (only when a block is given)
52
+ # @return [ResponseMessage] the assistant's response
53
+ def ask(message = nil, &block)
54
+ @messages << Ask::Message.new(role: :user, content: message.to_s) if message
55
+
56
+ stream = block_given?
57
+ tool_defs = @tools.map { |t| Ask::ToolDef.from_tool(t) }
58
+
59
+ # Accumulator for tool calls during streaming (keyed by index)
60
+ calls_acc = {}
61
+
62
+ result = provider.chat(
63
+ @messages.map(&:to_h),
64
+ model: @model_id,
65
+ tools: tool_defs,
66
+ temperature: @temperature,
67
+ stream: stream,
68
+ schema: @schema&.respond_to?(:to_json_schema) ? @schema.to_json_schema : @schema
69
+ ) do |raw_chunk|
70
+ next unless block_given?
71
+
72
+ # Accumulate tool calls by index during streaming
73
+ accumulate_tool_calls(raw_chunk, calls_acc)
74
+
75
+ # Yield adapted chunk with current tool call state
76
+ yield ChatChunk.new(
77
+ content: raw_chunk.content,
78
+ tool_calls: build_current_tool_calls(calls_acc),
79
+ thinking: raw_chunk.respond_to?(:thinking) ? raw_chunk.thinking : nil
80
+ )
81
+ end
82
+
83
+ response_msg = if stream
84
+ build_stream_response(result, calls_acc)
85
+ else
86
+ build_response(result)
87
+ end
88
+
89
+ # Store assistant response in conversation history
90
+ @messages << Ask::Message.new(
91
+ role: :assistant,
92
+ content: response_msg.content,
93
+ tool_calls: response_msg.tool_calls&.values&.map { |tc|
94
+ { id: tc.id, type: "function", name: tc.name, arguments: tc.arguments }
95
+ }
96
+ )
97
+
98
+ response_msg
99
+ end
100
+
101
+ # Add a message to the conversation history.
102
+ #
103
+ # @param role [Symbol] :system, :user, :assistant, :tool
104
+ # @param content [String, nil] message content
105
+ # @param tool_call_id [String, nil] tool call ID (for tool results)
106
+ # @param tool_calls [Array<Hash>, nil] tool call invocations
107
+ def add_message(role:, content: nil, tool_call_id: nil, tool_calls: nil)
108
+ @messages << Ask::Message.new(
109
+ role: role,
110
+ content: content,
111
+ tool_call_id: tool_call_id,
112
+ tool_calls: tool_calls
113
+ )
114
+ end
115
+
116
+ # Set or replace the system prompt.
117
+ #
118
+ # @param prompt [String] system instructions
119
+ # @return [self]
120
+ def with_instructions(prompt)
121
+ @messages.reject! { |m| m.role == :system }
122
+ @messages.unshift(Ask::Message.new(role: :system, content: prompt))
123
+ self
124
+ end
125
+
126
+ # Clear all messages from the conversation.
127
+ def reset_messages!
128
+ @messages.clear
129
+ end
130
+
131
+ private
132
+
133
+ # Resolve model info from the catalog.
134
+ def resolve_model(model_id)
135
+ Ask::ModelCatalog.find(model_id)
136
+ rescue Ask::ModelNotFound
137
+ nil
138
+ end
139
+
140
+ # Lazily resolve and instantiate the LLM provider.
141
+ def provider
142
+ @provider ||= build_provider
143
+ end
144
+
145
+ def build_provider
146
+ slug = @model_info&.provider || "openai"
147
+ klass = Ask::Provider.resolve(slug)
148
+ klass.new(provider_config(slug))
149
+ end
150
+
151
+ def provider_config(slug, extra_keys: {})
152
+ env_key = "#{slug.upcase}_API_KEY"
153
+ key = ENV[env_key] || ENV["OPENCODE_API_KEY"] || ENV["OPENAI_API_KEY"]
154
+ base = ENV["#{slug.upcase}_API_BASE"] || ENV["OPENCODE_API_BASE"]
155
+ config = { api_key: key }
156
+ config[:"#{slug}_api_key"] = key
157
+ config[:"#{slug}_api_base"] = base if base
158
+ Ask::LLM::Config.new(config)
159
+ end
160
+
161
+ # Accumulate partial tool calls from streaming chunks.
162
+ def accumulate_tool_calls(raw_chunk, calls_acc)
163
+ return unless raw_chunk.tool_call?
164
+
165
+ raw_chunk.tool_calls.each do |tc|
166
+ idx = tc[:index] || 0
167
+ calls_acc[idx] ||= { id: tc[:id], name: tc[:name], arguments: +"" }
168
+ calls_acc[idx][:id] ||= tc[:id]
169
+ calls_acc[idx][:name] ||= tc[:name]
170
+ calls_acc[idx][:arguments] << tc[:arguments].to_s if tc[:arguments]
171
+ end
172
+ end
173
+
174
+ # Build current snapshot of tool calls from accumulator.
175
+ def build_current_tool_calls(calls_acc)
176
+ hash = {}
177
+ calls_acc.each_value do |tc_data|
178
+ next unless tc_data[:id]
179
+ hash[tc_data[:id]] = ToolCallInfo.new(
180
+ id: tc_data[:id],
181
+ name: tc_data[:name] || "",
182
+ arguments: tc_data[:arguments]
183
+ )
184
+ end
185
+ hash
186
+ end
187
+
188
+ # Convert Ask::Provider tool_calls (Array of Hashes) to Hash.
189
+ def build_tool_call_hash(raw_calls)
190
+ hash = {}
191
+ raw_calls.each do |tc|
192
+ id = tc[:id] || tc["id"]
193
+ next unless id
194
+ hash[id] = ToolCallInfo.new(
195
+ id: id,
196
+ name: tc[:name] || tc["name"] || "",
197
+ arguments: tc[:arguments] || tc["arguments"] || ""
198
+ )
199
+ end
200
+ hash
201
+ end
202
+
203
+ # Build response from streaming result.
204
+ def build_stream_response(stream, calls_acc)
205
+ thinking = stream.chunks.filter_map(&:thinking).last
206
+ ResponseMessage.new(
207
+ content: stream.accumulated_text,
208
+ tool_calls: build_current_tool_calls(calls_acc),
209
+ thinking: thinking
210
+ )
211
+ end
212
+
213
+ # Build response from non-streaming result.
214
+ def build_response(msg)
215
+ tool_calls = msg.tool_calls ? build_tool_call_hash(msg.tool_calls) : {}
216
+ thinking = msg.respond_to?(:thinking) ? msg.thinking : nil
217
+ ResponseMessage.new(content: msg.content.to_s, tool_calls: tool_calls, thinking: thinking)
218
+ end
219
+ end
220
+ end
221
+ end
@@ -138,8 +138,8 @@ module Ask
138
138
 
139
139
  def build_llm_chat
140
140
  if @llm.respond_to?(:ask) then @llm
141
- elsif @llm.is_a?(String) then RubyLLM::Chat.new(model: @llm)
142
- else RubyLLM::Chat.new end
141
+ elsif @llm.is_a?(String) then Ask::Agent::Chat.new(model: @llm)
142
+ else Ask::Agent::Chat.new(model: Ask::Agent.configuration.default_model) end
143
143
  end
144
144
 
145
145
  def extract_summary
@@ -100,7 +100,7 @@ module Ask
100
100
 
101
101
  def call_llm_for_analysis(telemetry_data, existing_recommendations, resolved_ids)
102
102
  prompt = build_analysis_prompt(telemetry_data, existing_recommendations, resolved_ids)
103
- chat = RubyLLM::Chat.new(model: @model, **@chat_options)
103
+ chat = Ask::Agent::Chat.new(model: @model, **@chat_options)
104
104
  response = chat.ask(prompt)
105
105
  parse_llm_response(response.content.to_s)
106
106
  rescue => e
@@ -66,12 +66,12 @@ module Ask
66
66
 
67
67
  def build_eval_chat
68
68
  model_id = model_id_from(@model)
69
- RubyLLM::Chat.new(model: model_id)
69
+ Ask::Agent::Chat.new(model: model_id)
70
70
  end
71
71
 
72
72
  def model_id_from(model)
73
73
  case model
74
- when RubyLLM::Chat then model.model.respond_to?(:id) ? model.model.id : model.model.to_s
74
+ when Ask::Agent::Chat then model.model.to_s
75
75
  when String then model
76
76
  else model.to_s
77
77
  end
@@ -14,6 +14,8 @@ module Ask
14
14
  end
15
15
 
16
16
  attr_reader :meta_agent_results
17
+ # @return [Ask::Skills::Registry, nil] auto-discovered skills registry
18
+ attr_reader :skills_registry
17
19
 
18
20
  def initialize(model:, tools: [], max_turns: 25, max_tool_retries: 3,
19
21
  compactor: nil, hooks: {}, persistence: nil,
@@ -35,11 +37,20 @@ module Ask
35
37
 
36
38
  @chat = build_chat(model, system_prompt, tools, **chat_options)
37
39
  @tools = resolve_tools(tools)
38
- register_tools_on_chat
39
40
  @loop = Loop.new(max_turns: max_turns)
40
41
  @tool_executor = ToolExecutor.new(max_retries: max_tool_retries, parallel: parallel_tools)
41
42
  @compactor = compactor ? build_compactor(compactor) : nil
42
43
  @hooks = Hooks.new(hooks)
44
+
45
+ # Auto-discover skills and inject into system prompt
46
+ @skills_registry = Ask::Skills.discover rescue nil
47
+ if @skills_registry && !@skills_registry.names.empty?
48
+ skill_text = @skills_registry.format_for_prompt
49
+ if !skill_text.empty? && @chat.messages.any? { |m| m.role == :system }
50
+ current = @chat.messages.find { |m| m.role == :system }.content.to_s
51
+ @chat.with_instructions(current + skill_text)
52
+ end
53
+ end
43
54
  @persistence = persistence
44
55
 
45
56
  reflector_opts = reflector.is_a?(Hash) ? reflector : {}
@@ -96,7 +107,7 @@ module Ask
96
107
  emit(Events::LoopDetected.new(tool_name: e.message, repeated_count: 3))
97
108
  @telemetry.log(:loop_detected, session_id: @id, tool_name: e.message, repeated_count: 3)
98
109
  response = last_content
99
- rescue RubyLLM::ContextLengthExceededError
110
+ rescue Ask::ContextLengthExceeded
100
111
  if @compactor && !@compactor.overflow_recovered?
101
112
  @compactor.recover_from_overflow
102
113
  retry
@@ -203,6 +214,28 @@ module Ask
203
214
 
204
215
  def abort_requested? = @abort_requested
205
216
 
217
+ # Load a skill by name or file path.
218
+ # Injects the skill's full instructions into the conversation as a system message.
219
+ #
220
+ # @param name [String] skill name (e.g. "rails.db_debug") or path to a .md file
221
+ # @raise [Ask::Skills::Error] if the skill is not found
222
+ def skill(name)
223
+ if @skills_registry && (s = @skills_registry[name])
224
+ @chat.add_message(
225
+ role: :system,
226
+ content: "## Skill: #{s.name}\n\n#{s.description}\n\n---\n\n#{s.instructions}"
227
+ )
228
+ elsif File.exist?(name.to_s)
229
+ content = File.read(name.to_s)
230
+ @chat.add_message(
231
+ role: :system,
232
+ content: "## Skill: #{name}\n\n---\n\n#{content}"
233
+ )
234
+ else
235
+ raise Ask::Skills::Error, "Skill not found: #{name.inspect}"
236
+ end
237
+ end
238
+
206
239
  def reset_messages!
207
240
  @chat.reset_messages!
208
241
  @messages = []
@@ -210,20 +243,11 @@ module Ask
210
243
 
211
244
  private
212
245
 
213
- def register_tools_on_chat
214
- return unless @tools.any?
215
-
216
- def @chat.handle_tool_calls(response, &)
217
- @on[:end_message]&.call(response) if @on[:end_message]
218
- response
219
- end
220
- end
221
-
222
246
  def build_chat(model, system_prompt, tools, **chat_options)
223
247
  if model.respond_to?(:ask)
224
248
  model
225
249
  else
226
- chat = RubyLLM::Chat.new(model: model, **chat_options)
250
+ chat = Ask::Agent::Chat.new(model: model, tools: tools, **chat_options)
227
251
  chat.with_instructions(system_prompt) if system_prompt
228
252
  chat
229
253
  end
@@ -0,0 +1,237 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+ require "time"
5
+
6
+ module Ask
7
+ module Agent
8
+ class Session
9
+ attr_reader :id, :chat, :tools, :turn_count, :created_at, :messages
10
+ attr_reader :tool_calls_made
11
+
12
+ def reflection_count
13
+ @reflector&.reflection_count || 0
14
+ end
15
+
16
+
17
+ # @return [Ask::Skills::Registry, nil] auto-discovered skills registry
18
+ attr_reader :skills_registry
19
+ attr_reader :meta_agent_results
20
+ # @return [Ask::Skills::Registry, nil] auto-discovered skills registry
21
+ attr_reader :skills_registry
22
+
23
+ def initialize(model:, tools: [], max_turns: 25, max_tool_retries: 3,
24
+ compactor: nil, hooks: {}, persistence: nil,
25
+ id: nil, system_prompt: nil, parallel_tools: true,
26
+ reflector: nil, telemetry: true, meta_agent: nil, **chat_options)
27
+ @id = id || SecureRandom.uuid
28
+ @max_turns = max_turns
29
+ @max_tool_retries = max_tool_retries
30
+ @parallel_tools = parallel_tools
31
+ @event_handlers = { all: [] }
32
+ @running = false
33
+ @deleted = false
34
+ @abort_requested = false
35
+ @turn_count = 0
36
+ @created_at = Time.now
37
+ @_no_tools_instructed = false
38
+
39
+ @telemetry = telemetry.is_a?(Telemetry) ? telemetry : Telemetry.new(enabled: !!telemetry)
40
+
41
+ @chat = build_chat(model, system_prompt, tools, **chat_options)
42
+ @tools = resolve_tools(tools)
43
+ @loop = Loop.new(max_turns: max_turns)
44
+ @tool_executor = ToolExecutor.new(max_retries: max_tool_retries, parallel: parallel_tools)
45
+
46
+ # Auto-discover skills and inject into system prompt
47
+ @skills_registry = Ask::Skills.discover rescue nil
48
+ if @skills_registry && !@skills_registry.names.empty?
49
+ skill_text = @skills_registry.format_for_prompt
50
+ if !skill_text.empty? && @chat.messages.any? { |m| m.role == :system }
51
+ current = @chat.messages.find { |m| m.role == :system }.content.to_s
52
+ @chat.with_instructions(current + skill_text)
53
+ end
54
+ end
55
+ @compactor = compactor ? build_compactor(compactor) : nil
56
+ @hooks = Hooks.new(hooks)
57
+
58
+ # Auto-discover skills and inject into system prompt
59
+ @skills_registry = Ask::Skills.discover rescue nil
60
+ if @skills_registry && !@skills_registry.names.empty?
61
+ skill_text = @skills_registry.format_for_prompt
62
+ if !skill_text.empty? && @chat.messages.any? { |m| m.role == :system }
63
+ current = @chat.messages.find { |m| m.role == :system }.content.to_s
64
+ @chat.with_instructions(current + skill_text)
65
+ end
66
+ end
67
+ @persistence = persistence
68
+
69
+ reflector_opts = reflector.is_a?(Hash) ? reflector : {}
70
+ @reflector = if reflector
71
+ Reflector.new(
72
+ model: @chat,
73
+ max_reflections: reflector_opts[:max_reflections] || 1
74
+ )
75
+ end
76
+
77
+ @meta_agent_config = meta_agent
78
+ @meta_agent_results = nil
79
+
80
+
81
+ # Auto-discover skills and inject into system prompt
82
+ @skills_registry = Ask::Skills.discover rescue nil
83
+ if @skills_registry && !@skills_registry.names.empty?
84
+ skill_text = @skills_registry.format_for_prompt
85
+ if !skill_text.empty? && @chat.messages.any? { |m| m.role == :system }
86
+ current = @chat.messages.find { |m| m.role == :system }.content.to_s
87
+ @chat.with_instructions(current + skill_text)
88
+ end
89
+ end
90
+ @compactor&.chat = @chat
91
+ end
92
+
93
+ def run(message, tools: nil)
94
+ raise "Session deleted" if @deleted
95
+ raise "Session already running" if @running
96
+
97
+ @running = true
98
+ @abort_requested = false
99
+ @turn_count = 0
100
+ @loop.reset!
101
+
102
+ emit(Events::SessionStart.new)
103
+
104
+ active_tools = resolve_tools(tools || [])
105
+ active_tools = @tools if active_tools.empty?
106
+
107
+ if active_tools.empty? && !@_no_tools_instructed
108
+ @chat.add_message(role: :system, content: "You have no tools available. Do not claim you can look up information or use tools of any kind. Just respond based on your existing knowledge.")
109
+ @_no_tools_instructed = true
110
+ end
111
+
112
+ begin
113
+ @tool_executor.telemetry = @telemetry
114
+
115
+ response = @loop.run_turn(
116
+ chat: @chat,
117
+ message: message,
118
+ tools: active_tools,
119
+ tool_executor: @tool_executor,
120
+ compactor: @compactor,
121
+ hooks: @hooks,
122
+ event_emitter: self,
123
+ session_id: @id
124
+ )
125
+ rescue MaxTurnsExceeded => e
126
+ emit(Events::MaxTurnsExceeded.new(max_turns: @max_turns))
127
+ @telemetry.log(:max_turns_exceeded, session_id: @id, max_turns: @max_turns)
128
+ response = last_content
129
+ rescue LoopDetected => e
130
+ emit(Events::LoopDetected.new(tool_name: e.message, repeated_count: 3))
131
+ @telemetry.log(:loop_detected, session_id: @id, tool_name: e.message, repeated_count: 3)
132
+ response = last_content
133
+ rescue Ask::ContextLengthExceeded
134
+ if @compactor && !@compactor.overflow_recovered?
135
+ @compactor.recover_from_overflow
136
+ retry
137
+ end
138
+ response = "I'm sorry, the conversation has grown too long. Please start a new session."
139
+ rescue StandardError => e
140
+ emit(Events::Error.new(error: e.message, recoverable: true))
141
+ raise
142
+ ensure
143
+ @running = false
144
+ persist! if @persistence
145
+ end
146
+
147
+ @tool_calls_made = @tool_executor.total_executions
148
+
149
+ if @reflector && @reflector.reflect?(@tool_calls_made) && !@abort_requested
150
+ eval_result = @reflector.evaluate(response: response, event_emitter: self)
151
+ @telemetry.log(:reflection_end, session_id: @id, decision: eval_result[:decision], feedback: eval_result[:feedback])
152
+
153
+ if eval_result[:decision] == :improve && !@abort_requested
154
+ @chat.add_message(
155
+ role: :system,
156
+ content: "Improve your last response: #{eval_result[:feedback]}"
157
+ )
158
+
159
+ response = @loop.run_turn(
160
+ chat: @chat,
161
+ message: "",
162
+ tools: active_tools,
163
+ tool_executor: @tool_executor,
164
+ compactor: @compactor,
165
+ hooks: @hooks,
166
+ event_emitter: self,
167
+ session_id: @id
168
+ )
169
+ end
170
+ end
171
+
172
+ if @meta_agent_config
173
+ @telemetry.increment_session_count!
174
+ try_auto_meta_agent
175
+ end
176
+
177
+ emit(Events::SessionEnd.new(result: response, turn_count: @turn_count, tool_calls_made: @tool_calls_made))
178
+ @messages = @chat.messages.dup
179
+
180
+ response
181
+ end
182
+
183
+ def on_event(&block)
184
+ @event_handlers[:all] << block
185
+ self
186
+ end
187
+
188
+ def on(type, &block)
189
+ @event_handlers[type] ||= []
190
+ @event_handlers[type] << block
191
+ self
192
+ end
193
+
194
+ def emit(event)
195
+ @event_handlers[:all].each { |h| h.call(event) }
196
+ handlers = @event_handlers[event.class]
197
+ handlers&.each { |h| h.call(event) }
198
+ end
199
+
200
+ def running? = @running
201
+ def deleted? = @deleted
202
+
203
+ def save
204
+ persist! if @persistence
205
+ end
206
+
207
+ def self.load(id, adapter:)
208
+ data = adapter.load(id)
209
+ return nil unless data
210
+
211
+ session = new(
212
+ id: data[:id],
213
+ model: data.dig(:metadata, :model),
214
+ tools: data.dig(:metadata, :tools)&.map(&:constantize) || [],
215
+ persistence: adapter
216
+ )
217
+
218
+ data[:messages].each do |msg|
219
+ session.chat.add_message(
220
+ role: msg[:role].to_sym,
221
+ content: msg[:content],
222
+ tool_call_id: msg[:tool_call_id]
223
+ )
224
+ end
225
+
226
+ session
227
+ end
228
+
229
+ def delete
230
+ @deleted = true
231
+ @persistence&.delete(@id)
232
+ end
233
+
234
+ def abort
235
+ @abort_requested = true
236
+ end
237
+
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ask
4
4
  module Agent
5
- VERSION = "0.1.0"
5
+ VERSION = "0.1.2"
6
6
  end
7
7
  end
data/lib/ask/agent.rb CHANGED
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "ruby_llm"
4
3
  require "fileutils"
5
4
  require "json"
6
5
  require "securerandom"
7
6
  require "time"
7
+ require "ask/skills"
8
8
 
9
9
  module Ask
10
10
  module Agent
@@ -41,6 +41,7 @@ end
41
41
 
42
42
  require_relative "agent/version"
43
43
  require_relative "agent/events"
44
+ require_relative "agent/chat"
44
45
  require_relative "agent/telemetry"
45
46
  require_relative "agent/tool_abort_controller"
46
47
  require_relative "agent/session"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ask-agent
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kaka Ruto
@@ -9,6 +9,34 @@ bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: ask-core
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '0.1'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '0.1'
26
+ - !ruby/object:Gem::Dependency
27
+ name: ask-llm-providers
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '0.1'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '0.1'
12
40
  - !ruby/object:Gem::Dependency
13
41
  name: ask-tools
14
42
  requirement: !ruby/object:Gem::Requirement
@@ -24,7 +52,7 @@ dependencies:
24
52
  - !ruby/object:Gem::Version
25
53
  version: '0.1'
26
54
  - !ruby/object:Gem::Dependency
27
- name: ask-tools-shell
55
+ name: ask-skills
28
56
  requirement: !ruby/object:Gem::Requirement
29
57
  requirements:
30
58
  - - "~>"
@@ -38,19 +66,19 @@ dependencies:
38
66
  - !ruby/object:Gem::Version
39
67
  version: '0.1'
40
68
  - !ruby/object:Gem::Dependency
41
- name: ruby_llm
69
+ name: ask-tools-shell
42
70
  requirement: !ruby/object:Gem::Requirement
43
71
  requirements:
44
- - - ">="
72
+ - - "~>"
45
73
  - !ruby/object:Gem::Version
46
- version: '1.14'
47
- type: :development
74
+ version: '0.1'
75
+ type: :runtime
48
76
  prerelease: false
49
77
  version_requirements: !ruby/object:Gem::Requirement
50
78
  requirements:
51
- - - ">="
79
+ - - "~>"
52
80
  - !ruby/object:Gem::Version
53
- version: '1.14'
81
+ version: '0.1'
54
82
  - !ruby/object:Gem::Dependency
55
83
  name: minitest
56
84
  requirement: !ruby/object:Gem::Requirement
@@ -105,6 +133,7 @@ files:
105
133
  - README.md
106
134
  - lib/ask-agent.rb
107
135
  - lib/ask/agent.rb
136
+ - lib/ask/agent/chat.rb
108
137
  - lib/ask/agent/compactor.rb
109
138
  - lib/ask/agent/configuration.rb
110
139
  - lib/ask/agent/events.rb
@@ -118,6 +147,7 @@ files:
118
147
  - lib/ask/agent/persistence/in_memory.rb
119
148
  - lib/ask/agent/reflector.rb
120
149
  - lib/ask/agent/session.rb
150
+ - lib/ask/agent/session_backup.rb
121
151
  - lib/ask/agent/telemetry.rb
122
152
  - lib/ask/agent/tool_abort_controller.rb
123
153
  - lib/ask/agent/tool_executor.rb