Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ gem "rake", "~> 13.3.1"
gem "dotenv-rails"
gem "factory_bot", "~> 6.5.6"
gem "rspec", "~> 3.13.2"
gem "vcr", "~> 6.3.1"
gem "webmock"
gem "pry-byebug"
gem "rubocop"
Expand Down
5 changes: 1 addition & 4 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
ruby-chatkit (0.1.9)
ruby-chatkit (0.2.0)
base64
event_stream_parser
http
Expand Down Expand Up @@ -217,8 +217,6 @@ GEM
unicode-emoji (4.1.0)
uri (1.1.1)
useragent (0.16.11)
vcr (6.3.1)
base64
webmock (3.24.0)
addressable (>= 2.8.0)
crack (>= 0.3.2)
Expand Down Expand Up @@ -247,7 +245,6 @@ DEPENDENCIES
rubocop-rspec
ruby-chatkit!
shoulda-matchers (~> 7.0.1)
vcr (~> 6.3.1)
webmock

BUNDLED WITH
Expand Down
15 changes: 13 additions & 2 deletions lib/chatkit/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ module OpenAI
# @param timeout [Integer, nil] - optional - The timeout for requests.
# @param logger [Logger, nil] - optional - The logger instance.
def initialize(
api_key: ChatKit.configuration.api_key,
api_key: ChatKit.configuration.api_key,
host: ChatKit.configuration.host,
timeout: ChatKit.configuration.timeout,
logger: nil
)
)
@api_key = api_key
@host = host
@timeout = timeout
Expand Down Expand Up @@ -70,6 +70,17 @@ def create_session!(user_id:, workflow_id:, chatkit_configuration: nil, expires_
)
end

# Cancels the current session or a specified session by ID.
# @param session_id [String] - The ID of the session to cancel.
#
# @raise [RuntimeError] If no session ID is provided and there is no current session.
# @return [Session::Cancel::Response] The response from the cancel operation.
def cancel_session!(session_id:)
raise "No session ID provided" if session_id.nil?

Session.cancel!(session_id:, client: self)
end

# Sends a message in a conversation, optionally uploading files.
# @param text [String] The text message to send.
# @param files [Array<File>] - optional - The files to upload and attach to the message.
Expand Down
16 changes: 3 additions & 13 deletions lib/chatkit/conversation/response/thread/item.rb
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def from_event(data)
inference_options: data["inference_options"] || {},
content: data["content"] || [],
quoted_text: data["quoted_text"] || "",
workflow: parse_workflow_data(data)
workflow: Workflow.from_event(data["workflow"])
)
end
end
Expand Down Expand Up @@ -118,7 +118,7 @@ def update_from_event!(data)
if @workflow
@workflow.update!(data["workflow"])
else
@workflow = parse_workflow_data(data)
@workflow = Workflow.from_event(data["workflow"])
end
end

Expand All @@ -138,17 +138,6 @@ def process_update!(update_data)
end
end

protected

# Parse workflow data from event
# @param data [Hash] The event data
# @return [Workflow, nil]
def parse_workflow_data(data)
return nil unless data["workflow"]

Workflow.from_event(data["workflow"])
end

private

# Parse content array into Content objects
Expand Down Expand Up @@ -190,6 +179,7 @@ def handle_content_part_added!(update_data)
# @return [void]
def handle_text_delta!(update_data)
delta_text = update_data["delta"]

return unless delta_text

@delta << delta_text
Expand Down
2 changes: 2 additions & 0 deletions lib/chatkit/conversation/response/thread/item/workflow.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ def initialize(type: nil, tasks: nil, summary: nil, expanded: nil, response_item
# @param data [Hash] The workflow data from the event
# @return [Workflow]
def self.from_event(data)
return nil if data.nil?

new(
type: data["type"],
tasks: data["tasks"],
Expand Down
13 changes: 10 additions & 3 deletions lib/chatkit/request/endpoints.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ module Request
# etc.
module Endpoints
CONVERSATION = "/v1/chatkit/conversation"
SESSIONS = "/v1/chatkit/sessions"
CREATE_SESSION = "/v1/chatkit/sessions"
CANCEL_SESSION = "/v1/chatkit/sessions/%<id>s/cancel"
FILES = "/v1/chatkit/files"

class << self
Expand All @@ -18,8 +19,14 @@ class << self
#
# @return [String] The corresponding endpoint string
ChatKit::Request::Endpoints.constants.each do |const_name|
define_method "#{const_name.to_s.downcase}_endpoint" do
ChatKit::Request::Endpoints.const_get(const_name)
define_method "#{const_name.to_s.downcase}_endpoint" do |path_params = {}|
if path_params
raise ArgumentError, "Path parameters must be provided as a Hash" unless path_params.is_a?(Hash)

format(ChatKit::Request::Endpoints.const_get(const_name), **path_params)
else
ChatKit::Request::Endpoints.const_get(const_name)
end
Comment on lines +22 to +29
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition if path_params will always be true since it defaults to {}. An empty hash is truthy in Ruby, so the else branch at line 28 is unreachable. Change the condition to if path_params.any? or unless path_params.empty? to properly detect when parameters are provided.

Copilot uses AI. Check for mistakes.
end
end
end
Expand Down
164 changes: 33 additions & 131 deletions lib/chatkit/session.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,54 +17,18 @@ module ChatKit
# workflow: { id: "wf_68eeb857eaf8819089eb55d32f39a822050622537973ef99" }
# )
class Session
class SessionError < StandardError; end

module Defaults
ENABLED = true
end

# @!attribute [rw] user_id
# @return [String] The ID of the user.
attr_accessor :user_id

# @!attribute [rw] workflow
# @return [Workflow] The workflow for the session.
attr_accessor :workflow

# @!attribute [rw] chatkit_configuration
# @return [ChatKitConfiguration] The ChatKit configuration.
attr_accessor :chatkit_configuration

# @!attribute [rw] expires_after
# @return [ExpiresAfter] The expiration time for the session.
attr_accessor :expires_after
# @!attribute [rw] sesstion
# @return [Session::Create]
attr_accessor :session

# @!attribute [rw] rate_limits
# @return [RateLimits] The rate limits for the session.
attr_accessor :rate_limits

# @!attribute [rw] response
# @return [Response] The session response data.
attr_accessor :response

# @param workflow [Hash] The ID of the session.
# @param user_id [String] The ID of the user.
# @param chatkit_configuration [Hash, nil] - optional - The ChatKit configuration.
# @param expires_after [Hash, nil] - optional - The expiration time for the session.
# @param rate_limits [Hash, nil] - optional - The rate limits for the session.
# @param client [Ruby::ChatKit::Client] The ChatKit client instance.
def initialize(
user_id:,
workflow:,
chatkit_configuration: nil,
expires_after: nil,
rate_limits: nil,
client: Client.new
)
@client = client
@user_id = user_id
@workflow = Workflow.build(**workflow.to_h)
@chatkit_configuration = ChatKitConfiguration.build(**chatkit_configuration.to_h)
@expires_after = ExpiresAfter.build(**expires_after) if expires_after.is_a?(Hash)
@rate_limits = RateLimits.build(**rate_limits.to_h)
def initialize
@session = nil
end

class << self
Expand All @@ -76,103 +40,41 @@ def create!(
rate_limits: nil,
client: Client.new
)
new(
user_id:,
workflow:,
chatkit_configuration:,
expires_after:,
rate_limits:,
client:
).create!
new.tap do |instance|
instance.session = Create.call(
user_id:,
workflow:,
chatkit_configuration:,
expires_after:,
rate_limits:,
client:
)
end
end
end

# Creates a new ChatKit session.
# @return [Response] The created session data.
def create!
payload = build_payload
response = perform_request(payload)

handle_response_errors(response)

@response = parse_response(response)

self
rescue StandardError => e
raise SessionError, "Session creation failed: #{e.message}"
end

def refresh!
create!
end

private

# @return [Hash]
def build_payload
{
user: user_id,
workflow: workflow.serialize,
chatkit_configuration: chatkit_configuration.serialize,
expires_after: expires_after&.serialize,
rate_limits: rate_limits.serialize,
}.compact
end

# Performs the HTTP request to create a session.
#
# @param payload [Hash] The request payload.
# @return [HTTP::Response] The HTTP response.
def perform_request(payload)
@client.connection.headers(sessions_header).post(sessions_endpoint, json: payload)
end

# Handles HTTP response errors by raising appropriate exceptions.
#
# @param response [HTTP::Response] The HTTP response to check.
# @raise [SessionError] If the response indicates an error.
def handle_response_errors(response)
return unless response.code >= 300

error_message = begin
response.parse["error"]["message"]
rescue StandardError
"Unknown error occurred"
def cancel!(session_id:, client: Client.new)
Cancel.call(session_id:, client:)
end

raise SessionError, error_message
end

# Parses the HTTP response body.
# Cancel the current session.
#
# @param response [HTTP::Response] The HTTP response to parse.
# @return [Hash] The parsed response body.
def parse_response(response)
Response.deserialize(response.parse)
# @raise [SessionError] If there is no session to cancel.
# @return [void]
def cancel!
raise SessionError, "No session available to cancel" if @session.nil?

Cancel.call(session_id: session.response.id)
ensure
@session = nil
end

# Updates the current session with the provided data.
#
# @param data [Hash] The session data to store.
# @return [Response] The updated current session.
def update_current_session(session_response)
ChatKit.current_info.session = session_response
end

# @param response [HTTP::Response] The HTTP response to parse.
# @return [Hash] The parsed response body.
def parse!(response)
Response.deserialize(response.parse)
end

# @return [String] The endpoint URL for sessions.
def sessions_endpoint
Request::Endpoints.sessions_endpoint
end
# Refresh the current session.
# @return [Session::Create] The refreshed session.
def refresh!
raise SessionError, "No session available to refresh" if @session.nil?

# @return [Hash] The headers for session requests.
def sessions_header
Request::Headers.sessions_header
session.call
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
module ChatKit
class Session
# Represents a session response.
class Response
class BaseResponse
# @!attribute [r] id
# @return [String]
attr_accessor :id
Expand Down
Loading
Loading