ask-core 0.7.0 → 0.8.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: 07cf04e5db86d1aa2ed63aad10f47608619f320e00af7d730b0cd5b950f347ff
4
- data.tar.gz: 99ef57dd70f631d3bfa024eb03b63279df7d251cab2afa68988cecd66140bc92
3
+ metadata.gz: 4a806c5e8835fe74ef4bf8941a314ed462fa39bd3d607e6de112b92df9a8bbb8
4
+ data.tar.gz: a6301f23b50af2d5e508eb73e072951a2e3526a3c0c2776a92b24b281111ef93
5
5
  SHA512:
6
- metadata.gz: 62ca489c64a4b6ef8e89603e9a4d0442d7b9a4c5c54e61cbb46dc3803046d1b120e2b5e061c6826ee0ad1b2fc8cec9618f6fe3ec5587970efdd99a29d46b89af
7
- data.tar.gz: c228dc1e27f86a24eddc30b60e928dbdf2ca59ee45feb441954b07c91787ba55c825d3d537fb6cca7fba312908100d220ab63ab283d5b6736bb0fe60d1248ba3
6
+ metadata.gz: 685fd487472dcb656e7b928e32d8e972ce27b963e78c8a64b167fe3a76691268878b87cf3079ff39d530cd23082d33ad919e36b01d209a790a9225c89d26b3bf
7
+ data.tar.gz: ba3bae4f9839de021ac62c3aefb0e9675b159151e5d45c5b113e48c6ec042594c658568944e78075b0c38e9dc945c02be13c40dfd2cf6901ed181b7bf9184fe3
data/CHANGELOG.md CHANGED
@@ -1,3 +1,23 @@
1
+ ## [0.8.0] — 2026-07-28
2
+
3
+ ### Changed
4
+
5
+ - **`Ask::State::Memory` moved to `ask-state-providers`** — `ask-core` now only contains the abstract `State::Adapter` contract. The in-memory implementation is available in the `ask-state-providers` gem under the same class name (`Ask::State::Memory`). This keeps `ask-core` purely foundational.
6
+
7
+ ```ruby
8
+ # Before (ask-core 0.7.x)
9
+ require "ask"
10
+ store = Ask::State::Memory.new
11
+
12
+ # After — add ask-state-providers gem
13
+ require "ask-state-providers"
14
+ store = Ask::State::Memory.new # same class name, same API
15
+ ```
16
+
17
+ ### Tested
18
+
19
+ - 281 tests, 549 assertions, 0 failures
20
+
1
21
  ## [0.7.0] — 2026-07-26
2
22
 
3
23
  ### Added
data/lib/ask/state.rb CHANGED
@@ -1,17 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "securerandom"
4
- require "forwardable"
5
-
6
3
  module Ask
7
4
  # Pluggable state backend for agent sessions, channel providers, and
8
5
  # any other ask-rb component that needs durable key-value storage,
9
6
  # distributed locking, message queues, or ordered lists.
10
7
  #
11
8
  # The {State::Adapter} abstract base defines the contract.
12
- # {State::Memory} provides an in-process implementation backed by Hash.
13
9
  # Production apps can provide Redis, PostgreSQL, or other backends by
14
10
  # subclassing {State::Adapter}.
11
+ # {State::Memory} (in-process, Hash-backed) is available in the
12
+ # +ask-state-providers+ gem.
15
13
  #
16
14
  # @example Using the in-memory adapter
17
15
  # store = Ask::State::Memory.new
@@ -187,203 +185,5 @@ module Ask
187
185
  end
188
186
  end
189
187
 
190
- # In-process state backend backed by a Hash.
191
- # All operations are thread-safe via a Mutex.
192
- # Data is not persisted — lost on process exit.
193
- class Memory < Adapter
194
- def initialize
195
- @data = {}
196
- @locks = {}
197
- @queues = {}
198
- @lists = {}
199
- @mutex = Mutex.new
200
- end
201
-
202
- # -- key-value --
203
-
204
- def get(key)
205
- @mutex.synchronize do
206
- expiry = @data[key]&.dig(:expires_at)
207
- return nil if expiry && Time.now >= expiry
208
-
209
- @data[key]&.dig(:value)
210
- end
211
- end
212
-
213
- def set(key, value, ttl: nil)
214
- @mutex.synchronize do
215
- @data[key] = {
216
- value: value,
217
- expires_at: ttl ? Time.now + ttl : nil
218
- }
219
- end
220
- end
221
-
222
- def delete(key)
223
- @mutex.synchronize { @data.delete(key) }
224
- end
225
-
226
- def set_if_not_exists(key, value, ttl: nil)
227
- @mutex.synchronize do
228
- if @data.key?(key)
229
- expiry = @data[key][:expires_at]
230
- return false if expiry.nil? || Time.now < expiry
231
-
232
- # Key expired — treat as nonexistent
233
- @data.delete(key)
234
- end
235
-
236
- @data[key] = {
237
- value: value,
238
- expires_at: ttl ? Time.now + ttl : nil
239
- }
240
- true
241
- end
242
- end
243
-
244
- def clear
245
- @mutex.synchronize do
246
- @data.clear
247
- @locks.clear
248
- @queues.clear
249
- @lists.clear
250
- end
251
- end
252
-
253
- def exists?(key)
254
- @mutex.synchronize do
255
- expiry = @data[key]&.dig(:expires_at)
256
- return false if expiry && Time.now >= expiry
257
- @data.key?(key)
258
- end
259
- end
260
-
261
- def keys(pattern: nil)
262
- @mutex.synchronize do
263
- # Filter out expired keys
264
- active = @data.select do |_k, v|
265
- expiry = v[:expires_at]
266
- expiry.nil? || Time.now < expiry
267
- end
268
-
269
- keys = active.keys.map(&:to_s)
270
- return keys unless pattern
271
-
272
- regex = self.class.glob_to_regex(pattern)
273
- keys.select { |k| k.match?(regex) }
274
- end
275
- end
276
-
277
- # -- locking --
278
-
279
- def acquire_lock(key, ttl: 10)
280
- @mutex.synchronize do
281
- existing = @locks[key]
282
- if existing.nil? || existing.expired?
283
- lock = Lock.new(
284
- id: key,
285
- token: SecureRandom.hex(16),
286
- expires_at: Time.now + ttl
287
- )
288
- @locks[key] = lock
289
- lock
290
- end
291
- end
292
- end
293
-
294
- def release_lock(key, lock)
295
- @mutex.synchronize do
296
- current = @locks[key]
297
- if current && current.token == lock.token && !current.expired?
298
- @locks.delete(key)
299
- true
300
- else
301
- false
302
- end
303
- end
304
- end
305
-
306
- # -- queues --
307
-
308
- def enqueue(queue, value)
309
- @mutex.synchronize do
310
- @queues[queue] ||= []
311
- entry = QueueEntry.new(
312
- id: SecureRandom.uuid,
313
- value: value,
314
- enqueued_at: Time.now
315
- )
316
- @queues[queue] << entry
317
- entry
318
- end
319
- end
320
-
321
- def dequeue(queue)
322
- @mutex.synchronize do
323
- q = @queues[queue]
324
- return nil unless q&.any?
325
-
326
- q.shift
327
- end
328
- end
329
-
330
- def queue_depth(queue)
331
- @mutex.synchronize do
332
- (@queues[queue] || []).length
333
- end
334
- end
335
-
336
- # -- lists --
337
-
338
- def list_append(key, value, max_length: nil)
339
- @mutex.synchronize do
340
- @lists[key] ||= []
341
- @lists[key] << value
342
- @lists[key].shift if max_length && @lists[key].length > max_length
343
- end
344
- end
345
-
346
- def list_range(key, start = 0, stop = -1)
347
- @mutex.synchronize do
348
- list = @lists[key] || []
349
- return list if start == 0 && stop == -1
350
-
351
- stop = list.length - 1 if stop == -1 || stop >= list.length
352
- return [] if start > stop
353
-
354
- list[start..stop] || []
355
- end
356
- end
357
-
358
- def list_remove(key, value)
359
- @mutex.synchronize do
360
- list = @lists[key]
361
- return 0 unless list
362
-
363
- before = list.length
364
- list.delete(value)
365
- before - list.length
366
- end
367
- end
368
-
369
- # -- lifecycle --
370
-
371
- def close
372
- @mutex.synchronize do
373
- @data.clear
374
- @locks.clear
375
- @queues.clear
376
- @lists.clear
377
- end
378
- end
379
-
380
- private
381
-
382
- # Convert a glob pattern (*, ?) to a Regexp.
383
- # Delegates to the shared class method for consistency.
384
- def glob_to_regex(pattern)
385
- self.class.glob_to_regex(pattern)
386
- end
387
- end
388
188
  end
389
189
  end
data/lib/ask/stream.rb CHANGED
@@ -35,7 +35,7 @@ module Ask
35
35
  def finished? = !@finish_reason.nil?
36
36
 
37
37
  # @return [Boolean] true if this chunk contains tool calls
38
- def tool_call? = @tool_calls&.any? == true
38
+ def tool_call? = @tool_calls.respond_to?(:any?) && @tool_calls.any?
39
39
 
40
40
  # @return [Boolean] true if this chunk contains thinking/reasoning content
41
41
  def thinking? = @thinking.to_s.length > 0
data/lib/ask/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ask
4
- VERSION = "0.7.0"
4
+ VERSION = "0.8.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ask-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kaka Ruto