chat_sdk 0.1.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.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/lib/chat_sdk/adapter/base.rb +99 -0
  3. data/lib/chat_sdk/adapter/capabilities.rb +34 -0
  4. data/lib/chat_sdk/author.rb +28 -0
  5. data/lib/chat_sdk/cards/actions_context.rb +32 -0
  6. data/lib/chat_sdk/cards/builder.rb +52 -0
  7. data/lib/chat_sdk/cards/fields_context.rb +17 -0
  8. data/lib/chat_sdk/cards/node.rb +45 -0
  9. data/lib/chat_sdk/cards/renderer.rb +49 -0
  10. data/lib/chat_sdk/cards/select_context.rb +19 -0
  11. data/lib/chat_sdk/channel.rb +31 -0
  12. data/lib/chat_sdk/chat.rb +93 -0
  13. data/lib/chat_sdk/config.rb +41 -0
  14. data/lib/chat_sdk/dispatcher.rb +118 -0
  15. data/lib/chat_sdk/errors.rb +38 -0
  16. data/lib/chat_sdk/event_registry.rb +41 -0
  17. data/lib/chat_sdk/events/action.rb +19 -0
  18. data/lib/chat_sdk/events/base.rb +17 -0
  19. data/lib/chat_sdk/events/direct_message.rb +16 -0
  20. data/lib/chat_sdk/events/mention.rb +16 -0
  21. data/lib/chat_sdk/events/reaction.rb +27 -0
  22. data/lib/chat_sdk/events/slash_command.rb +18 -0
  23. data/lib/chat_sdk/events/subscribed_message.rb +16 -0
  24. data/lib/chat_sdk/log.rb +23 -0
  25. data/lib/chat_sdk/message.rb +30 -0
  26. data/lib/chat_sdk/modals/builder.rb +40 -0
  27. data/lib/chat_sdk/postable_message.rb +28 -0
  28. data/lib/chat_sdk/state/base.rb +47 -0
  29. data/lib/chat_sdk/state/memory.rb +105 -0
  30. data/lib/chat_sdk/streaming/stream.rb +71 -0
  31. data/lib/chat_sdk/testing/adapter_contract.rb +81 -0
  32. data/lib/chat_sdk/testing/fake_adapter.rb +195 -0
  33. data/lib/chat_sdk/testing/helpers.rb +31 -0
  34. data/lib/chat_sdk/testing/recorded_call.rb +22 -0
  35. data/lib/chat_sdk/testing/state_contract.rb +81 -0
  36. data/lib/chat_sdk/testing.rb +13 -0
  37. data/lib/chat_sdk/thread.rb +104 -0
  38. data/lib/chat_sdk/version.rb +5 -0
  39. data/lib/chat_sdk/webhook/endpoint.rb +35 -0
  40. data/lib/chat_sdk/webhook/router.rb +24 -0
  41. data/lib/chat_sdk.rb +26 -0
  42. metadata +97 -0
@@ -0,0 +1,195 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ChatSDK
4
+ module Testing
5
+ class FakeAdapter < Adapter::Base
6
+ capabilities :edit_messages, :delete_messages, :ephemeral_messages,
7
+ :file_uploads, :reactions, :modals, :typing_indicator,
8
+ :streaming_edit, :threads, :direct_messages, :message_history
9
+
10
+ attr_reader :posted_messages, :edited_messages, :deleted_messages,
11
+ :ephemeral_messages_sent, :reactions_added, :reactions_removed,
12
+ :files_uploaded, :modals_opened, :typing_started, :dm_channels
13
+
14
+ def initialize
15
+ reset!
16
+ end
17
+
18
+ def name
19
+ :test
20
+ end
21
+
22
+ def client
23
+ self
24
+ end
25
+
26
+ def verify_request!(rack_request)
27
+ true
28
+ end
29
+
30
+ def ack_response(rack_request)
31
+ nil
32
+ end
33
+
34
+ def parse_events(rack_request)
35
+ []
36
+ end
37
+
38
+ def post_message(channel_id:, message:, thread_id: nil)
39
+ record = RecordedCall.new(:post_message, channel_id: channel_id, message: message, thread_id: thread_id)
40
+ @posted_messages << record
41
+ Message.new(
42
+ id: "msg_#{@posted_messages.size}",
43
+ text: message.is_a?(PostableMessage) ? message.text : message.to_s,
44
+ author: Author.new(id: "bot", name: "test-bot", platform: :test, bot: true),
45
+ thread_id: thread_id,
46
+ channel_id: channel_id,
47
+ platform: :test
48
+ )
49
+ end
50
+
51
+ def edit_message(channel_id:, message_id:, message:)
52
+ record = RecordedCall.new(:edit_message, channel_id: channel_id, message_id: message_id, message: message)
53
+ @edited_messages << record
54
+ record
55
+ end
56
+
57
+ def delete_message(channel_id:, message_id:)
58
+ record = RecordedCall.new(:delete_message, channel_id: channel_id, message_id: message_id)
59
+ @deleted_messages << record
60
+ record
61
+ end
62
+
63
+ def post_ephemeral(channel_id:, user_id:, message:, thread_id: nil)
64
+ record = RecordedCall.new(:post_ephemeral, channel_id: channel_id, user_id: user_id, message: message, thread_id: thread_id)
65
+ @ephemeral_messages_sent << record
66
+ record
67
+ end
68
+
69
+ def upload_file(channel_id:, io:, filename:, thread_id: nil, comment: nil)
70
+ record = RecordedCall.new(:upload_file, channel_id: channel_id, filename: filename, thread_id: thread_id, comment: comment)
71
+ @files_uploaded << record
72
+ record
73
+ end
74
+
75
+ def add_reaction(channel_id:, message_id:, emoji:)
76
+ record = RecordedCall.new(:add_reaction, channel_id: channel_id, message_id: message_id, emoji: emoji)
77
+ @reactions_added << record
78
+ record
79
+ end
80
+
81
+ def remove_reaction(channel_id:, message_id:, emoji:)
82
+ record = RecordedCall.new(:remove_reaction, channel_id: channel_id, message_id: message_id, emoji: emoji)
83
+ @reactions_removed << record
84
+ record
85
+ end
86
+
87
+ def open_dm(user_id)
88
+ channel_id = "dm_#{user_id}"
89
+ @dm_channels << RecordedCall.new(:open_dm, user_id: user_id, channel_id: channel_id)
90
+ channel_id
91
+ end
92
+
93
+ def fetch_messages(channel_id:, thread_id: nil, cursor: nil, limit: 50)
94
+ [[], nil]
95
+ end
96
+
97
+ def open_modal(trigger_id:, modal:)
98
+ record = RecordedCall.new(:open_modal, trigger_id: trigger_id, modal: modal)
99
+ @modals_opened << record
100
+ record
101
+ end
102
+
103
+ def start_typing(channel_id:, thread_id: nil)
104
+ record = RecordedCall.new(:start_typing, channel_id: channel_id, thread_id: thread_id)
105
+ @typing_started << record
106
+ record
107
+ end
108
+
109
+ def mention(user_id)
110
+ "<@#{user_id}>"
111
+ end
112
+
113
+ def render(postable_message)
114
+ Cards::Renderer.new.render(postable_message.card)
115
+ end
116
+
117
+ # Simulation helpers
118
+ def simulate_mention(chat, text:, user_id: "U123", user_name: "testuser", channel_id: "C123", thread_id: nil)
119
+ thread_id ||= "T#{rand(1000..9999)}"
120
+ author = Author.new(id: user_id, name: user_name, platform: :test)
121
+ message = Message.new(
122
+ id: "evt_#{rand(10000..99999)}",
123
+ text: text,
124
+ author: author,
125
+ thread_id: thread_id,
126
+ channel_id: channel_id,
127
+ platform: :test
128
+ )
129
+ event = Events::Mention.new(
130
+ message: message,
131
+ thread_id: thread_id,
132
+ channel_id: channel_id,
133
+ platform: :test,
134
+ adapter_name: :test
135
+ )
136
+ chat.dispatch(event, adapter_name: :test)
137
+ end
138
+
139
+ def simulate_action(chat, action_id:, value: nil, user_id: "U123", channel_id: "C123", thread_id: "T123", trigger_id: nil)
140
+ user = Author.new(id: user_id, name: "testuser", platform: :test)
141
+ event = Events::Action.new(
142
+ action_id: action_id,
143
+ value: value,
144
+ user: user,
145
+ thread_id: thread_id,
146
+ channel_id: channel_id,
147
+ trigger_id: trigger_id,
148
+ platform: :test,
149
+ adapter_name: :test
150
+ )
151
+ chat.dispatch(event, adapter_name: :test)
152
+ end
153
+
154
+ def simulate_reaction(chat, emoji:, user_id: "U123", message_id: "M123", channel_id: "C123", thread_id: "T123", added: true)
155
+ event = Events::Reaction.new(
156
+ emoji: emoji,
157
+ user_id: user_id,
158
+ message_id: message_id,
159
+ thread_id: thread_id,
160
+ channel_id: channel_id,
161
+ added: added,
162
+ platform: :test,
163
+ adapter_name: :test
164
+ )
165
+ chat.dispatch(event, adapter_name: :test)
166
+ end
167
+
168
+ def simulate_slash_command(chat, command:, text: "", user_id: "U123", channel_id: "C123", trigger_id: nil)
169
+ event = Events::SlashCommand.new(
170
+ command: command,
171
+ text: text,
172
+ user_id: user_id,
173
+ channel_id: channel_id,
174
+ trigger_id: trigger_id,
175
+ platform: :test,
176
+ adapter_name: :test
177
+ )
178
+ chat.dispatch(event, adapter_name: :test)
179
+ end
180
+
181
+ def reset!
182
+ @posted_messages = []
183
+ @edited_messages = []
184
+ @deleted_messages = []
185
+ @ephemeral_messages_sent = []
186
+ @reactions_added = []
187
+ @reactions_removed = []
188
+ @files_uploaded = []
189
+ @modals_opened = []
190
+ @typing_started = []
191
+ @dm_channels = []
192
+ end
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ChatSDK
4
+ module Testing
5
+ module Helpers
6
+ def build_bot(**options)
7
+ ChatSDK::Testing.build_bot(**options)
8
+ end
9
+
10
+ def fake_adapter
11
+ @fake_adapter ||= FakeAdapter.new
12
+ end
13
+
14
+ def build_message(text: "hello", user_id: "U123", user_name: "testuser", channel_id: "C123", thread_id: "T123")
15
+ author = Author.new(id: user_id, name: user_name, platform: :test)
16
+ Message.new(
17
+ id: "msg_#{rand(10000..99999)}",
18
+ text: text,
19
+ author: author,
20
+ thread_id: thread_id,
21
+ channel_id: channel_id,
22
+ platform: :test
23
+ )
24
+ end
25
+
26
+ def build_card(title: "Test Card", &block)
27
+ ChatSDK.card(title: title, &block)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ChatSDK
4
+ module Testing
5
+ class RecordedCall
6
+ attr_reader :method_name, :arguments
7
+
8
+ def initialize(method_name, **arguments)
9
+ @method_name = method_name
10
+ @arguments = arguments
11
+ end
12
+
13
+ def [](key)
14
+ @arguments[key]
15
+ end
16
+
17
+ def to_h
18
+ {method: @method_name}.merge(@arguments)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ChatSDK
4
+ module Testing
5
+ module StateContract
6
+ RSpec.shared_examples "a chat_sdk state adapter" do
7
+ describe "subscriptions" do
8
+ it "subscribes and checks" do
9
+ subject.subscribe("thread_1")
10
+ expect(subject.subscribed?("thread_1")).to be true
11
+ end
12
+
13
+ it "unsubscribes" do
14
+ subject.subscribe("thread_1")
15
+ subject.unsubscribe("thread_1")
16
+ expect(subject.subscribed?("thread_1")).to be false
17
+ end
18
+
19
+ it "returns false for unsubscribed threads" do
20
+ expect(subject.subscribed?("nonexistent")).to be false
21
+ end
22
+ end
23
+
24
+ describe "locks" do
25
+ it "acquires a lock" do
26
+ expect(subject.acquire_lock("key1", owner: "a", ttl: 10)).to be true
27
+ end
28
+
29
+ it "prevents double acquisition" do
30
+ subject.acquire_lock("key1", owner: "a", ttl: 10)
31
+ expect(subject.acquire_lock("key1", owner: "b", ttl: 10)).to be false
32
+ end
33
+
34
+ it "releases a lock by owner" do
35
+ subject.acquire_lock("key1", owner: "a", ttl: 10)
36
+ expect(subject.release_lock("key1", owner: "a")).to be true
37
+ expect(subject.acquire_lock("key1", owner: "b", ttl: 10)).to be true
38
+ end
39
+
40
+ it "does not release lock with wrong owner" do
41
+ subject.acquire_lock("key1", owner: "a", ttl: 10)
42
+ expect(subject.release_lock("key1", owner: "b")).to be false
43
+ end
44
+
45
+ it "force acquires a lock" do
46
+ subject.acquire_lock("key1", owner: "a", ttl: 10)
47
+ expect(subject.force_lock("key1", owner: "b", ttl: 10)).to be true
48
+ end
49
+ end
50
+
51
+ describe "key-value store" do
52
+ it "gets and sets values" do
53
+ subject.set("k1", {"count" => 1})
54
+ expect(subject.get("k1")).to eq({"count" => 1})
55
+ end
56
+
57
+ it "returns nil for missing keys" do
58
+ expect(subject.get("missing")).to be_nil
59
+ end
60
+
61
+ it "deletes keys" do
62
+ subject.set("k1", "v1")
63
+ subject.delete("k1")
64
+ expect(subject.get("k1")).to be_nil
65
+ end
66
+
67
+ it "set_if_absent succeeds when key absent" do
68
+ expect(subject.set_if_absent("k1", "v1")).to be true
69
+ expect(subject.get("k1")).to eq("v1")
70
+ end
71
+
72
+ it "set_if_absent fails when key present" do
73
+ subject.set("k1", "v1")
74
+ expect(subject.set_if_absent("k1", "v2")).to be false
75
+ expect(subject.get("k1")).to eq("v1")
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ChatSDK
4
+ module Testing
5
+ class << self
6
+ def build_bot(adapters: nil, state: nil, **options)
7
+ adapters ||= {test: FakeAdapter.new}
8
+ state ||= State::Memory.new
9
+ Chat.new(user_name: "test-bot", adapters: adapters, state: state, **options)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ChatSDK
4
+ class Thread
5
+ attr_reader :id, :channel_id, :adapter, :chat
6
+
7
+ def initialize(id:, channel_id:, adapter:, chat:)
8
+ @id = id
9
+ @channel_id = channel_id
10
+ @adapter = adapter
11
+ @chat = chat
12
+ end
13
+
14
+ def subscribe
15
+ chat.state.subscribe(state_key)
16
+ end
17
+
18
+ def unsubscribe
19
+ chat.state.unsubscribe(state_key)
20
+ end
21
+
22
+ def subscribed?
23
+ chat.state.subscribed?(state_key)
24
+ end
25
+
26
+ def post(content)
27
+ message = PostableMessage.from(content)
28
+ adapter.post_message(channel_id: channel_id, message: message, thread_id: id)
29
+ end
30
+
31
+ def post_ephemeral(content, user_id:)
32
+ message = PostableMessage.from(content)
33
+ adapter.post_ephemeral(channel_id: channel_id, user_id: user_id, message: message, thread_id: id)
34
+ end
35
+
36
+ def post_stream(placeholder: nil, &block)
37
+ stream = Streaming::Stream.new(
38
+ adapter: adapter,
39
+ channel_id: channel_id,
40
+ thread_id: id,
41
+ placeholder: placeholder,
42
+ update_interval: chat.config.streaming_update_interval
43
+ )
44
+ stream.run(&block)
45
+ end
46
+
47
+ def edit(message_id, content)
48
+ message = PostableMessage.from(content)
49
+ adapter.edit_message(channel_id: channel_id, message_id: message_id, message: message)
50
+ end
51
+
52
+ def delete(message_id)
53
+ adapter.delete_message(channel_id: channel_id, message_id: message_id)
54
+ end
55
+
56
+ def react(message_id, emoji)
57
+ adapter.add_reaction(channel_id: channel_id, message_id: message_id, emoji: emoji)
58
+ end
59
+
60
+ def unreact(message_id, emoji)
61
+ adapter.remove_reaction(channel_id: channel_id, message_id: message_id, emoji: emoji)
62
+ end
63
+
64
+ def upload(io:, filename:, comment: nil)
65
+ adapter.upload_file(channel_id: channel_id, io: io, filename: filename, thread_id: id, comment: comment)
66
+ end
67
+
68
+ def messages(cursor: nil, limit: 50)
69
+ adapter.fetch_messages(channel_id: channel_id, thread_id: id, cursor: cursor, limit: limit)
70
+ end
71
+
72
+ def state
73
+ chat.state.get(state_key(:state))
74
+ end
75
+
76
+ def set_state(value)
77
+ chat.state.set(state_key(:state), value, ttl: 30 * 24 * 3600)
78
+ end
79
+
80
+ def mention_user(user_id)
81
+ adapter.mention(user_id)
82
+ end
83
+
84
+ def open_modal(trigger_id:, modal:)
85
+ adapter.open_modal(trigger_id: trigger_id, modal: modal)
86
+ end
87
+
88
+ def ==(other)
89
+ other.is_a?(ChatSDK::Thread) && id == other.id && channel_id == other.channel_id
90
+ end
91
+ alias_method :eql?, :==
92
+
93
+ def hash
94
+ [id, channel_id].hash
95
+ end
96
+
97
+ private
98
+
99
+ def state_key(suffix = nil)
100
+ base = "#{adapter.name}:#{channel_id}:#{id}"
101
+ suffix ? "#{base}:#{suffix}" : base
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ChatSDK
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ChatSDK
4
+ module Webhook
5
+ class Endpoint
6
+ attr_reader :chat, :adapter, :adapter_name
7
+
8
+ def initialize(chat:, adapter:, adapter_name:)
9
+ @chat = chat
10
+ @adapter = adapter
11
+ @adapter_name = adapter_name
12
+ end
13
+
14
+ def call(env)
15
+ request = Rack::Request.new(env) if defined?(Rack)
16
+
17
+ adapter.verify_request!(request || env)
18
+
19
+ ack = adapter.ack_response(request || env)
20
+ return ack if ack
21
+
22
+ events = adapter.parse_events(request || env)
23
+ events.each { |event| chat.dispatch(event, adapter_name: adapter_name) }
24
+
25
+ [200, {"content-type" => "text/plain"}, [""]]
26
+ rescue ChatSDK::SignatureVerificationError => e
27
+ ChatSDK::Log.warn("Signature verification failed: #{e.message}")
28
+ [401, {"content-type" => "text/plain"}, ["Unauthorized"]]
29
+ rescue => e
30
+ ChatSDK::Log.error("Webhook error: #{e.message}")
31
+ [200, {"content-type" => "text/plain"}, [""]]
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ChatSDK
4
+ module Webhook
5
+ class Router
6
+ def initialize(chat, adapters)
7
+ @chat = chat
8
+ @adapters = adapters
9
+ end
10
+
11
+ def call(env)
12
+ path = env["PATH_INFO"].to_s.split("/").last&.to_sym
13
+ adapter = @adapters[path]
14
+
15
+ unless adapter
16
+ return [404, {"content-type" => "text/plain"}, ["Unknown adapter: #{path}"]]
17
+ end
18
+
19
+ endpoint = Endpoint.new(chat: @chat, adapter: adapter, adapter_name: path)
20
+ endpoint.call(env)
21
+ end
22
+ end
23
+ end
24
+ end
data/lib/chat_sdk.rb ADDED
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "zeitwerk"
4
+ require_relative "chat_sdk/version"
5
+ require_relative "chat_sdk/errors"
6
+
7
+ module ChatSDK
8
+ class << self
9
+ def loader
10
+ @loader ||= begin
11
+ loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
12
+ loader.inflector.inflect("chat_sdk" => "ChatSDK")
13
+ loader.ignore("#{__dir__}/chat_sdk/version.rb")
14
+ loader.ignore("#{__dir__}/chat_sdk/errors.rb")
15
+ loader.setup
16
+ loader
17
+ end
18
+ end
19
+
20
+ def card(title: nil, subtitle: nil, &block)
21
+ Cards::Builder.new(title: title, subtitle: subtitle, &block).build
22
+ end
23
+ end
24
+ end
25
+
26
+ ChatSDK.loader
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: chat_sdk
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Rootly
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: zeitwerk
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '2.6'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '2.6'
26
+ description: Platform-agnostic chat bot framework with normalized events, cards DSL,
27
+ streaming, and pluggable adapters
28
+ email:
29
+ - eng@rootly.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - lib/chat_sdk.rb
35
+ - lib/chat_sdk/adapter/base.rb
36
+ - lib/chat_sdk/adapter/capabilities.rb
37
+ - lib/chat_sdk/author.rb
38
+ - lib/chat_sdk/cards/actions_context.rb
39
+ - lib/chat_sdk/cards/builder.rb
40
+ - lib/chat_sdk/cards/fields_context.rb
41
+ - lib/chat_sdk/cards/node.rb
42
+ - lib/chat_sdk/cards/renderer.rb
43
+ - lib/chat_sdk/cards/select_context.rb
44
+ - lib/chat_sdk/channel.rb
45
+ - lib/chat_sdk/chat.rb
46
+ - lib/chat_sdk/config.rb
47
+ - lib/chat_sdk/dispatcher.rb
48
+ - lib/chat_sdk/errors.rb
49
+ - lib/chat_sdk/event_registry.rb
50
+ - lib/chat_sdk/events/action.rb
51
+ - lib/chat_sdk/events/base.rb
52
+ - lib/chat_sdk/events/direct_message.rb
53
+ - lib/chat_sdk/events/mention.rb
54
+ - lib/chat_sdk/events/reaction.rb
55
+ - lib/chat_sdk/events/slash_command.rb
56
+ - lib/chat_sdk/events/subscribed_message.rb
57
+ - lib/chat_sdk/log.rb
58
+ - lib/chat_sdk/message.rb
59
+ - lib/chat_sdk/modals/builder.rb
60
+ - lib/chat_sdk/postable_message.rb
61
+ - lib/chat_sdk/state/base.rb
62
+ - lib/chat_sdk/state/memory.rb
63
+ - lib/chat_sdk/streaming/stream.rb
64
+ - lib/chat_sdk/testing.rb
65
+ - lib/chat_sdk/testing/adapter_contract.rb
66
+ - lib/chat_sdk/testing/fake_adapter.rb
67
+ - lib/chat_sdk/testing/helpers.rb
68
+ - lib/chat_sdk/testing/recorded_call.rb
69
+ - lib/chat_sdk/testing/state_contract.rb
70
+ - lib/chat_sdk/thread.rb
71
+ - lib/chat_sdk/version.rb
72
+ - lib/chat_sdk/webhook/endpoint.rb
73
+ - lib/chat_sdk/webhook/router.rb
74
+ homepage: https://github.com/rootlyhq/rootly-chat-sdk
75
+ licenses:
76
+ - MIT
77
+ metadata:
78
+ rubygems_mfa_required: 'true'
79
+ rdoc_options: []
80
+ require_paths:
81
+ - lib
82
+ required_ruby_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: 3.2.0
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ requirements: []
93
+ rubygems_version: 4.0.16
94
+ specification_version: 4
95
+ summary: Unified Ruby SDK for building chat bots across Slack, Teams, Google Chat,
96
+ and more
97
+ test_files: []