ruby_llm-ai_sdk 0.1.0 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 83d6d8d1f54099cf62f2f8514780f8df54b185e7ad9e0fe7dbd5bb6e57be4a45
4
- data.tar.gz: 49069ade3a6b4ff8fe19a417680651740331df524b1126ebe7fec854cd9882ed
3
+ metadata.gz: 95427bfb5aaa6e2a7f396d47095e84b104886203850cdd88d5e5d2a0b75f9ef6
4
+ data.tar.gz: a6191fc53db9e82dd82d84eb0bd2ad2b76fbf2e1fd71ab820db459671ee36151
5
5
  SHA512:
6
- metadata.gz: 53f1644c79f531dd9d89fc655e900b6a27b50c0d55a41b7dec1d8d81b12991a2cca82d9bec777bd1cc685071f321e904f73f7efa55c30abd7379ff025ab910df
7
- data.tar.gz: 21219200c9740960c39bab1d1b8ae88c663009a0b6b2de0e8977e23cc45f6229470144e2ca336d27529388b70159a0ac7cc68c6b98010ac4cc199500ca6f79f3
6
+ metadata.gz: 2ed760fa36e55304ee7c96cba1c0e3751a493384704e56c934adea23c8b4e527c84955386414886cc2ecbfe247e36f0a33d746d4a5b18c4ed69f2aabd8e8ec83
7
+ data.tar.gz: d4646943ad0bca03f949273ef523b66389e14bf8f8d5c58e355f2ba5dee0085037680a2a160b7e0bb343196d0a331b2a80195f4cfe0c8ffe767a6e24df454c99
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.0
4
+
5
+ - Files attached to a `useChat` message reach the chat: hosted files by URL, data URLs decoded into attachments.
6
+ - `examples/` holds the Rails and Next.js pair the gem was verified with end to end.
7
+
3
8
  ## 0.1.0
4
9
 
5
10
  - `RubyLLM::AiSdk::Stream` writes a chat turn as AI SDK UI message stream events: text, reasoning, tool calls, tool results, and approval requests.
data/README.md CHANGED
@@ -39,7 +39,9 @@ const { messages, sendMessage, addToolApprovalResponse } = useChat({
39
39
  });
40
40
  ```
41
41
 
42
- `stream_chat` reads the body `useChat` posts. When the last message is from the user, it asks it. When the last assistant message carries approval decisions, it records them with `chat.approve` or `chat.deny` and continues the parked turn.
42
+ `stream_chat` reads the body `useChat` posts. When the last message is from the user, it asks it, with any attached files passed to `chat.ask` as attachments. When the last assistant message carries approval decisions, it records them with `chat.approve` or `chat.deny` and continues the parked turn.
43
+
44
+ [`examples/`](examples/) holds a complete pair, a Rails API app and a Next.js page, that exercises text, tools, approval and denial through a browser.
43
45
 
44
46
  ## Usage anywhere else
45
47
 
@@ -89,6 +91,10 @@ Declare a tool with `requires_approval` and the turn stops before it runs. The c
89
91
  - **Tool results come from the transcript, not the callback.** RubyLLM's `after_tool_result` does not carry the tool call id, so results are read from the tool messages appended to the chat. That also keeps concurrent tool execution correct.
90
92
  - **Steps follow model responses.** Each assistant message closes a step; a text or reasoning chunk that arrives with no step open opens one.
91
93
 
94
+ ## Files
95
+
96
+ `useChat` sends file parts as data URLs by default, or as hosted URLs when you upload them yourself. Both reach `chat.ask` through `with:`: hosted files as their URL, data URLs decoded into a `RubyLLM::Attachment` that keeps the filename, so RubyLLM detects the type and sends it the way the provider expects.
97
+
92
98
  ## Development
93
99
 
94
100
  ```bash
@@ -12,6 +12,8 @@ module RubyLLM
12
12
  # request.approvals # => []
13
13
  #
14
14
  class Request
15
+ DATA_URL = %r{\Adata:[^,]*;base64,(?<data>.*)\z}m # :nodoc:
16
+
15
17
  # One decision on a tool call that required approval.
16
18
  Approval = Struct.new(:tool_call_id, :approved, keyword_init: true) do
17
19
  # Returns whether the person approved the tool call.
@@ -49,6 +51,15 @@ module RubyLLM
49
51
  parts_of_type('text').map { |part| part['text'] }.join("\n")
50
52
  end
51
53
 
54
+ # Returns the files attached to the last user message as sources
55
+ # Chat#ask accepts with +with:+. Hosted files come through as their
56
+ # URL; data URLs are decoded into an Attachment with the file's name.
57
+ def files
58
+ return [] unless last_message&.fetch('role', nil) == 'user'
59
+
60
+ parts_of_type('file').map { |part| file_source(part) }
61
+ end
62
+
52
63
  # Returns the Approval decisions recorded on the last assistant
53
64
  # message, an empty array when there are none.
54
65
  def approvals
@@ -60,6 +71,12 @@ module RubyLLM
60
71
  end
61
72
 
62
73
  private
74
+ def file_source(part)
75
+ match = DATA_URL.match(part['url'].to_s)
76
+ return part['url'] unless match
77
+
78
+ RubyLLM::Attachment.new(StringIO.new(Base64.decode64(match[:data])), filename: part['filename'])
79
+ end
63
80
 
64
81
  def last_message
65
82
  messages.last
@@ -30,12 +30,12 @@ module RubyLLM
30
30
 
31
31
  # Handles one +useChat+ request end to end. Records the approval
32
32
  # decisions when the request carries them and continues the parked
33
- # turn, otherwise asks the last user message. Returns the final
34
- # Message.
33
+ # turn, otherwise asks the last user message with its files attached.
34
+ # Returns the final Message.
35
35
  def serve(request)
36
36
  request = Request.parse(request) if request.is_a?(String)
37
37
  request = Request.new(request) if request.is_a?(Hash)
38
- return ask(request.text) if request.approvals.empty?
38
+ return ask(request.text, with: request.files) if request.approvals.empty?
39
39
 
40
40
  request.approvals.each { |approval| decide(approval) }
41
41
  complete(message_id: request.message_id)
@@ -3,6 +3,6 @@
3
3
  module RubyLLM
4
4
  module AiSdk
5
5
  # The version of the ruby_llm-ai_sdk gem, as a string.
6
- VERSION = '0.1.0'
6
+ VERSION = '0.2.0'
7
7
  end
8
8
  end
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'base64'
3
4
  require 'json'
4
5
  require 'securerandom'
6
+ require 'stringio'
5
7
  require 'ruby_llm'
6
8
  require_relative 'ai_sdk/version'
7
9
  require_relative 'ai_sdk/request'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_llm-ai_sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bruno Costanzo