agentdyne 1.0.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 +7 -0
- data/README.md +233 -0
- data/agentdyne.gemspec +29 -0
- data/lib/agentdyne/client.rb +262 -0
- data/lib/agentdyne/errors.rb +105 -0
- data/lib/agentdyne/http.rb +182 -0
- data/lib/agentdyne/version.rb +5 -0
- data/lib/agentdyne.rb +28 -0
- metadata +112 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 1c1119bd1943f2861ad02a8e489c4c3e6185712ff487d8800d1f8ce1133cb262
|
|
4
|
+
data.tar.gz: 01be54ee75e15986f9d3a830570d653794102ee88286c030ba2bbf4f77c83e74
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: '091bdb780213fa5b05438fa49a58690f5f3cc623959c548a54da0b52de7a9fae66bb014d7ac3b9fa16f9742c809e920cf72a7a4bb73f48a9770a8d901300fb75'
|
|
7
|
+
data.tar.gz: 9c89bf8c3d2b0843c90152536d61d17dfbcf96e1798180d3aee5627791d8de84176e2a7d53c51175181a280eb91e028ec3c23c5e348005ca39a9bb76a0fc3acc
|
data/README.md
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# agentdyne
|
|
2
|
+
|
|
3
|
+
Official Ruby SDK for [AgentDyne](https://agentdyne.com) — The Global Microagent Marketplace.
|
|
4
|
+
|
|
5
|
+
[](https://rubygems.org/gems/agentdyne)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```ruby
|
|
11
|
+
# Gemfile
|
|
12
|
+
gem "agentdyne"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
bundle install
|
|
17
|
+
# or standalone:
|
|
18
|
+
gem install agentdyne
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
require "agentdyne"
|
|
25
|
+
|
|
26
|
+
client = AgentDyne.new(api_key: "agd_your_key_here")
|
|
27
|
+
|
|
28
|
+
result = client.execute("agent_id", "Summarize this email thread...")
|
|
29
|
+
puts result.output
|
|
30
|
+
# => { "summary" => "...", "action_items" => [...] }
|
|
31
|
+
|
|
32
|
+
puts "Latency: #{result.latency_ms}ms Cost: $#{"%.6f" % result.cost}"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Authentication
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
export AGENTDYNE_API_KEY=agd_your_key_here
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
# Reads from env automatically
|
|
43
|
+
client = AgentDyne.new
|
|
44
|
+
|
|
45
|
+
# Or pass directly
|
|
46
|
+
client = AgentDyne.new(api_key: "agd_...")
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Usage
|
|
50
|
+
|
|
51
|
+
### List Agents
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
page = client.list_agents(category: "coding", sort: "rating", limit: 10)
|
|
55
|
+
page.data.each do |agent|
|
|
56
|
+
puts "#{agent.name} ★#{agent.average_rating} #{agent.pricing_model}"
|
|
57
|
+
end
|
|
58
|
+
puts "Total: #{page.pagination.total}"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Execute an Agent
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
# String input
|
|
65
|
+
result = client.execute("email-summarizer-pro", "Hi team, the Q4 report is attached...")
|
|
66
|
+
|
|
67
|
+
# Structured input
|
|
68
|
+
result = client.execute("code-review-agent", {
|
|
69
|
+
code: "def add(a, b)\n a + b\nend",
|
|
70
|
+
language: "ruby"
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
# With idempotency key (safe to retry on network failure)
|
|
74
|
+
result = client.execute("agent_id", "Hello", idempotency_key: SecureRandom.uuid)
|
|
75
|
+
|
|
76
|
+
puts result.output
|
|
77
|
+
puts "#{result.tokens.input} in / #{result.tokens.output} out tokens"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Streaming
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
client.stream("content-writer", "Write a blog post about AI agents in 2026") do |chunk|
|
|
84
|
+
case chunk.type
|
|
85
|
+
when "delta"
|
|
86
|
+
print chunk.delta
|
|
87
|
+
$stdout.flush
|
|
88
|
+
when "done"
|
|
89
|
+
puts "\n✓ Done (execution: #{chunk.execution_id})"
|
|
90
|
+
when "error"
|
|
91
|
+
warn "Stream error: #{chunk.error}"
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Poll Execution
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
# Start an execution (imagine it was async)
|
|
100
|
+
exec = client.poll_execution("exec_id", interval: 0.5, timeout: 60)
|
|
101
|
+
puts exec.status # => "success"
|
|
102
|
+
puts exec.output
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Paginate All Agents
|
|
106
|
+
|
|
107
|
+
```ruby
|
|
108
|
+
# Automatically pages through all results
|
|
109
|
+
client.paginate_agents(category: "finance").each do |agent|
|
|
110
|
+
puts agent.name
|
|
111
|
+
end
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### User & Quota
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
me = client.me
|
|
118
|
+
puts me.subscription_plan # => "pro"
|
|
119
|
+
puts me.full_name
|
|
120
|
+
|
|
121
|
+
quota = client.my_quota
|
|
122
|
+
puts "#{quota.used}/#{quota.quota} calls used (#{quota.percent_used}%)"
|
|
123
|
+
puts "Resets: #{quota.resets_at}"
|
|
124
|
+
|
|
125
|
+
# Update profile
|
|
126
|
+
client.update_profile(full_name: "Ada Lovelace", bio: "Building AI agents")
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Reviews
|
|
130
|
+
|
|
131
|
+
```ruby
|
|
132
|
+
# List reviews
|
|
133
|
+
page = client.list_reviews("agent_id")
|
|
134
|
+
page.data.each { |r| puts "★#{r.rating} — #{r.title}" }
|
|
135
|
+
|
|
136
|
+
# Post a review (must have executed the agent first)
|
|
137
|
+
review = client.create_review("agent_id",
|
|
138
|
+
rating: 5,
|
|
139
|
+
title: "Incredible accuracy",
|
|
140
|
+
body: "Handles every edge case I've thrown at it."
|
|
141
|
+
)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Webhooks
|
|
145
|
+
|
|
146
|
+
```ruby
|
|
147
|
+
# Rails controller
|
|
148
|
+
class WebhooksController < ApplicationController
|
|
149
|
+
skip_before_action :verify_authenticity_token
|
|
150
|
+
|
|
151
|
+
def agentdyne
|
|
152
|
+
client = AgentDyne.new
|
|
153
|
+
event = client.construct_webhook_event(
|
|
154
|
+
request.raw_post,
|
|
155
|
+
request.headers["X-AgentDyne-Signature"],
|
|
156
|
+
ENV["AGENTDYNE_WEBHOOK_SECRET"]
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
case event.type
|
|
160
|
+
when "execution.completed"
|
|
161
|
+
Rails.logger.info "Execution done: #{event.data["executionId"]}"
|
|
162
|
+
when "payout.processed"
|
|
163
|
+
Payout.record!(event.data)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
head :ok
|
|
167
|
+
|
|
168
|
+
rescue AgentDyne::WebhookSignatureError
|
|
169
|
+
head :bad_request
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
```ruby
|
|
175
|
+
# Sinatra
|
|
176
|
+
post "/webhook" do
|
|
177
|
+
client = AgentDyne.new
|
|
178
|
+
event = client.construct_webhook_event(
|
|
179
|
+
request.body.read,
|
|
180
|
+
request.env["HTTP_X_AGENTDYNE_SIGNATURE"],
|
|
181
|
+
ENV["AGENTDYNE_WEBHOOK_SECRET"]
|
|
182
|
+
)
|
|
183
|
+
"OK"
|
|
184
|
+
rescue AgentDyne::WebhookSignatureError
|
|
185
|
+
[400, "Invalid signature"]
|
|
186
|
+
end
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Error Handling
|
|
190
|
+
|
|
191
|
+
Every error inherits from `AgentDyne::AgentDyneError`:
|
|
192
|
+
|
|
193
|
+
```ruby
|
|
194
|
+
require "agentdyne"
|
|
195
|
+
|
|
196
|
+
begin
|
|
197
|
+
result = client.execute("agent_id", "Hello")
|
|
198
|
+
rescue AgentDyne::QuotaExceededError => e
|
|
199
|
+
puts "Upgrade at agentdyne.com/billing (plan: #{e.plan})"
|
|
200
|
+
rescue AgentDyne::RateLimitError => e
|
|
201
|
+
sleep e.retry_after_seconds
|
|
202
|
+
retry
|
|
203
|
+
rescue AgentDyne::SubscriptionRequiredError => e
|
|
204
|
+
puts "Subscribe to use agent: #{e.agent_id}"
|
|
205
|
+
rescue AgentDyne::NotFoundError
|
|
206
|
+
puts "Agent not found"
|
|
207
|
+
rescue AgentDyne::AuthenticationError
|
|
208
|
+
puts "Check your API key"
|
|
209
|
+
rescue AgentDyne::AgentDyneError => e
|
|
210
|
+
puts "#{e.message} (HTTP #{e.status_code}, code=#{e.code})"
|
|
211
|
+
end
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## Configuration
|
|
215
|
+
|
|
216
|
+
```ruby
|
|
217
|
+
client = AgentDyne.new(
|
|
218
|
+
api_key: "agd_...",
|
|
219
|
+
base_url: "http://localhost:3000", # local dev override
|
|
220
|
+
max_retries: 3, # retries on 429/5xx
|
|
221
|
+
timeout: 60 # seconds
|
|
222
|
+
)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Requirements
|
|
226
|
+
|
|
227
|
+
- Ruby >= 3.1
|
|
228
|
+
- No required dependencies (uses stdlib `net/http`, `openssl`, `json`)
|
|
229
|
+
- Optional: `rack` gem for `secure_compare` in webhook verification
|
|
230
|
+
|
|
231
|
+
## License
|
|
232
|
+
|
|
233
|
+
MIT © 2026 AgentDyne, Inc.
|
data/agentdyne.gemspec
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Gem::Specification.new do |spec|
|
|
2
|
+
spec.name = "agentdyne"
|
|
3
|
+
spec.version = "1.0.0"
|
|
4
|
+
spec.authors = ["AgentDyne, Inc."]
|
|
5
|
+
spec.email = ["sdk@agentdyne.com"]
|
|
6
|
+
spec.summary = "Official Ruby SDK for AgentDyne — The Global Microagent Marketplace"
|
|
7
|
+
spec.description = "Discover, execute, and monetise AI agents with the AgentDyne Ruby SDK. Zero required dependencies."
|
|
8
|
+
spec.homepage = "https://agentdyne.com"
|
|
9
|
+
spec.license = "MIT"
|
|
10
|
+
|
|
11
|
+
spec.metadata = {
|
|
12
|
+
"homepage_uri" => "https://agentdyne.com",
|
|
13
|
+
"source_code_uri" => "https://github.com/agentdyne/sdk-ruby",
|
|
14
|
+
"bug_tracker_uri" => "https://github.com/agentdyne/sdk-ruby/issues",
|
|
15
|
+
"documentation_uri" => "https://agentdyne.com/docs",
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
spec.required_ruby_version = ">= 3.1"
|
|
19
|
+
spec.files = Dir["lib/**/*.rb", "README.md", "LICENSE", "agentdyne.gemspec"]
|
|
20
|
+
spec.require_paths = ["lib"]
|
|
21
|
+
|
|
22
|
+
# Zero required dependencies — uses Ruby's stdlib (net/http, openssl, json).
|
|
23
|
+
# Optional: rack for webhook secure_compare
|
|
24
|
+
spec.add_runtime_dependency "rack", ">= 2.0"
|
|
25
|
+
|
|
26
|
+
spec.add_development_dependency "rspec", "~> 3.13"
|
|
27
|
+
spec.add_development_dependency "webmock", "~> 3.23"
|
|
28
|
+
spec.add_development_dependency "rake", "~> 13.2"
|
|
29
|
+
end
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "openssl"
|
|
5
|
+
require "ostruct"
|
|
6
|
+
|
|
7
|
+
module AgentDyne
|
|
8
|
+
# The main AgentDyne Ruby client.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# client = AgentDyne::Client.new(api_key: "agd_...")
|
|
12
|
+
# result = client.execute("agent_id", "Summarize this...")
|
|
13
|
+
# puts result.output
|
|
14
|
+
#
|
|
15
|
+
class Client
|
|
16
|
+
TERMINAL_STATUSES = %w[success failed timeout].freeze
|
|
17
|
+
|
|
18
|
+
# @param api_key [String] Your AgentDyne API key. Falls back to
|
|
19
|
+
# the AGENTDYNE_API_KEY environment variable.
|
|
20
|
+
# @param base_url [String] API base URL (default: https://api.agentdyne.com).
|
|
21
|
+
# @param max_retries [Integer] Retries on 429/5xx (default: 3).
|
|
22
|
+
# @param timeout [Integer] Request timeout in seconds (default: 60).
|
|
23
|
+
def initialize(
|
|
24
|
+
api_key: nil,
|
|
25
|
+
base_url: "https://api.agentdyne.com",
|
|
26
|
+
max_retries: 3,
|
|
27
|
+
timeout: 60
|
|
28
|
+
)
|
|
29
|
+
resolved_key = api_key || ENV["AGENTDYNE_API_KEY"]
|
|
30
|
+
raise ArgumentError, "AgentDyne API key is required. Pass api_key: or set AGENTDYNE_API_KEY." if resolved_key.nil? || resolved_key.empty?
|
|
31
|
+
|
|
32
|
+
@http = HttpClient.new(
|
|
33
|
+
api_key: resolved_key,
|
|
34
|
+
base_url: base_url,
|
|
35
|
+
timeout: timeout,
|
|
36
|
+
max_retries: max_retries,
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# ── Agents ──────────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
# List agents with optional filters.
|
|
43
|
+
#
|
|
44
|
+
# @param q [String, nil] Full-text search query.
|
|
45
|
+
# @param category [String, nil] Agent category.
|
|
46
|
+
# @param pricing [String, nil] Pricing model filter.
|
|
47
|
+
# @param sort [String, nil] Sort order ("popular", "rating", "newest").
|
|
48
|
+
# @param page [Integer] Page number (default: 1).
|
|
49
|
+
# @param limit [Integer] Results per page (default: 24, max: 100).
|
|
50
|
+
# @return [OpenStruct] with .data (Array) and .pagination.
|
|
51
|
+
#
|
|
52
|
+
# @example
|
|
53
|
+
# page = client.list_agents(category: "coding", sort: "rating")
|
|
54
|
+
# page.data.each { |a| puts "#{a['name']} ★#{a['average_rating']}" }
|
|
55
|
+
def list_agents(q: nil, category: nil, pricing: nil, sort: nil, page: 1, limit: 24)
|
|
56
|
+
params = { page: page, limit: limit }
|
|
57
|
+
params[:q] = q if q
|
|
58
|
+
params[:category] = category if category
|
|
59
|
+
params[:pricing] = pricing if pricing
|
|
60
|
+
params[:sort] = sort if sort
|
|
61
|
+
raw = @http.get("/v1/agents", params)
|
|
62
|
+
to_ostruct(raw)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Get a single agent by ID.
|
|
66
|
+
#
|
|
67
|
+
# @return [OpenStruct]
|
|
68
|
+
def get_agent(agent_id)
|
|
69
|
+
to_ostruct(@http.get("/v1/agents/#{agent_id}"))
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Search agents by keyword.
|
|
73
|
+
def search_agents(query, **kwargs)
|
|
74
|
+
list_agents(q: query, **kwargs)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Iterate through ALL matching agents across pages automatically.
|
|
78
|
+
#
|
|
79
|
+
# @example
|
|
80
|
+
# client.paginate_agents(category: "finance").each { |a| puts a.name }
|
|
81
|
+
def paginate_agents(**kwargs)
|
|
82
|
+
Enumerator.new do |y|
|
|
83
|
+
p = 1
|
|
84
|
+
loop do
|
|
85
|
+
page = list_agents(page: p, **kwargs)
|
|
86
|
+
page.data.each { |item| y << to_ostruct(item) }
|
|
87
|
+
break unless page.pagination.has_next
|
|
88
|
+
p += 1
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# ── Execution ────────────────────────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
# Execute an agent synchronously and return the output.
|
|
96
|
+
#
|
|
97
|
+
# @param agent_id [String]
|
|
98
|
+
# @param input [String, Hash, Array] Input to the agent.
|
|
99
|
+
# @param idempotency_key [String, nil] Optional UUID for safe retries.
|
|
100
|
+
# @return [OpenStruct] with .output, .latency_ms, .cost, .tokens, .execution_id
|
|
101
|
+
#
|
|
102
|
+
# @example
|
|
103
|
+
# result = client.execute("code-review-agent", { code: "def f(): pass", language: "python" })
|
|
104
|
+
# puts result.output
|
|
105
|
+
def execute(agent_id, input, idempotency_key: nil)
|
|
106
|
+
body = { input: input }
|
|
107
|
+
body[:idempotencyKey] = idempotency_key if idempotency_key
|
|
108
|
+
raw = @http.post("/v1/agents/#{agent_id}/execute", body)
|
|
109
|
+
to_ostruct(raw)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Stream an agent's output.
|
|
113
|
+
# Yields StreamChunk-like OpenStructs with .type, .delta, .execution_id.
|
|
114
|
+
#
|
|
115
|
+
# @example
|
|
116
|
+
# client.stream("content-writer", "Write a haiku about Ruby") do |chunk|
|
|
117
|
+
# print chunk.delta if chunk.type == "delta"
|
|
118
|
+
# end
|
|
119
|
+
def stream(agent_id, input, &block)
|
|
120
|
+
@http.stream("/v1/agents/#{agent_id}/execute", { input: input, stream: true }) do |raw_line|
|
|
121
|
+
begin
|
|
122
|
+
data = JSON.parse(raw_line)
|
|
123
|
+
chunk = OpenStruct.new(
|
|
124
|
+
type: data["type"] || "delta",
|
|
125
|
+
delta: data["delta"],
|
|
126
|
+
execution_id: data["executionId"],
|
|
127
|
+
error: data["error"],
|
|
128
|
+
)
|
|
129
|
+
rescue JSON::ParserError
|
|
130
|
+
chunk = OpenStruct.new(type: "delta", delta: raw_line)
|
|
131
|
+
end
|
|
132
|
+
block.call(chunk)
|
|
133
|
+
return if chunk.type == "done"
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# ── Executions ───────────────────────────────────────────────────────────
|
|
138
|
+
|
|
139
|
+
# Get a single execution by ID.
|
|
140
|
+
def get_execution(execution_id)
|
|
141
|
+
to_ostruct(@http.get("/v1/executions/#{execution_id}"))
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# List execution history.
|
|
145
|
+
def list_executions(agent_id: nil, status: nil, page: 1, limit: 20)
|
|
146
|
+
params = { page: page, limit: limit }
|
|
147
|
+
params[:agentId] = agent_id if agent_id
|
|
148
|
+
params[:status] = status if status
|
|
149
|
+
to_ostruct(@http.get("/v1/executions", params))
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Poll until an execution reaches a terminal state.
|
|
153
|
+
#
|
|
154
|
+
# @param interval [Float] Seconds between polls (default: 1.0).
|
|
155
|
+
# @param timeout [Float] Maximum seconds to wait (default: 120.0).
|
|
156
|
+
# @raise [AgentDyneError] if the execution does not complete in time.
|
|
157
|
+
#
|
|
158
|
+
# @example
|
|
159
|
+
# exec = client.poll_execution("exec_id", interval: 0.5)
|
|
160
|
+
def poll_execution(execution_id, interval: 1.0, timeout: 120.0)
|
|
161
|
+
deadline = Time.now + timeout
|
|
162
|
+
loop do
|
|
163
|
+
ex = get_execution(execution_id)
|
|
164
|
+
return ex if TERMINAL_STATUSES.include?(ex.status)
|
|
165
|
+
raise AgentDyneError, "Execution \"#{execution_id}\" did not complete within #{timeout}s" if Time.now > deadline
|
|
166
|
+
sleep(interval)
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# ── User ─────────────────────────────────────────────────────────────────
|
|
171
|
+
|
|
172
|
+
# Return the authenticated user's profile.
|
|
173
|
+
def me
|
|
174
|
+
to_ostruct(@http.get("/v1/user/me"))
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Return quota usage for the current billing period.
|
|
178
|
+
def my_quota
|
|
179
|
+
to_ostruct(@http.get("/v1/user/quota"))
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Update profile fields.
|
|
183
|
+
def update_profile(**updates)
|
|
184
|
+
to_ostruct(@http.patch("/v1/user/me", updates))
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# ── Reviews ──────────────────────────────────────────────────────────────
|
|
188
|
+
|
|
189
|
+
# List approved reviews for an agent.
|
|
190
|
+
def list_reviews(agent_id, page: 1, limit: 20)
|
|
191
|
+
to_ostruct(@http.get("/v1/agents/#{agent_id}/reviews", { page: page, limit: limit }))
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Post a review for an agent.
|
|
195
|
+
def create_review(agent_id, rating:, title: nil, body: nil)
|
|
196
|
+
payload = { rating: rating }
|
|
197
|
+
payload[:title] = title if title
|
|
198
|
+
payload[:body] = body if body
|
|
199
|
+
to_ostruct(@http.post("/v1/agents/#{agent_id}/reviews", payload))
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# ── Notifications ────────────────────────────────────────────────────────
|
|
203
|
+
|
|
204
|
+
# List your notifications.
|
|
205
|
+
def list_notifications
|
|
206
|
+
@http.get("/v1/notifications").fetch("notifications", []).map { |n| to_ostruct(n) }
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Mark all notifications as read.
|
|
210
|
+
def mark_notifications_read
|
|
211
|
+
@http.patch("/v1/notifications").fetch("ok", false)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# ── Webhooks ─────────────────────────────────────────────────────────────
|
|
215
|
+
|
|
216
|
+
# Verify and parse an incoming AgentDyne webhook.
|
|
217
|
+
#
|
|
218
|
+
# Raises WebhookSignatureError if the signature is invalid.
|
|
219
|
+
#
|
|
220
|
+
# @param payload [String] Raw request body.
|
|
221
|
+
# @param signature [String] Value of X-AgentDyne-Signature header.
|
|
222
|
+
# @param secret [String] Your webhook signing secret.
|
|
223
|
+
# @return [OpenStruct] Parsed event with .type and .data.
|
|
224
|
+
#
|
|
225
|
+
# @example (Sinatra)
|
|
226
|
+
# post "/webhook" do
|
|
227
|
+
# event = client.construct_webhook_event(
|
|
228
|
+
# request.body.read,
|
|
229
|
+
# request.env["HTTP_X_AGENTDYNE_SIGNATURE"],
|
|
230
|
+
# ENV["WEBHOOK_SECRET"]
|
|
231
|
+
# )
|
|
232
|
+
# case event.type
|
|
233
|
+
# when "execution.completed" then process_execution(event.data)
|
|
234
|
+
# end
|
|
235
|
+
# "OK"
|
|
236
|
+
# end
|
|
237
|
+
def construct_webhook_event(payload, signature, secret)
|
|
238
|
+
sig_clean = signature.to_s.sub(/^sha256=/, "")
|
|
239
|
+
expected = OpenSSL::HMAC.hexdigest("SHA256", secret, payload)
|
|
240
|
+
|
|
241
|
+
unless Rack::Utils.secure_compare(expected, sig_clean) rescue (expected == sig_clean)
|
|
242
|
+
raise WebhookSignatureError
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
begin
|
|
246
|
+
to_ostruct(JSON.parse(payload))
|
|
247
|
+
rescue JSON::ParserError
|
|
248
|
+
raise WebhookSignatureError, "Webhook payload is not valid JSON"
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
private
|
|
253
|
+
|
|
254
|
+
def to_ostruct(obj)
|
|
255
|
+
case obj
|
|
256
|
+
when Hash then OpenStruct.new(obj.transform_values { |v| to_ostruct(v) })
|
|
257
|
+
when Array then obj.map { |item| to_ostruct(item) }
|
|
258
|
+
else obj
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AgentDyne
|
|
4
|
+
# Base error for all AgentDyne SDK exceptions.
|
|
5
|
+
class AgentDyneError < StandardError
|
|
6
|
+
attr_reader :status_code, :code, :raw
|
|
7
|
+
|
|
8
|
+
def initialize(message, status_code: nil, code: nil, raw: nil)
|
|
9
|
+
super(message)
|
|
10
|
+
@status_code = status_code
|
|
11
|
+
@code = code
|
|
12
|
+
@raw = raw
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def to_h
|
|
16
|
+
{ error: self.class.name, message: message, status_code: status_code, code: code }
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# HTTP 401 — API key missing, invalid, or revoked.
|
|
21
|
+
class AuthenticationError < AgentDyneError
|
|
22
|
+
def initialize(message = "Invalid or missing API key", raw: nil)
|
|
23
|
+
super(message, status_code: 401, code: "AUTHENTICATION_ERROR", raw: raw)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# HTTP 403 — insufficient permissions.
|
|
28
|
+
class PermissionDeniedError < AgentDyneError
|
|
29
|
+
def initialize(message = "Permission denied", raw: nil)
|
|
30
|
+
super(message, status_code: 403, code: "PERMISSION_DENIED", raw: raw)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# HTTP 403 / SUBSCRIPTION_REQUIRED — agent requires subscription.
|
|
35
|
+
class SubscriptionRequiredError < AgentDyneError
|
|
36
|
+
attr_reader :agent_id
|
|
37
|
+
|
|
38
|
+
def initialize(agent_id = nil, raw: nil)
|
|
39
|
+
msg = agent_id ? "Agent \"#{agent_id}\" requires an active subscription" : "Subscription required"
|
|
40
|
+
super(msg, status_code: 403, code: "SUBSCRIPTION_REQUIRED", raw: raw)
|
|
41
|
+
@agent_id = agent_id
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# HTTP 404 — resource not found.
|
|
46
|
+
class NotFoundError < AgentDyneError
|
|
47
|
+
def initialize(message = "Resource not found", raw: nil)
|
|
48
|
+
super(message, status_code: 404, code: "NOT_FOUND", raw: raw)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# HTTP 400 — malformed request or missing required fields.
|
|
53
|
+
class ValidationError < AgentDyneError
|
|
54
|
+
attr_reader :fields
|
|
55
|
+
|
|
56
|
+
def initialize(message, fields: nil, raw: nil)
|
|
57
|
+
super(message, status_code: 400, code: "VALIDATION_ERROR", raw: raw)
|
|
58
|
+
@fields = fields || {}
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# HTTP 429 — per-minute rate limit exceeded.
|
|
63
|
+
class RateLimitError < AgentDyneError
|
|
64
|
+
attr_reader :retry_after_seconds
|
|
65
|
+
|
|
66
|
+
def initialize(retry_after_seconds = 60.0, raw: nil)
|
|
67
|
+
super("Rate limit exceeded. Retry after #{retry_after_seconds.to_i}s",
|
|
68
|
+
status_code: 429, code: "RATE_LIMIT_EXCEEDED", raw: raw)
|
|
69
|
+
@retry_after_seconds = retry_after_seconds
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# HTTP 429 / QUOTA_EXCEEDED — monthly execution quota exhausted.
|
|
74
|
+
class QuotaExceededError < AgentDyneError
|
|
75
|
+
attr_reader :plan
|
|
76
|
+
|
|
77
|
+
def initialize(plan = nil, raw: nil)
|
|
78
|
+
msg = plan ? "Monthly quota exceeded on \"#{plan}\" plan. Please upgrade." : "Monthly quota exceeded."
|
|
79
|
+
super(msg, status_code: 429, code: "QUOTA_EXCEEDED", raw: raw)
|
|
80
|
+
@plan = plan
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# 5xx — unrecoverable server error.
|
|
85
|
+
class InternalServerError < AgentDyneError
|
|
86
|
+
def initialize(message = "Internal server error", raw: nil)
|
|
87
|
+
super(message, status_code: 500, code: "INTERNAL_SERVER_ERROR", raw: raw)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Network-level failure (connection refused, DNS, TLS).
|
|
92
|
+
class NetworkError < AgentDyneError
|
|
93
|
+
def initialize(message, cause: nil)
|
|
94
|
+
super(message, code: "NETWORK_ERROR")
|
|
95
|
+
set_backtrace(cause&.backtrace)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Webhook HMAC-SHA256 signature verification failed.
|
|
100
|
+
class WebhookSignatureError < AgentDyneError
|
|
101
|
+
def initialize(message = "Webhook signature verification failed")
|
|
102
|
+
super(message, code: "WEBHOOK_SIGNATURE_INVALID")
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "net/http"
|
|
5
|
+
require "openssl"
|
|
6
|
+
|
|
7
|
+
module AgentDyne
|
|
8
|
+
# Internal HTTP client. Not intended for direct use.
|
|
9
|
+
class HttpClient # :nodoc:
|
|
10
|
+
SDK_VERSION = AgentDyne::VERSION
|
|
11
|
+
DEFAULT_TIMEOUT = 60
|
|
12
|
+
NON_RETRYABLE = [400, 401, 403, 404, 422].freeze
|
|
13
|
+
|
|
14
|
+
def initialize(api_key:, base_url:, timeout:, max_retries:)
|
|
15
|
+
@api_key = api_key
|
|
16
|
+
@base_url = URI(base_url.chomp("/"))
|
|
17
|
+
@timeout = timeout
|
|
18
|
+
@max_retries = max_retries
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def get(path, params = {})
|
|
22
|
+
request_with_retry(:get, path, params: params)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def post(path, body = nil)
|
|
26
|
+
request_with_retry(:post, path, body: body)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def patch(path, body = nil)
|
|
30
|
+
request_with_retry(:patch, path, body: body)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def delete(path)
|
|
34
|
+
request_with_retry(:delete, path)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Yields raw SSE data lines.
|
|
38
|
+
def stream(path, body, &block)
|
|
39
|
+
uri = build_uri(path)
|
|
40
|
+
http = build_http(uri)
|
|
41
|
+
|
|
42
|
+
req = Net::HTTP::Post.new(uri)
|
|
43
|
+
apply_headers(req, stream: true)
|
|
44
|
+
req.body = JSON.generate(body)
|
|
45
|
+
|
|
46
|
+
http.request(req) do |resp|
|
|
47
|
+
raise_for_status(resp) unless resp.is_a?(Net::HTTPSuccess)
|
|
48
|
+
resp.read_body do |chunk|
|
|
49
|
+
chunk.each_line do |line|
|
|
50
|
+
line = line.strip
|
|
51
|
+
next unless line.start_with?("data: ")
|
|
52
|
+
data = line[6..]
|
|
53
|
+
return if data == "[DONE]"
|
|
54
|
+
block.call(data)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT, SocketError => e
|
|
59
|
+
raise NetworkError.new(e.message, cause: e)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def request_with_retry(method, path, params: {}, body: nil)
|
|
65
|
+
last_error = nil
|
|
66
|
+
|
|
67
|
+
(@max_retries + 1).times do |attempt|
|
|
68
|
+
if attempt > 0
|
|
69
|
+
sleep(backoff_delay(attempt - 1))
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
begin
|
|
73
|
+
result = execute_request(method, path, params: params, body: body)
|
|
74
|
+
return result
|
|
75
|
+
rescue RateLimitError => e
|
|
76
|
+
sleep(e.retry_after_seconds) if attempt < @max_retries
|
|
77
|
+
last_error = e
|
|
78
|
+
rescue AgentDyneError => e
|
|
79
|
+
raise if NON_RETRYABLE.include?(e.status_code)
|
|
80
|
+
raise if e.status_code && e.status_code < 500
|
|
81
|
+
last_error = e
|
|
82
|
+
rescue NetworkError => e
|
|
83
|
+
last_error = e
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
raise last_error
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def execute_request(method, path, params: {}, body: nil)
|
|
91
|
+
uri = build_uri(path, params)
|
|
92
|
+
http = build_http(uri)
|
|
93
|
+
req = build_request(method, uri, body)
|
|
94
|
+
|
|
95
|
+
resp = http.request(req)
|
|
96
|
+
raise_for_status(resp)
|
|
97
|
+
|
|
98
|
+
body_str = resp.body
|
|
99
|
+
return nil if body_str.nil? || body_str.empty?
|
|
100
|
+
JSON.parse(body_str)
|
|
101
|
+
rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT, SocketError => e
|
|
102
|
+
raise NetworkError.new(e.message, cause: e)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def build_uri(path, params = {})
|
|
106
|
+
uri = URI.join(@base_url.to_s + "/", path.delete_prefix("/"))
|
|
107
|
+
unless params.empty?
|
|
108
|
+
uri.query = URI.encode_www_form(params.compact)
|
|
109
|
+
end
|
|
110
|
+
uri
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def build_http(uri)
|
|
114
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
115
|
+
http.use_ssl = uri.scheme == "https"
|
|
116
|
+
http.read_timeout = @timeout
|
|
117
|
+
http.open_timeout = @timeout
|
|
118
|
+
http
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def build_request(method, uri, body)
|
|
122
|
+
klass = {
|
|
123
|
+
get: Net::HTTP::Get,
|
|
124
|
+
post: Net::HTTP::Post,
|
|
125
|
+
patch: Net::HTTP::Patch,
|
|
126
|
+
delete: Net::HTTP::Delete,
|
|
127
|
+
}[method]
|
|
128
|
+
req = klass.new(uri)
|
|
129
|
+
apply_headers(req)
|
|
130
|
+
req.body = JSON.generate(body) if body
|
|
131
|
+
req
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def apply_headers(req, stream: false)
|
|
135
|
+
req["Authorization"] = "Bearer #{@api_key}"
|
|
136
|
+
req["Content-Type"] = "application/json"
|
|
137
|
+
req["Accept"] = stream ? "text/event-stream" : "application/json"
|
|
138
|
+
req["User-Agent"] = "agentdyne-ruby/#{SDK_VERSION}"
|
|
139
|
+
req["X-SDK-Language"]= "ruby"
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def raise_for_status(resp)
|
|
143
|
+
return if resp.is_a?(Net::HTTPSuccess)
|
|
144
|
+
|
|
145
|
+
status = resp.code.to_i
|
|
146
|
+
raw_body = resp.body.to_s
|
|
147
|
+
body = begin; JSON.parse(raw_body); rescue JSON::ParserError; {}; end
|
|
148
|
+
|
|
149
|
+
message = body["error"] || body["message"] || "HTTP #{status}"
|
|
150
|
+
code = body["code"]
|
|
151
|
+
|
|
152
|
+
case status
|
|
153
|
+
when 400 then raise ValidationError.new(message, fields: body["fields"], raw: body)
|
|
154
|
+
when 401 then raise AuthenticationError.new(message, raw: body)
|
|
155
|
+
when 403
|
|
156
|
+
if code == "SUBSCRIPTION_REQUIRED"
|
|
157
|
+
raise SubscriptionRequiredError.new(nil, raw: body)
|
|
158
|
+
end
|
|
159
|
+
raise PermissionDeniedError.new(message, raw: body)
|
|
160
|
+
when 404 then raise NotFoundError.new(message, raw: body)
|
|
161
|
+
when 429
|
|
162
|
+
if code == "QUOTA_EXCEEDED"
|
|
163
|
+
raise QuotaExceededError.new(nil, raw: body)
|
|
164
|
+
end
|
|
165
|
+
retry_after = (resp["Retry-After"] || "60").to_f
|
|
166
|
+
raise RateLimitError.new(retry_after, raw: body)
|
|
167
|
+
else
|
|
168
|
+
if status >= 500
|
|
169
|
+
raise InternalServerError.new(message, raw: body)
|
|
170
|
+
end
|
|
171
|
+
raise AgentDyneError.new(message, status_code: status, code: code, raw: body)
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def backoff_delay(attempt)
|
|
176
|
+
base = 0.5
|
|
177
|
+
cap = 30.0
|
|
178
|
+
ceiling = [cap, base * (2**attempt)].min
|
|
179
|
+
rand * ceiling
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
data/lib/agentdyne.rb
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# lib/agentdyne.rb — AgentDyne Ruby SDK main entry point.
|
|
4
|
+
#
|
|
5
|
+
# Quick start:
|
|
6
|
+
#
|
|
7
|
+
# require "agentdyne"
|
|
8
|
+
#
|
|
9
|
+
# client = AgentDyne::Client.new(api_key: "agd_...")
|
|
10
|
+
# result = client.execute("agent_id", "Summarize this email...")
|
|
11
|
+
# puts result.output
|
|
12
|
+
#
|
|
13
|
+
|
|
14
|
+
require_relative "agentdyne/version"
|
|
15
|
+
require_relative "agentdyne/errors"
|
|
16
|
+
require_relative "agentdyne/types"
|
|
17
|
+
require_relative "agentdyne/http"
|
|
18
|
+
require_relative "agentdyne/client"
|
|
19
|
+
|
|
20
|
+
module AgentDyne
|
|
21
|
+
# Convenience constructor — mirrors Python and Node patterns.
|
|
22
|
+
#
|
|
23
|
+
# client = AgentDyne.new(api_key: "agd_...")
|
|
24
|
+
#
|
|
25
|
+
def self.new(**kwargs)
|
|
26
|
+
Client.new(**kwargs)
|
|
27
|
+
end
|
|
28
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: agentdyne
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- AgentDyne, Inc.
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-04-13 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: rack
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '2.0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '2.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rspec
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '3.13'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '3.13'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: webmock
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '3.23'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '3.23'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: rake
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '13.2'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '13.2'
|
|
69
|
+
description: Discover, execute, and monetise AI agents with the AgentDyne Ruby SDK.
|
|
70
|
+
Zero required dependencies.
|
|
71
|
+
email:
|
|
72
|
+
- sdk@agentdyne.com
|
|
73
|
+
executables: []
|
|
74
|
+
extensions: []
|
|
75
|
+
extra_rdoc_files: []
|
|
76
|
+
files:
|
|
77
|
+
- README.md
|
|
78
|
+
- agentdyne.gemspec
|
|
79
|
+
- lib/agentdyne.rb
|
|
80
|
+
- lib/agentdyne/client.rb
|
|
81
|
+
- lib/agentdyne/errors.rb
|
|
82
|
+
- lib/agentdyne/http.rb
|
|
83
|
+
- lib/agentdyne/version.rb
|
|
84
|
+
homepage: https://agentdyne.com
|
|
85
|
+
licenses:
|
|
86
|
+
- MIT
|
|
87
|
+
metadata:
|
|
88
|
+
homepage_uri: https://agentdyne.com
|
|
89
|
+
source_code_uri: https://github.com/agentdyne/sdk-ruby
|
|
90
|
+
bug_tracker_uri: https://github.com/agentdyne/sdk-ruby/issues
|
|
91
|
+
documentation_uri: https://agentdyne.com/docs
|
|
92
|
+
post_install_message:
|
|
93
|
+
rdoc_options: []
|
|
94
|
+
require_paths:
|
|
95
|
+
- lib
|
|
96
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
97
|
+
requirements:
|
|
98
|
+
- - ">="
|
|
99
|
+
- !ruby/object:Gem::Version
|
|
100
|
+
version: '3.1'
|
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
102
|
+
requirements:
|
|
103
|
+
- - ">="
|
|
104
|
+
- !ruby/object:Gem::Version
|
|
105
|
+
version: '0'
|
|
106
|
+
requirements: []
|
|
107
|
+
rubyforge_project:
|
|
108
|
+
rubygems_version: 2.7.6.3
|
|
109
|
+
signing_key:
|
|
110
|
+
specification_version: 4
|
|
111
|
+
summary: Official Ruby SDK for AgentDyne — The Global Microagent Marketplace
|
|
112
|
+
test_files: []
|