copilotkit-runtime 0.1.0.rc.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5c0fd5d40fa6a10bd0d00aad92089643f79ac5f2ac6e33cb953f3c2dc5269899
4
+ data.tar.gz: f5389ea3309785c9b5ea7afc57293a797499e5ded6a920b61d6500df2723081c
5
+ SHA512:
6
+ metadata.gz: ed8d2881b8cf7eb6be2cf715eecc20410229ed9cb5fbe1c936cabf744bb3c4c7f51efc32aec00619e3cc3403cfedcac15d95dba4540f5b7da79f8566b8bf9aa2
7
+ data.tar.gz: dfc7cc16b9b61d4bfec08fdb1812239130e02e9b698ba295b0c915084b22b98fc63d05e8c690379c07043109668f4cc1fdd2d76ec1bc4e462af73a3d07398507
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) Atai Barkai
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,445 @@
1
+ # CopilotKit Intelligence Runtime for Ruby
2
+
3
+ Connect a Rack-compatible Ruby application to CopilotKit Intelligence with native
4
+ Ruby agents or an AG-UI HTTP agent. Rails and Sinatra are supported through Rack;
5
+ neither framework is required. The runtime uses the Intelligence Runner.
6
+ It does not start a Node process.
7
+
8
+ ## Use Intelligence without Rack or Rails
9
+
10
+ The gem also includes a standalone SDK. `require 'copilotkit/intelligence'` does not load Runtime or Rack.
11
+ Use the SDK from a script, job, or service without mounting HTTP routes.
12
+
13
+ ```ruby
14
+ require 'copilotkit/intelligence'
15
+
16
+ intelligence = CopilotKit::Intelligence.new(api_key: ENV.fetch('CPK_INTELLIGENCE_API_KEY'))
17
+ thread = intelligence.get_or_create_thread(
18
+ thread_id: '9dcc02ea-695d-4635-8efc-649c1b94ab90', user_id: 'customer-42', agent_id: 'support',
19
+ learning_container_id: 'support-quality'
20
+ )
21
+ memories = intelligence.recall_memories(user_id: 'customer-42', query: 'support preferences', limit: 5)
22
+ puts thread.fetch('thread').fetch('id')
23
+ puts memories.fetch('memories')
24
+ ```
25
+
26
+ `learning_container_id` assigns a new thread to an existing Learning Container.
27
+ Intelligence owns the binding and rejects attempts to move a bound thread.
28
+
29
+ Thread methods include `list_threads`, `get_thread`, `create_thread`, `update_thread`, and `archive_thread`.
30
+ Read persisted data with `get_thread_messages`, `get_thread_events`, and `get_thread_state`.
31
+ `delete_thread` permanently deletes a thread and its history.
32
+
33
+ Memory methods include `list_memories`, `create_memory`, `update_memory`, `remove_memory`, and `recall_memories`.
34
+ Pass `CopilotKit::MemoryGrant.new(user: :read_write, project: :read)` as `memory_grant` to apply explicit limits.
35
+ Without a grant, Intelligence applies its policy. Each Memory call requires the bare application user ID.
36
+
37
+ `annotate` records an annotation. Reuse `client_event_id` when retrying the same annotation.
38
+ `CopilotKit::Error` contains the HTTP status but no private response body.
39
+ The default transport uses a five-second connection timeout and a 15-second read timeout.
40
+ It closes each connection after the call, does not retry requests, and does not follow redirects.
41
+
42
+ Pass the same SDK client to `CopilotKit::Runtime.new(intelligence: intelligence, identify_user: identify_user, agents: agents)` to mount Runtime routes.
43
+ The Runtime borrows the SDK. Existing `api_key:` constructors remain valid.
44
+
45
+ ## Handle thread changes
46
+
47
+ Register a block on the SDK:
48
+
49
+ ```ruby
50
+ unsubscribe = intelligence.on_thread_created { |thread| puts thread.fetch('id') }
51
+ # To stop this listener:
52
+ unsubscribe.call
53
+ ```
54
+
55
+ `on_thread_created` receives the canonical thread after creation.
56
+ `on_thread_updated` receives the thread after an update or archive.
57
+ `on_thread_deleted` receives `threadId`, `userId`, and `agentId` after deletion.
58
+ Listeners receive changes from direct SDK calls and from a Runtime that shares the SDK.
59
+
60
+ The SDK synchronizes registration across threads and calls listeners outside its mutex.
61
+ Failed requests and concurrent-create conflicts emit no success event.
62
+ A failed listener does not stop other listeners or replace a completed platform write.
63
+ The SDK writes a warning with the event and exception class, without the exception message or thread payload.
64
+
65
+ ## Read Inspector metadata
66
+
67
+ Read project display metadata from application code:
68
+
69
+ ```ruby
70
+ metadata = intelligence.get_inspector_metadata
71
+ puts metadata.dig('plan', 'label') if metadata && metadata.key?('plan')
72
+ ```
73
+
74
+ The result is a hash with `schemaVersion: 1` and optional string-keyed modules:
75
+
76
+ | Module | Fields |
77
+ | ---------- | --------------------------------------------------------------------------- |
78
+ | `identity` | `organizationName`, `projectName` |
79
+ | `plan` | `code`, `label` |
80
+ | `license` | `state`: `valid`, `none`, `expired`, or `unknown` |
81
+ | `action` | `kind`: `manage_plan`, `renew`, or `enable_intelligence`, plus a safe `url` |
82
+ | `usage` | `used`, `limit`, and optional `expiringSoonCount` |
83
+
84
+ A usage limit has kind `finite`, `unlimited`, or `unknown`. Only a finite limit has a positive `value`.
85
+ Counts preserve known zero values. An absent expiry count has no `expiringSoonCount` key.
86
+ The SDK removes unknown fields and unsafe action URLs.
87
+ Metadata describes the project. It does not grant access to a feature or resource.
88
+
89
+ The request uses the server API key and a five-second deadline, including the response body.
90
+ Deadline expiry raises `Timeout::Error`. The default transport closes the connection.
91
+ Custom transports must release per-request resources in `ensure` blocks.
92
+ A 204, 404, or unsupported schema returns `nil`.
93
+ Other provider errors raise `CopilotKit::Error` with the HTTP status. Invalid JSON uses status 502.
94
+
95
+ The Runtime exposes this data at `GET /inspector-metadata`, relative to its mount path.
96
+ Like `/info`, this display route does not require an application-user identity.
97
+ It never forwards browser credentials to Intelligence.
98
+ Responses use `Cache-Control: no-store, private`. Provider errors produce an empty 204 response and call `on_error`.
99
+ The `/info` response advertises the route through `inspectorMetadata: true`.
100
+
101
+ ## Read Runtime entitlements
102
+
103
+ Read the Runtime grant without a web server:
104
+
105
+ ```ruby
106
+ result = intelligence.get_runtime_entitlements
107
+ if result.fetch('status') == 'ready'
108
+ puts result.fetch('entitlement').fetch('active')
109
+ else
110
+ puts result.fetch('error').fetch('code')
111
+ end
112
+ ```
113
+
114
+ The result is a string-keyed hash. A ready result contains the grant, features, and limits.
115
+ Its `active` value determines Runtime access.
116
+ Other results have status `degraded`, `misconfigured`, or `unavailable` and contain a structured error.
117
+ The SDK accepts both current responses and legacy flat responses.
118
+
119
+ Concurrent threads share one lookup. Each caller receives a separate copy.
120
+ Active grants remain in the cache for 30 seconds. Other results and request errors remain for five seconds.
121
+ After expiry, the SDK requests a fresh result. A failed lookup does not return an expired grant.
122
+ The cache uses a monotonic clock, so changes to the system clock do not extend grants.
123
+
124
+ The full request deadline is 1.5 seconds, including the response body.
125
+ `CopilotKit::RuntimeEntitlementError` extends `CopilotKit::Error` with a `retryable` value.
126
+ Invalid responses use status 502 with `retryable: false`. Timeouts use status 504 with `retryable: true`.
127
+ The default transport closes the connection and excludes private response bodies from errors.
128
+ Custom transports must release per-request resources in `ensure` blocks.
129
+
130
+ The Runtime uses this SDK method and cache for `/info`.
131
+ Configuration errors produce a non-retryable `misconfigured` result.
132
+ Retryable failures produce an `unavailable` result and an `unknown` compatibility license status.
133
+
134
+ ## Install
135
+
136
+ The initial release candidate is `0.1.0.rc.1`. After publication:
137
+
138
+ ```sh
139
+ gem install copilotkit-runtime --version 0.1.0.rc.1
140
+ ```
141
+
142
+ 1. Add the gem from your checkout to your application's `Gemfile`:
143
+
144
+ ```ruby
145
+ gem 'copilotkit-runtime', path: '/path/to/CopilotKit/packages/runtime-ruby'
146
+ ```
147
+
148
+ 2. Run `bundle install`.
149
+
150
+ Ruby 2.7 or later is required. The gem installs its `websocket` dependency.
151
+ Your application supplies a Rack-compatible server. The gem does not depend on Rails.
152
+
153
+ ## Mount with Rack
154
+
155
+ `CopilotKit::Runtime` is a Rack application. Its `call(env)` method accepts the
156
+ Rack environment and returns `[status, headers, body]`. Your application owns
157
+ authentication and passes an `identify_user` callback that reads that environment.
158
+
159
+ Mount a configured runtime in your application's `config.ru`:
160
+
161
+ ```ruby
162
+ map '/copilotkit' do
163
+ run runtime
164
+ end
165
+ ```
166
+
167
+ Here, `runtime` is your configured `CopilotKit::Runtime` instance. Rack removes
168
+ the mount prefix from `PATH_INFO`, so leave the runtime's `base_path` empty.
169
+ Create one runtime per worker and close it during worker shutdown; see
170
+ [Worker lifecycle](#worker-lifecycle).
171
+
172
+ ## Mount in Rails
173
+
174
+ 1. Set `CPK_INTELLIGENCE_API_KEY` and `AG_UI_AGENT_URL` in your server environment.
175
+ 2. Add this initializer:
176
+
177
+ ```ruby
178
+ # config/initializers/copilotkit.rb
179
+ require 'copilotkit/runtime'
180
+
181
+ Rails.application.config.x.copilotkit_runtime = CopilotKit::Runtime.new(
182
+ api_key: ENV.fetch('CPK_INTELLIGENCE_API_KEY'),
183
+ agents: {
184
+ 'default' => CopilotKit::HttpAgent.new(url: ENV.fetch('AG_UI_AGENT_URL'))
185
+ },
186
+ identify_user: lambda do |env|
187
+ user = env['warden']&.user
188
+ user && { id: user.id.to_s, name: user.name.to_s }
189
+ end
190
+ )
191
+ ```
192
+
193
+ 3. Mount the runtime in your routes:
194
+
195
+ ```ruby
196
+ # config/routes.rb
197
+ Rails.application.routes.draw do
198
+ mount Rails.application.config.x.copilotkit_runtime => '/copilotkit'
199
+ end
200
+ ```
201
+
202
+ 4. Point your CopilotKit frontend at `/copilotkit`.
203
+
204
+ This example uses Devise/Warden for authentication. If you use another system,
205
+ replace `identify_user` with your application's authentication lookup.
206
+ The callback receives the real Rack environment. It returns a hash with `id`
207
+ and optional `name`. Symbol and string keys are accepted. A `nil` result denies access.
208
+
209
+ The initializer assumes each worker boots Rails without preloading.
210
+ For preloaded applications, follow [Worker lifecycle](#worker-lifecycle).
211
+
212
+ ## Write a Ruby agent
213
+
214
+ Subclass `CopilotKit::Agent` and yield AG-UI event hashes from `each_event(input)`:
215
+
216
+ ```ruby
217
+ class GreetingAgent < CopilotKit::Agent
218
+ def each_event(_input)
219
+ message_id = SecureRandom.uuid
220
+ yield('type' => 'TEXT_MESSAGE_START', 'messageId' => message_id, 'role' => 'assistant')
221
+ yield('type' => 'TEXT_MESSAGE_CONTENT', 'messageId' => message_id, 'delta' => 'Hello')
222
+ yield('type' => 'TEXT_MESSAGE_END', 'messageId' => message_id)
223
+ yield('type' => 'RUN_FINISHED')
224
+ end
225
+ end
226
+ ```
227
+
228
+ Register the instance with `agents: { 'default' => GreetingAgent.new }`.
229
+ The input contains canonical thread and run IDs and message history.
230
+ State and tools remain available from the request.
231
+ The runner adds `RUN_STARTED`, event IDs, and sequence numbers.
232
+
233
+ Each invocation must keep mutable state local. Multiple runs can share an agent
234
+ instance. Use `ensure` to release resources when a run stops.
235
+
236
+ For agents that use Active Record, wrap the event method in the Rails executor.
237
+ The executor manages connection cleanup on the runtime's agent thread:
238
+
239
+ ```ruby
240
+ class DatabaseAgent < CopilotKit::Agent
241
+ def each_event(_input)
242
+ Rails.application.executor.wrap do
243
+ value = ActiveRecord::Base.connection.select_value('SELECT 1')
244
+ message_id = SecureRandom.uuid
245
+ yield('type' => 'TEXT_MESSAGE_START', 'messageId' => message_id, 'role' => 'assistant')
246
+ yield('type' => 'TEXT_MESSAGE_CONTENT', 'messageId' => message_id, 'delta' => value.to_s)
247
+ yield('type' => 'TEXT_MESSAGE_END', 'messageId' => message_id)
248
+ yield('type' => 'RUN_FINISHED')
249
+ end
250
+ end
251
+ end
252
+ ```
253
+
254
+ Each agent must yield `RUN_FINISHED` or `RUN_ERROR`. A return without a terminal
255
+ event produces `INCOMPLETE_STREAM`, not success. The runner closes open text
256
+ and tool streams before that error.
257
+
258
+ `CopilotKit::HttpAgent.new(url:, headers: {}, description: '')` connects to an
259
+ AG-UI SSE endpoint. The server owns its headers. The adapter reads events as
260
+ they arrive and applies the same terminal-event rule.
261
+
262
+ ## Mount in Rack
263
+
264
+ The runtime implements `call(env)`. Mount it inside your authenticated Rack
265
+ application with `Rack::Builder`:
266
+
267
+ ```ruby
268
+ require 'copilotkit/runtime'
269
+ require 'rack'
270
+
271
+ runtime = CopilotKit::Runtime.new(
272
+ api_key: ENV.fetch('CPK_INTELLIGENCE_API_KEY'),
273
+ agents: { 'default' => CopilotKit::HttpAgent.new(url: ENV.fetch('AG_UI_AGENT_URL')) },
274
+ identify_user: lambda do |env|
275
+ user = env['warden']&.user
276
+ user && { id: user.id.to_s, name: user.name.to_s }
277
+ end
278
+ )
279
+
280
+ app = Rack::Builder.new do
281
+ map('/copilotkit') { run runtime }
282
+ end.to_app
283
+ ```
284
+
285
+ Pass `app` to your Rack server behind your authentication middleware.
286
+ This example expects Warden to have resolved the current user.
287
+ Rails and `Rack::Builder#map` strip the mount prefix. Leave `base_path` empty
288
+ for these mounts. For a host that preserves the prefix, set
289
+ `base_path: '/copilotkit'`.
290
+
291
+ ## Configure access and connections
292
+
293
+ Keep API keys and agent credentials on the server. Resolve user IDs from
294
+ authenticated application state, not request bodies, query strings, or arbitrary
295
+ HTTP headers. Intelligence checks resource ownership using that identity.
296
+
297
+ Without `memory_access`, Intelligence applies its platform memory policy.
298
+ The runtime sends the trusted user ID without a grant override.
299
+ To set an application policy, pass a callback:
300
+
301
+ ```ruby
302
+ memory_access: ->(user, env) { { user: 'read-write', project: 'none' } }
303
+ ```
304
+
305
+ Each scope accepts `none`, `read`, or `read-write`. A `nil` grant or two `none`
306
+ grants deny access. Invalid grants return a server error without an upstream
307
+ request. Callback errors also stop the request without a platform fallback.
308
+ The callback receives the trusted user and Rack environment.
309
+ Grant keys accept symbols or strings. Callback user hashes use string keys.
310
+ When both key forms exist, the string value takes precedence, including `nil` or `false`.
311
+
312
+ `learning_container: ->(user, input) { ... }` selects a learning container ID.
313
+ A thread must keep the same container.
314
+
315
+ For self-hosted Intelligence, set `api_url`, `runner_url`, and `client_url`
316
+ together. Runner and client URLs end in `/runner` and `/client`.
317
+ Do not append `/websocket`. TLS checks certificate trust and hostnames.
318
+
319
+ For a frontend on another origin, set `cors_origins: ['https://app.example.com']`.
320
+ The runtime sends no CORS allow headers by default.
321
+
322
+ The mount serves agent run/connect/stop routes, thread and memory routes,
323
+ subscriptions, annotations, and `/info`. A successful run response means the
324
+ runner joined its authenticated gateway channel, not that the agent finished.
325
+ The frontend receives events through Intelligence.
326
+
327
+ ## Add A2UI or MCP Apps
328
+
329
+ Pass a server-owned catalog through `a2ui`:
330
+
331
+ ```ruby
332
+ a2ui: {
333
+ 'injectA2UITool' => true,
334
+ 'schema' => catalog,
335
+ 'agents' => ['default']
336
+ }
337
+ ```
338
+
339
+ A string value for `injectA2UITool` selects a custom tool name.
340
+ The `agents` array limits A2UI to named agents. Omit it to include all agents.
341
+ Set `'enabled' => false` to disable A2UI.
342
+
343
+ The middleware adds the render tool and context. It validates complete component
344
+ trees before publishing them, then publishes complete data items as they arrive.
345
+ Browser actions become tool history for the next run. The agent owns model retries.
346
+
347
+ Set `defaultCatalogId` to select a host catalog. Otherwise, catalog selection uses
348
+ the frontend schema, a streamed non-basic ID, or the basic catalog URL.
349
+ The streaming gate checks component structure and references, not general JSON
350
+ Schema rules or binding resolution.
351
+
352
+ Configure MCP Apps servers and credentials on the server:
353
+
354
+ ```ruby
355
+ mcp_apps: { 'servers' => [{
356
+ 'type' => 'http',
357
+ 'url' => 'https://mcp.example.com/mcp',
358
+ 'serverId' => 'cards',
359
+ 'agentId' => 'default',
360
+ 'headers' => { 'authorization' => ENV.fetch('MCP_AUTHORIZATION') }
361
+ }] }
362
+ ```
363
+
364
+ The runtime discovers UI tools, executes calls, and publishes MCP Apps activities.
365
+ Iframe requests use configured server IDs or hashes. They cannot override the
366
+ server URL or credentials. The proxy accepts `tools/call`, `resources/read`,
367
+ `notifications/message`, and `ping`.
368
+
369
+ MCP Apps uses Streamable HTTP. SSE responses to HTTP requests are supported.
370
+ Legacy MCP SSE discovery is not supported.
371
+
372
+ ## Worker lifecycle
373
+
374
+ Create one runtime per worker after fork. Close it from your server's worker
375
+ shutdown hook:
376
+
377
+ ```ruby
378
+ Rails.application.config.x.copilotkit_runtime.close(timeout: 10)
379
+ ```
380
+
381
+ For preloaded Rails applications, create the runtime in the worker-boot hook
382
+ instead of the initializer. Mount a callable that resolves the worker instance:
383
+
384
+ ```ruby
385
+ mount ->(env) { Rails.application.config.x.copilotkit_runtime.call(env) } => '/copilotkit'
386
+ ```
387
+
388
+ Shutdown cancels pending startup requests and agent producers before draining
389
+ event delivery. The timeout bounds the drain phase. Platform cleanup has its
390
+ own three-second bound. If cleanup cannot reach Intelligence, the lease expires.
391
+
392
+ The default lease lasts 20 seconds and renews every 15 seconds. Set `lock_ttl:`
393
+ and `lock_heartbeat_interval:` together to change them. Renewal starts before
394
+ history loading and channel join. A lost lease cancels the agent.
395
+
396
+ The producer queue holds at most 32 events. The publisher waits for durable ACKs
397
+ and replays unacknowledged events after reconnect without rerunning the agent.
398
+ Gateway batches contain at most 32 events. Retries use at most four attempts.
399
+
400
+ HTTP stop requests check current ownership and an optional `runId` before
401
+ stopping a local run. Use the authenticated realtime gateway to stop a run
402
+ on another worker. Repeated stops do not cancel pending durable delivery.
403
+
404
+ ## Telemetry and errors
405
+
406
+ Pass a `CopilotKit::Telemetry` instance to control analytics:
407
+
408
+ ```ruby
409
+ telemetry: CopilotKit::Telemetry.new(disabled: true)
410
+ ```
411
+
412
+ `DO_NOT_TRACK=true` or `COPILOTKIT_TELEMETRY_DISABLED=true` also disables analytics.
413
+ Both variables accept `1`. Opt-out takes precedence over identity and sampling.
414
+
415
+ Analytics reports runtime creation, run/connect requests, and agent start,
416
+ completion, or failure. Event bodies contain no prompts, user IDs, thread/run
417
+ IDs, credentials, routes, or dependency error messages.
418
+
419
+ The default sample rate is `1`, so events are not sampled. Set `sample_rate:`
420
+ on `Telemetry` to change it. `COPILOTKIT_TELEMETRY_SAMPLE_RATE` overrides this
421
+ value. Rates must be finite numbers from zero through one. Invalid rates use
422
+ the default.
423
+
424
+ `telemetry_id:` takes precedence over `CPK_TELEMETRY_ID`. IDs allow 1–128 ASCII
425
+ letters, digits, underscores, or hyphens after spaces and tabs are trimmed.
426
+ An ID travels only in `X-CopilotKit-Telemetry-Id` and does not bypass sampling.
427
+
428
+ `license_token:` accepts a legacy analytics token. A blank value falls back to
429
+ `COPILOTKIT_LICENSE_TOKEN`. Without a standalone ID, a valid `telemetry_id` claim
430
+ selects every event. The exporter sends only the extracted ID, never the token.
431
+ This claim does not verify a license signature or grant access.
432
+
433
+ The default sink is `https://telemetry.copilotkit.ai/ingest`.
434
+ Set `COPILOTKIT_TELEMETRY_URL` or `Telemetry.new(url: endpoint)` to change it.
435
+ Use `Telemetry.new(exporter: ->(event) { ... })` for an application exporter.
436
+ An injected exporter owns its telemetry configuration.
437
+
438
+ The queue holds at most 256 events and discards new events when full.
439
+ Exports have a three-second timeout and do not follow redirects.
440
+ Exporter failures do not fail runtime requests. `flush(timeout:)` waits for
441
+ queued events. `close(timeout:)` drains the queue and stops the exporter.
442
+
443
+ Use `on_error: ->(error) { ... }` on the runtime for application error reporting.
444
+ It receives the native exception separately from analytics. Callback failures
445
+ do not affect HTTP responses or cleanup.