muxi 0.20260211.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2a71c3de149de10344234f91e906aa9282499252f85f9d732e7e786619675ac3
4
+ data.tar.gz: 46903422d8696f82902658d44817332a6ba6e2f7cbe5b1591b298215b57de7b6
5
+ SHA512:
6
+ metadata.gz: 5edbf6f9ef3eccec33b8d4bba841f0eab3af71714a06a7516ba4ab4c3bdbc6319b1ad2f61ebfcb2e1adc6dd841a021aab7f2fb2f87f897dd9f9437c7292d78ca
7
+ data.tar.gz: 512e17859b7b6349ee81d3e023ee53bcdb8982298eecbafe47c6e8c3e0bfd85fe64d8979ff2bae3568f00e714d26ef183eb85ef4da4f8ad3ecd302537ff69b7a
data/.rubocop.yml ADDED
@@ -0,0 +1,28 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.0
3
+ NewCops: enable
4
+ SuggestExtensions: false
5
+ Exclude:
6
+ - 'vendor/**/*'
7
+ - 'tmp/**/*'
8
+
9
+ Style/Documentation:
10
+ Enabled: false
11
+
12
+ Style/FrozenStringLiteralComment:
13
+ Enabled: true
14
+ EnforcedStyle: always
15
+
16
+ Metrics/MethodLength:
17
+ Max: 30
18
+
19
+ Metrics/ClassLength:
20
+ Max: 500
21
+
22
+ Metrics/BlockLength:
23
+ Exclude:
24
+ - 'spec/**/*'
25
+ - '*.gemspec'
26
+
27
+ Layout/LineLength:
28
+ Max: 150
data/CHANGELOG.md ADDED
@@ -0,0 +1,14 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [Unreleased]
6
+
7
+ ### Added
8
+ - `mode` parameter in `FormationConfig` for draft/live URL routing
9
+ - `mode="live"` (default): Uses `/api/{id}/v1` prefix
10
+ - `mode="draft"`: Uses `/draft/{id}/v1` prefix for local development with `muxi up`
11
+ - SDK version update notifications (via `X-Muxi-SDK-Latest` response header)
12
+ - Notifies when newer SDK version available (max once per 12 hours)
13
+ - Disable with `MUXI_SDK_VERSION_NOTIFICATION=0`
14
+ - Console telemetry support via internal `_app` parameter
data/README.md ADDED
@@ -0,0 +1,160 @@
1
+ # MUXI Ruby SDK
2
+
3
+ Official Ruby SDK for [MUXI](https://muxi.org) — infrastructure for AI agents.
4
+
5
+ **Highlights**
6
+ - Pure Ruby with stdlib only (no external dependencies)
7
+ - Built-in retries, idempotency, and typed errors
8
+ - Streaming helpers for chat/audio and deploy/log tails
9
+
10
+ > Need deeper usage notes? See the [User Guide](https://github.com/muxi-ai/muxi-ruby/blob/main/USER_GUIDE.md) for streaming, retries, and auth details.
11
+
12
+ ## Installation
13
+
14
+ Add to your Gemfile:
15
+
16
+ ```ruby
17
+ gem 'muxi'
18
+ ```
19
+
20
+ Or install directly:
21
+
22
+ ```bash
23
+ gem install muxi
24
+ ```
25
+
26
+ ## Quick Start
27
+
28
+ ### Server Management (Control Plane)
29
+
30
+ ```ruby
31
+ require 'muxi'
32
+
33
+ # Create a server client for managing formations
34
+ server = Muxi::ServerClient.new(
35
+ url: ENV['MUXI_SERVER_URL'],
36
+ key_id: ENV['MUXI_KEY_ID'],
37
+ secret_key: ENV['MUXI_SECRET_KEY']
38
+ )
39
+
40
+ # List formations
41
+ formations = server.list_formations
42
+ formations['formations'].each do |f|
43
+ puts "#{f['id']}: #{f['status']}"
44
+ end
45
+
46
+ # Get server status
47
+ status = server.status
48
+ puts "Uptime: #{status['uptime']}s"
49
+ ```
50
+
51
+ ### Formation Usage (Runtime API)
52
+
53
+ ```ruby
54
+ require 'muxi'
55
+
56
+ # Create a formation client
57
+ client = Muxi::FormationClient.new(
58
+ formation_id: 'my-bot',
59
+ server_url: ENV['MUXI_SERVER_URL'],
60
+ admin_key: ENV['MUXI_ADMIN_KEY'],
61
+ client_key: ENV['MUXI_CLIENT_KEY']
62
+ )
63
+
64
+ # Or connect directly to a formation
65
+ client = Muxi::FormationClient.new(
66
+ url: 'http://localhost:8001',
67
+ admin_key: ENV['MUXI_ADMIN_KEY'],
68
+ client_key: ENV['MUXI_CLIENT_KEY']
69
+ )
70
+
71
+ # Chat (non-streaming)
72
+ response = client.chat({ message: 'Hello!' }, user_id: 'user123')
73
+ puts response['message']
74
+
75
+ # Chat (streaming)
76
+ client.chat_stream({ message: 'Tell me a story' }, user_id: 'user123') do |event|
77
+ data = JSON.parse(event['data']) rescue event['data']
78
+ print data['text'] if data.is_a?(Hash) && data['text']
79
+ end
80
+
81
+ # Health check
82
+ health = client.health
83
+ puts "Status: #{health['status']}"
84
+ ```
85
+
86
+ ## Webhook Verification
87
+
88
+ ```ruby
89
+ require 'muxi'
90
+
91
+ # In your webhook handler (e.g., Rails controller)
92
+ def webhook
93
+ payload = request.raw_post
94
+ signature = request.headers['X-Muxi-Signature']
95
+
96
+ unless Muxi::Webhook.verify_signature(payload, signature, ENV['WEBHOOK_SECRET'])
97
+ render json: { error: 'Invalid signature' }, status: 401
98
+ return
99
+ end
100
+
101
+ event = Muxi::Webhook.parse(payload)
102
+
103
+ case event.status
104
+ when 'completed'
105
+ event.content.each do |item|
106
+ puts item.text if item.type == 'text'
107
+ end
108
+ when 'failed'
109
+ puts "Error: #{event.error.message}"
110
+ when 'awaiting_clarification'
111
+ puts "Question: #{event.clarification.question}"
112
+ end
113
+
114
+ render json: { received: true }
115
+ end
116
+ ```
117
+
118
+ ## Configuration
119
+
120
+ ### Environment Variables
121
+
122
+ - `MUXI_DEBUG=1` - Enable debug logging
123
+
124
+ ### Client Options
125
+
126
+ ```ruby
127
+ Muxi::ServerClient.new(
128
+ url: 'https://muxi.example.com:7890',
129
+ key_id: 'your-key-id',
130
+ secret_key: 'your-secret-key',
131
+ timeout: 30, # Request timeout in seconds
132
+ max_retries: 3, # Retry on 429/5xx errors
133
+ debug: true # Enable debug logging
134
+ )
135
+ ```
136
+
137
+ ## Error Handling
138
+
139
+ ```ruby
140
+ begin
141
+ server.get_formation('nonexistent')
142
+ rescue Muxi::NotFoundError => e
143
+ puts "Not found: #{e.message}"
144
+ rescue Muxi::AuthenticationError => e
145
+ puts "Auth failed: #{e.message}"
146
+ rescue Muxi::RateLimitError => e
147
+ puts "Rate limited. Retry after: #{e.retry_after}s"
148
+ rescue Muxi::MuxiError => e
149
+ puts "Error: #{e.message} (#{e.status_code})"
150
+ end
151
+ ```
152
+
153
+ ## Requirements
154
+
155
+ - Ruby 3.0+
156
+ - No external dependencies (uses stdlib only)
157
+
158
+ ## License
159
+
160
+ MIT
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+ require "rubocop/rake_task"
6
+
7
+ RSpec::Core::RakeTask.new(:spec)
8
+ RuboCop::RakeTask.new
9
+
10
+ task default: %i[spec]
data/SECURITY.md ADDED
@@ -0,0 +1,28 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ | Version | Supported |
6
+ | ------- | ------------------ |
7
+ | 0.x.x | :white_check_mark: |
8
+
9
+ ## Reporting a Vulnerability
10
+
11
+ If you discover a security vulnerability in this SDK, please report it by emailing security@muxi.org.
12
+
13
+ Please include:
14
+ - Description of the vulnerability
15
+ - Steps to reproduce
16
+ - Potential impact
17
+ - Any suggested fixes (optional)
18
+
19
+ We will acknowledge receipt within 48 hours and provide a detailed response within 7 days.
20
+
21
+ ## Security Best Practices
22
+
23
+ When using this SDK:
24
+
25
+ 1. **Never commit credentials** - Use environment variables or secure secret management
26
+ 2. **Validate webhook signatures** - Always verify `X-Muxi-Signature` headers
27
+ 3. **Use HTTPS** - Always connect to MUXI servers over HTTPS in production
28
+ 4. **Keep dependencies updated** - Regularly update the SDK to get security patches
data/USER_GUIDE.md ADDED
@@ -0,0 +1,151 @@
1
+ # MUXI Ruby SDK User Guide
2
+
3
+ ## Installation
4
+
5
+ ```bash
6
+ gem install muxi
7
+ ```
8
+
9
+ Or add to your Gemfile:
10
+
11
+ ```ruby
12
+ gem 'muxi'
13
+ ```
14
+
15
+ ## Quickstart
16
+
17
+ ```ruby
18
+ require 'muxi'
19
+
20
+ # Server client (management, HMAC auth)
21
+ server = Muxi::ServerClient.new(
22
+ url: 'https://server.example.com',
23
+ key_id: '<key_id>',
24
+ secret_key: '<secret_key>'
25
+ )
26
+ puts server.status
27
+
28
+ # Formation client (runtime, key auth)
29
+ formation = Muxi::FormationClient.new(
30
+ server_url: 'https://server.example.com',
31
+ formation_id: '<formation_id>',
32
+ client_key: '<client_key>',
33
+ admin_key: '<admin_key>'
34
+ )
35
+ puts formation.health
36
+ ```
37
+
38
+ ## Clients
39
+
40
+ - **ServerClient** (management, HMAC): deploy/list/update formations, server health/status, server logs.
41
+ - **FormationClient** (runtime, client/admin keys): chat/audio (streaming), agents, secrets, MCP, memory, scheduler, sessions/requests, identifiers, credentials, triggers/SOPs/audit, async/A2A/logging config, overlord/LLM settings, events/logs streaming.
42
+
43
+ ## Streaming
44
+
45
+ ```ruby
46
+ # Chat streaming (block)
47
+ formation.chat_stream({ message: 'Tell me a story' }, user_id: 'user-123') do |event|
48
+ puts event['data'] if event['event'] == 'message'
49
+ end
50
+
51
+ # Chat streaming (enumerator)
52
+ formation.chat_stream({ message: 'Tell me a story' }, user_id: 'user-123').each do |event|
53
+ puts event['data']
54
+ end
55
+
56
+ # Event streaming
57
+ formation.stream_events('user-123') do |event|
58
+ puts event
59
+ end
60
+
61
+ # Log streaming (admin)
62
+ formation.stream_logs(level: 'info') do |log|
63
+ puts log
64
+ end
65
+ ```
66
+
67
+ ## Auth & Headers
68
+
69
+ - **ServerClient**: HMAC with `key_id`/`secret_key` on `/rpc` endpoints.
70
+ - **FormationClient**: `X-MUXI-CLIENT-KEY` or `X-MUXI-ADMIN-KEY` on `/api/{formation}/v1`. Override `base_url` for direct access (e.g., `http://localhost:9012/v1`).
71
+ - **Idempotency**: `X-Muxi-Idempotency-Key` auto-generated on every request.
72
+ - **SDK headers**: `X-Muxi-SDK`, `X-Muxi-Client` set automatically.
73
+
74
+ ## Timeouts & Retries
75
+
76
+ - Default timeout: 30s (no timeout for streaming).
77
+ - Retries: `max_retries` with exponential backoff on 429/5xx/connection errors; respects `Retry-After`.
78
+ - Debug logging: enabled when `debug: true` or `MUXI_DEBUG=1`.
79
+
80
+ ## Error Handling
81
+
82
+ ```ruby
83
+ begin
84
+ formation.chat(message: 'hello')
85
+ rescue Muxi::AuthenticationError => e
86
+ puts "Auth failed: #{e.message}"
87
+ rescue Muxi::RateLimitError => e
88
+ puts "Rate limited. Retry after: #{e.retry_after}s"
89
+ rescue Muxi::NotFoundError => e
90
+ puts "Not found: #{e.message}"
91
+ rescue Muxi::MuxiError => e
92
+ puts "#{e.code}: #{e.message} (#{e.status_code})"
93
+ end
94
+ ```
95
+
96
+ Error types: `AuthenticationError`, `AuthorizationError`, `NotFoundError`, `ValidationError`, `RateLimitError`, `ServerError`, `ConnectionError`.
97
+
98
+ ## Notable Endpoints (FormationClient)
99
+
100
+ | Category | Methods |
101
+ |----------|---------|
102
+ | Chat/Audio | `chat`, `chat_stream`, `audio_chat`, `audio_chat_stream` |
103
+ | Memory | `get_memory_config`, `get_memories`, `add_memory`, `delete_memory`, `get_user_buffer`, `clear_user_buffer`, `clear_session_buffer`, `clear_all_buffers`, `get_buffer_stats` |
104
+ | Scheduler | `get_scheduler_config`, `get_scheduler_jobs`, `get_scheduler_job`, `create_scheduler_job`, `delete_scheduler_job` |
105
+ | Sessions | `get_sessions`, `get_session`, `get_session_messages`, `restore_session` |
106
+ | Requests | `get_requests`, `get_request_status`, `cancel_request` |
107
+ | Agents/MCP | `get_agents`, `get_agent`, `get_mcp_servers`, `get_mcp_server`, `get_mcp_tools` |
108
+ | Secrets | `get_secrets`, `get_secret`, `set_secret`, `delete_secret` |
109
+ | Credentials | `list_credential_services`, `list_credentials`, `get_credential`, `create_credential`, `delete_credential` |
110
+ | Identifiers | `get_user_identifiers_for_user`, `link_user_identifier`, `unlink_user_identifier` |
111
+ | Triggers/SOP | `get_triggers`, `get_trigger`, `fire_trigger`, `get_sops`, `get_sop` |
112
+ | Audit | `get_audit_log`, `clear_audit_log` |
113
+ | Config | `get_status`, `get_config`, `get_formation_info`, `get_async_config`, `get_a2a_config`, `get_logging_config`, `get_logging_destinations`, `get_overlord_config`, `get_overlord_persona`, `get_llm_settings` |
114
+ | Streaming | `stream_events`, `stream_logs`, `stream_request` |
115
+ | User | `resolve_user` |
116
+
117
+ ## Webhook Verification
118
+
119
+ ```ruby
120
+ require 'muxi'
121
+
122
+ post '/webhooks/muxi' do
123
+ payload = request.body.read
124
+ signature = request.env['HTTP_X_MUXI_SIGNATURE']
125
+
126
+ unless Muxi::Webhook.verify_signature(payload, signature, ENV['WEBHOOK_SECRET'])
127
+ halt 401, 'Invalid signature'
128
+ end
129
+
130
+ event = Muxi::Webhook.parse(payload)
131
+
132
+ case event.status
133
+ when 'completed'
134
+ event.content.each { |item| puts item.text if item.type == 'text' }
135
+ when 'failed'
136
+ puts "Error: #{event.error&.message}"
137
+ when 'awaiting_clarification'
138
+ puts "Question: #{event.clarification&.question}"
139
+ end
140
+
141
+ { status: 'received' }.to_json
142
+ end
143
+ ```
144
+
145
+ ## Testing Locally
146
+
147
+ ```bash
148
+ cd ruby
149
+ bundle install
150
+ rspec
151
+ ```
data/lib/muxi/auth.rb ADDED
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+ require "base64"
5
+
6
+ module Muxi
7
+ module Auth
8
+ module_function
9
+
10
+ def generate_hmac_signature(secret_key, method, path)
11
+ timestamp = Time.now.to_i
12
+ sign_path = path.split("?").first
13
+ message = "#{timestamp};#{method};#{sign_path}"
14
+
15
+ digest = OpenSSL::Digest.new("sha256")
16
+ hmac = OpenSSL::HMAC.digest(digest, secret_key, message)
17
+ signature = Base64.strict_encode64(hmac)
18
+
19
+ [signature, timestamp]
20
+ end
21
+
22
+ def build_auth_header(key_id, secret_key, method, path)
23
+ signature, timestamp = generate_hmac_signature(secret_key, method, path)
24
+ "MUXI-HMAC key=#{key_id}, timestamp=#{timestamp}, signature=#{signature}"
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Muxi
4
+ class MuxiError < StandardError
5
+ attr_reader :code, :status_code, :details
6
+
7
+ def initialize(code, message, status_code, details = nil)
8
+ @code = code
9
+ @status_code = status_code
10
+ @details = details || {}
11
+ super(code ? "#{code}: #{message}" : message)
12
+ end
13
+ end
14
+
15
+ class AuthenticationError < MuxiError; end
16
+ class AuthorizationError < MuxiError; end
17
+ class NotFoundError < MuxiError; end
18
+ class ConflictError < MuxiError; end
19
+ class ValidationError < MuxiError; end
20
+
21
+ class RateLimitError < MuxiError
22
+ attr_reader :retry_after
23
+
24
+ def initialize(message, status_code, retry_after: nil, details: nil)
25
+ super("RATE_LIMITED", message, status_code, details)
26
+ @retry_after = retry_after
27
+ end
28
+ end
29
+
30
+ class ServerError < MuxiError; end
31
+ class ConnectionError < MuxiError; end
32
+
33
+ class << self
34
+ def map_error(status, code, message, details = nil, retry_after = nil)
35
+ case status
36
+ when 401
37
+ AuthenticationError.new(code || "UNAUTHORIZED", message, status, details)
38
+ when 403
39
+ AuthorizationError.new(code || "FORBIDDEN", message, status, details)
40
+ when 404
41
+ NotFoundError.new(code || "NOT_FOUND", message, status, details)
42
+ when 409
43
+ ConflictError.new(code || "CONFLICT", message, status, details)
44
+ when 422
45
+ ValidationError.new(code || "VALIDATION_ERROR", message, status, details)
46
+ when 429
47
+ RateLimitError.new(message || "Too Many Requests", status, retry_after: retry_after, details: details)
48
+ when 500..599
49
+ ServerError.new(code || "SERVER_ERROR", message, status, details)
50
+ else
51
+ MuxiError.new(code || "ERROR", message, status, details)
52
+ end
53
+ end
54
+ end
55
+ end