Skip to content

Commit

Permalink
Support for explicit agent dispatches (#66)
Browse files Browse the repository at this point in the history
* Support for explicit agent dispatches

* lockfile
  • Loading branch information
davidzhao authored Nov 11, 2024
1 parent a6f76c8 commit 949f9f5
Show file tree
Hide file tree
Showing 21 changed files with 197 additions and 31 deletions.
5 changes: 2 additions & 3 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
livekit-server-sdk (0.7.0)
livekit-server-sdk (0.8.0)
google-protobuf (>= 3.21.0, < 4.0)
jwt (>= 2.2.3, < 3.0)
rack (>= 2.2.3)
Expand All @@ -19,10 +19,9 @@ GEM
faraday-net_http (3.1.1)
net-http
google-protobuf (3.25.4)
google-protobuf (3.25.4-x86_64-linux)
jwt (2.8.2)
base64
logger (1.6.0)
logger (1.6.1)
method_source (1.0.0)
net-http (0.4.1)
uri
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ require 'livekit'
token = LiveKit::AccessToken.new(api_key: 'yourkey', api_secret: 'yoursecret')
token.identity = 'participant-identity'
token.name = 'participant-name'
token.set_video_grant(LiveKit::VideoGrant.new(roomJoin: true, room: 'room-name'))
token.video_grant = LiveKit::VideoGrant.new(roomJoin: true, room: 'room-name')
token.attributes = { "mykey" => "myvalue" }
puts token.to_jwt
```
Expand Down
1 change: 1 addition & 0 deletions lib/livekit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@
require "livekit/egress_service_client"
require "livekit/ingress_service_client"
require "livekit/sip_service_client"
require "livekit/agent_dispatch_service_client"
20 changes: 14 additions & 6 deletions lib/livekit/access_token.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,27 +31,27 @@ def initialize(
@ttl = ttl
end

# Deprecated, use set_video_grant instead
# Deprecated, use video_grant = instead
def add_grant(video_grant)
if video_grant.is_a?(Hash)
video_grant = VideoGrant.from_hash(video_grant)
end
self.set_video_grant(video_grant)
self.video_grant = video_grant
end

def set_video_grant(video_grant)
def video_grant=(video_grant)
@grants.video = video_grant
end

# Deprecated, use set_sip_grant instead
# Deprecated, use sip_grant = instead
def add_sip_grant(sip_grant)
if sip_grant.is_a?(Hash)
sip_grant = SIPGrant.from_hash(sip_grant)
end
self.set_sip_grant(sip_grant)
self.sip_grant = sip_grant
end

def set_sip_grant(sip_grant)
def sip_grant=(sip_grant)
@grants.sip = sip_grant
end

Expand All @@ -75,6 +75,14 @@ def sha256=(sha_string)
@grants.sha256 = sha_string
end

def room_config=(room_config)
@grants.room_config = room_config
end

def room_preset=(room_preset)
@grants.room_preset = room_preset
end

def to_jwt
if @grants.video.nil? && @grants.sip.nil?
raise ArgumentError, "VideoGrant or SIPGrant is required"
Expand Down
95 changes: 95 additions & 0 deletions lib/livekit/agent_dispatch_service_client.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
require "livekit/proto/livekit_agent_dispatch_twirp"
require "livekit/auth_mixin"
require 'livekit/utils'

module LiveKit
# Client for LiveKit's Agent Dispatch Service, which manages agent assignments to rooms
# This client handles creating, deleting, and retrieving agent dispatches
class AgentDispatchServiceClient < Twirp::Client
client_for Proto::SIPService
include AuthMixin
attr_accessor :api_key, :api_secret

def initialize(base_url, api_key: nil, api_secret: nil)
super(File.join(Utils.to_http_url(base_url), "/twirp"))
@api_key = api_key
@api_secret = api_secret
end

# Creates a new agent dispatch for a named agent
# @param room_name [String] The room to dispatch the agent to
# @param agent_name [String] The name of the agent to dispatch
# @param metadata [String, nil] Optional metadata to include with the dispatch
# @return [LiveKit::Proto::AgentDispatch] Dispatch that was just created
def create_dispatch(
# room to dispatch agent to
room_name,
# agent to dispatch
agent_name,
# optional, metadata to send along with the job
metadata: nil
)
request = Proto::CreateAgentDispatchRequest.new(
room: room_name,
agent_name: agent_name,
metadata: metadata,
)
self.rpc(
:CreateDispatch,
request,
headers: auth_header(VideoGrant.new(roomAdmin: true, room: room_name)),
)
end

# Deletes an existing agent dispatch
# @param dispatch_id [String] The ID of the dispatch to delete
# @param room_name [String] The room name associated with the dispatch
# @return [LiveKit::Proto::AgentDispatch] AgentDispatch record that was deleted
def delete_dispatch(dispatch_id, room_name)
request = Proto::DeleteAgentDispatchRequest.new(
dispatch_id: dispatch_id,
room: room_name,
)
self.rpc(
:DeleteDispatch,
request,
headers: auth_header(VideoGrant.new(roomAdmin: true, room: room_name)),
)
end

# Retrieves a specific agent dispatch by ID
# @param dispatch_id [String] The ID of the dispatch to retrieve
# @param room_name [String] The room name associated with the dispatch
# @return [LiveKit::Proto::AgentDispatch, nil] The agent dispatch if found, nil otherwise
def get_dispatch(dispatch_id, room_name)
request = Proto::ListAgentDispatchRequest.new(
dispatch_id: dispatch_id,
room: room_name,
)
res = self.rpc(
:ListDispatch,
request,
headers: auth_header(VideoGrant.new(roomAdmin: true, room: room_name)),
)
if res.agent_dispatches.size > 0
return res.agent_dispatches[0]
end
nil
end

# Lists all agent dispatches for a specific room
# @param room_name [String] The room name to list dispatches for
# @return [Array<LiveKit::Proto::AgentDispatch>] Array of agent dispatches
def list_dispatch(room_name)
request = Proto::ListAgentDispatchRequest.new(
room: room_name,
)
res = self.rpc(
:ListDispatch,
request,
headers: auth_header(VideoGrant.new(roomAdmin: true, room: room_name)),
)
res.agent_dispatches
end
end
end
4 changes: 2 additions & 2 deletions lib/livekit/auth_mixin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ def auth_header(
headers = {}
t = ::LiveKit::AccessToken.new(api_key: @api_key, api_secret: @api_secret)
if video_grant != nil
t.set_video_grant(video_grant)
t.video_grant = video_grant
end
if sip_grant != nil
t.set_sip_grant(sip_grant)
t.sip_grant = sip_grant
end
headers["Authorization"] = "Bearer #{t.to_jwt}"
headers["User-Agent"] = "LiveKit Ruby SDK"
Expand Down
15 changes: 14 additions & 1 deletion lib/livekit/grants.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

module LiveKit
class ClaimGrant
attr_accessor :identity, :name, :metadata, :sha256, :video, :sip, :attributes
attr_accessor :identity, :name, :metadata, :sha256, :video, :sip, :attributes, :room_preset, :room_config

def self.from_hash(hash)
return nil if hash.nil?
Expand All @@ -15,6 +15,11 @@ def self.from_hash(hash)
claim_grant.sha256 = hash["sha256"]
claim_grant.video = VideoGrant.from_hash(hash["video"])
claim_grant.sip = SIPGrant.from_hash(hash["sip"])
claim_grant.room_preset = hash["roomPreset"]
if hash["roomConfig"]
# re-hydrate from JSON to ensure it can parse camelCase fields correctly
claim_grant.room_config = Proto::RoomConfiguration.decode_json(hash["roomConfig"].to_json)
end
return claim_grant
end

Expand All @@ -26,6 +31,8 @@ def initialize
@video = nil
@sip = nil
@attributes = nil
@room_preset = nil
@room_config = nil
end

def to_hash
Expand All @@ -41,6 +48,12 @@ def to_hash
if @sip
val[:sip] = @sip.to_hash
end
if @room_preset
val[:roomPreset] = @room_preset
end
if @room_config
val[:roomConfig] = JSON.parse(@room_config.to_json)
end
return val
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/livekit/proto/livekit_agent_dispatch_twirp.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Code generated by protoc-gen-twirp_ruby 1.11.0, DO NOT EDIT.
# Code generated by protoc-gen-twirp_ruby 1.10.0, DO NOT EDIT.
require 'twirp'
require_relative 'livekit_agent_dispatch_pb.rb'

Expand Down
2 changes: 1 addition & 1 deletion lib/livekit/proto/livekit_agent_twirp.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Code generated by protoc-gen-twirp_ruby 1.11.0, DO NOT EDIT.
# Code generated by protoc-gen-twirp_ruby 1.10.0, DO NOT EDIT.
require 'twirp'
require_relative 'livekit_agent_pb.rb'

Expand Down
2 changes: 1 addition & 1 deletion lib/livekit/proto/livekit_egress_twirp.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Code generated by protoc-gen-twirp_ruby 1.11.0, DO NOT EDIT.
# Code generated by protoc-gen-twirp_ruby 1.10.0, DO NOT EDIT.
require 'twirp'
require_relative 'livekit_egress_pb.rb'

Expand Down
2 changes: 1 addition & 1 deletion lib/livekit/proto/livekit_ingress_twirp.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Code generated by protoc-gen-twirp_ruby 1.11.0, DO NOT EDIT.
# Code generated by protoc-gen-twirp_ruby 1.10.0, DO NOT EDIT.
require 'twirp'
require_relative 'livekit_ingress_pb.rb'

Expand Down
2 changes: 1 addition & 1 deletion lib/livekit/proto/livekit_metrics_twirp.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Code generated by protoc-gen-twirp_ruby 1.11.0, DO NOT EDIT.
# Code generated by protoc-gen-twirp_ruby 1.10.0, DO NOT EDIT.
require 'twirp'
require_relative 'livekit_metrics_pb.rb'

Expand Down
2 changes: 1 addition & 1 deletion lib/livekit/proto/livekit_models_twirp.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Code generated by protoc-gen-twirp_ruby 1.11.0, DO NOT EDIT.
# Code generated by protoc-gen-twirp_ruby 1.10.0, DO NOT EDIT.
require 'twirp'
require_relative 'livekit_models_pb.rb'

Expand Down
2 changes: 1 addition & 1 deletion lib/livekit/proto/livekit_room_twirp.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Code generated by protoc-gen-twirp_ruby 1.11.0, DO NOT EDIT.
# Code generated by protoc-gen-twirp_ruby 1.10.0, DO NOT EDIT.
require 'twirp'
require_relative 'livekit_room_pb.rb'

Expand Down
2 changes: 1 addition & 1 deletion lib/livekit/proto/livekit_sip_pb.rb

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion lib/livekit/proto/livekit_sip_twirp.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Code generated by protoc-gen-twirp_ruby 1.11.0, DO NOT EDIT.
# Code generated by protoc-gen-twirp_ruby 1.10.0, DO NOT EDIT.
require 'twirp'
require_relative 'livekit_sip_pb.rb'

Expand Down
2 changes: 1 addition & 1 deletion lib/livekit/proto/livekit_webhook_twirp.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Code generated by protoc-gen-twirp_ruby 1.11.0, DO NOT EDIT.
# Code generated by protoc-gen-twirp_ruby 1.10.0, DO NOT EDIT.
require 'twirp'
require_relative 'livekit_webhook_pb.rb'

Expand Down
2 changes: 1 addition & 1 deletion lib/livekit/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module LiveKit
VERSION = "0.7.1"
VERSION = "0.8.0"
end
54 changes: 52 additions & 2 deletions spec/livekit/access_token_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
RSpec.describe LiveKit::AccessToken do
it "generates a valid JWT with defaults" do
token = described_class.new(api_key: TEST_KEY, api_secret: TEST_SECRET)
token.set_video_grant LiveKit::VideoGrant.new
token.video_grant = LiveKit::VideoGrant.new
jwt = token.to_jwt
decoded = JWT.decode(jwt, TEST_SECRET, true, algorithm: "HS256")
expect(decoded.first["iss"]).to eq(TEST_KEY)
Expand All @@ -24,7 +24,7 @@
token = described_class.new(api_key: TEST_KEY, api_secret: TEST_SECRET,
identity: "test_identity", ttl: 60)
token.name = "myname"
token.set_video_grant(LiveKit::VideoGrant.new(roomJoin: true, room: "myroom", canPublish: false))
token.video_grant = LiveKit::VideoGrant.new(roomJoin: true, room: "myroom", canPublish: false)
jwt = token.to_jwt

decoded = JWT.decode(jwt, TEST_SECRET, true, algorithm: "HS256")
Expand All @@ -34,4 +34,54 @@
expect(video_grant["room"]).to eq("myroom")
expect(video_grant["canPublish"]).to eq(false)
end

it "handles agent dispatch" do
token = described_class.new(api_key: TEST_KEY, api_secret: TEST_SECRET)
token.video_grant = LiveKit::VideoGrant.new(roomJoin: true, room: "myroom", canPublish: false)
token.room_config = LiveKit::Proto::RoomConfiguration.new(
max_participants: 10,
agents: [LiveKit::Proto::RoomAgentDispatch.new(
agent_name: "test-agent",
metadata: "test-metadata",
)]
)
jwt = token.to_jwt

grant = LiveKit::TokenVerifier.new(api_key: TEST_KEY, api_secret: TEST_SECRET).verify(jwt)
expect(grant.video.room).to eq("myroom")
expect(grant.room_config.agents[0].agent_name).to eq("test-agent")
expect(grant.room_config.agents[0].metadata).to eq("test-metadata")
end
end

RSpec.describe LiveKit::ClaimGrant do
it "can be converted to a hash and back" do
claim_grant = described_class.new
claim_grant.identity = "test_identity"
claim_grant.name = "myname"
claim_grant.video = LiveKit::VideoGrant.new(roomJoin: true, room: "myroom", canPublish: false)
claim_grant.room_config = LiveKit::Proto::RoomConfiguration.new(
max_participants: 10,
agents: [LiveKit::Proto::RoomAgentDispatch.new(
agent_name: "test-agent",
metadata: "test-metadata",
)]
)
hash = claim_grant.to_hash
expect(hash[:name]).to eq("myname")
agents = hash[:roomConfig]["agents"]
expect(agents.size).to eq(1)
expect(agents[0]["agentName"]).to eq("test-agent")
expect(agents[0]["metadata"]).to eq("test-metadata")

serialized = hash.to_json
restored = JSON.parse(serialized)

grant_parsed = LiveKit::ClaimGrant.from_hash(restored)
expect(grant_parsed.name).to eq("myname")
expect(grant_parsed.video.room).to eq("myroom")
expect(grant_parsed.room_config.max_participants).to eq(10)
expect(grant_parsed.room_config.agents[0].agent_name).to eq("test-agent")
expect(grant_parsed.room_config.agents[0].metadata).to eq("test-metadata")
end
end
Loading

0 comments on commit 949f9f5

Please sign in to comment.