wave-dispatch 0.5.0 → 0.5.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 +4 -4
- data/lib/wave_dispatch.rb +82 -7
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 409e75177b57d71d5c816915aca6be428f36539c462c19610a945d34435d90ce
|
|
4
|
+
data.tar.gz: 364157ef490e38907643ba7b5a46a5d33a7c0bf41bc5392681fdf3ff4831f512
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5cef51963137cdccaf2df37be1d4337bbdc54904afda8a02541451fe2755645d139e6fc246f183aa923fc35d3fbcca432016f4c368419dd1d248ec8696355e4e
|
|
7
|
+
data.tar.gz: 76f60d66aebc013a709155442104dcd47e7dc50db030e326e8c8e75f58cf5a7b9061e8f3a2b4586998af0d121edf1c664eaeaa472d4cda1d98b0b6b582fdc10b
|
data/lib/wave_dispatch.rb
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
# wave Dispatch — thin Ruby client. Route each request to the cheapest capable model (local-first;
|
|
2
2
|
# escalate to your frontier only when needed). BYO keys + infra. Stdlib only (net/http).
|
|
3
|
+
require "base64"
|
|
3
4
|
require "net/http"
|
|
4
5
|
require "json"
|
|
5
6
|
require "uri"
|
|
6
7
|
require "openssl"
|
|
7
8
|
|
|
8
9
|
module WaveDispatch
|
|
9
|
-
VERSION = "0.5.
|
|
10
|
+
VERSION = "0.5.1"
|
|
10
11
|
|
|
12
|
+
# 0.5.1 — payment hook: a Proc called once with the 402 challenge body (Hash) that returns the
|
|
13
|
+
# headers (Hash[String => String]) to retry the request with. Pair with `Client.wallet_hook(provider:,
|
|
14
|
+
# credentials:)` for the built-in CDP / Privy / Bridge factories, or build a Proc yourself.
|
|
11
15
|
class Client
|
|
12
16
|
def initialize(license = ENV["WAVE_LICENSE"], endpoint: "https://dispatch.wave.online",
|
|
13
|
-
agents_endpoint: ENV["WAVE_AGENTS_ENDPOINT"] || "https://dispatch-agents.wave.online"
|
|
17
|
+
agents_endpoint: ENV["WAVE_AGENTS_ENDPOINT"] || "https://dispatch-agents.wave.online",
|
|
18
|
+
payment_hook: nil)
|
|
14
19
|
@license = license
|
|
15
20
|
@endpoint = endpoint
|
|
16
21
|
@agents = agents_endpoint
|
|
22
|
+
@payment_hook = payment_hook # 0.5.1 — handles 402 inside the client
|
|
17
23
|
end
|
|
18
24
|
|
|
19
25
|
# Classify a prompt (no execution) -> {"route", "probability", "margin", "forward"}
|
|
@@ -22,6 +28,9 @@ module WaveDispatch
|
|
|
22
28
|
# Classify and run on the edge if your plan allows it.
|
|
23
29
|
def execute(prompt) = send_req(:post, @endpoint + "/", { prompt: prompt, execute: true })
|
|
24
30
|
|
|
31
|
+
# Classify a pre-computed 768-d embedding (matmul-only: cheapest + fastest).
|
|
32
|
+
def route_vector(vector) = send_req(:post, @endpoint + "/", { vector: vector })
|
|
33
|
+
|
|
25
34
|
# This license's savings ledger (decisions, saved_usd, saved_pct, ...). Requires a license.
|
|
26
35
|
def savings = send_req(:get, "#{@agents}/ledger/summary?license=#{lic}")
|
|
27
36
|
|
|
@@ -31,6 +40,17 @@ module WaveDispatch
|
|
|
31
40
|
# Start/replace a programmatic subscription (plan: agent_starter | agent_pro | agent_scale).
|
|
32
41
|
def subscribe(plan) = send_req(:post, "#{@agents}/subscription/create", { license: @license, plan: plan })
|
|
33
42
|
|
|
43
|
+
# 0.5.1 — build a payment_hook for a built-in provider. provider: :cdp | :privy | :bridge.
|
|
44
|
+
# credentials per provider: cdp: {api_key:, api_secret:, address:}; privy: {app_id:, app_secret:,
|
|
45
|
+
# wallet_id:}; bridge: {api_key:, source_wallet:, destination:}. For custom wallets, build a Proc
|
|
46
|
+
# of your own that returns the header Hash you want set on the retry.
|
|
47
|
+
def self.wallet_hook(provider:, credentials: {})
|
|
48
|
+
provider = provider.to_sym
|
|
49
|
+
header = { cdp: "cdp-payment", privy: "privy-payment", bridge: "bridge-payment" }[provider]
|
|
50
|
+
raise "dispatch.wallet_hook: unknown provider #{provider.inspect}" unless header
|
|
51
|
+
->(challenge) { { header => WaveDispatch.wallet_sign(provider, credentials, challenge) } }
|
|
52
|
+
end
|
|
53
|
+
|
|
34
54
|
private
|
|
35
55
|
|
|
36
56
|
def lic
|
|
@@ -38,18 +58,73 @@ module WaveDispatch
|
|
|
38
58
|
URI.encode_www_form_component(@license)
|
|
39
59
|
end
|
|
40
60
|
|
|
41
|
-
def
|
|
61
|
+
def build_req(method, url, body)
|
|
42
62
|
uri = URI(url)
|
|
43
63
|
req = (method == :post ? Net::HTTP::Post : Net::HTTP::Get).new(uri, "content-type" => "application/json")
|
|
44
64
|
req["authorization"] = "Bearer #{@license}" if @license
|
|
45
65
|
req.body = body.to_json if body
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
66
|
+
[uri, req]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def http_call(uri, req)
|
|
70
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https",
|
|
71
|
+
verify_mode: OpenSSL::SSL::VERIFY_PEER) { |h| h.request(req) }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def send_req(method, url, body = nil)
|
|
75
|
+
uri, req = build_req(method, url, body)
|
|
76
|
+
res = http_call(uri, req)
|
|
77
|
+
if res.code == "402" && @payment_hook
|
|
78
|
+
challenge = (JSON.parse(res.body) rescue {})
|
|
79
|
+
extra = @payment_hook.call(challenge) || {}
|
|
80
|
+
uri2, req2 = build_req(method, url, body)
|
|
81
|
+
extra.each { |k, v| req2[k.to_s] = v }
|
|
82
|
+
res = http_call(uri2, req2)
|
|
83
|
+
end
|
|
84
|
+
raise "dispatch: 402 payment required (x402) — pay and retry, or set a license / payment_hook" if res.code == "402"
|
|
49
85
|
raise "dispatch: 401 unauthorized — set a valid license" if res.code == "401"
|
|
50
|
-
# any other non-2xx must raise — never JSON.parse an error body and return it as a success result
|
|
51
86
|
raise "dispatch: #{res.code} #{res.body.to_s[0, 160]}" unless res.code.start_with?("2")
|
|
52
87
|
JSON.parse(res.body)
|
|
53
88
|
end
|
|
54
89
|
end
|
|
90
|
+
|
|
91
|
+
# Built-in provider sign — HTTP orchestration only; actual signing happens at the provider.
|
|
92
|
+
def self.wallet_sign(provider, creds, challenge)
|
|
93
|
+
accepts = challenge.is_a?(Hash) ? (challenge["accepts"] || []) : []
|
|
94
|
+
accept = accepts.find { |a| a.is_a?(Hash) && a["protocol"] == provider.to_s } || accepts.first || {}
|
|
95
|
+
case provider
|
|
96
|
+
when :cdp
|
|
97
|
+
# CDP-JWT signing is non-trivial in stdlib-only Ruby; the built-in returns a marker payload that
|
|
98
|
+
# the worker accepts via the wave-payments adapter when WAVE_VERIFY_URL is set. For full on-chain
|
|
99
|
+
# CDP signing, build your own Proc with the official Coinbase CDP gem.
|
|
100
|
+
{ provider: "cdp", address: creds[:address], accept: accept,
|
|
101
|
+
hint: "use the coinbase-cdp gem for CDP-JWT signing in production" }.to_json
|
|
102
|
+
when :privy
|
|
103
|
+
%i[app_id app_secret wallet_id].each { |k| raise "dispatch.wallet_hook(privy): #{k} required" unless creds[k] }
|
|
104
|
+
basic = Base64.strict_encode64("#{creds[:app_id]}:#{creds[:app_secret]}")
|
|
105
|
+
uri = URI("https://auth.privy.io/api/v1/wallets/#{URI.encode_www_form_component(creds[:wallet_id])}/rpc")
|
|
106
|
+
req = Net::HTTP::Post.new(uri, "content-type" => "application/json",
|
|
107
|
+
"authorization" => "Basic #{basic}", "privy-app-id" => creds[:app_id])
|
|
108
|
+
req.body = { method: "personal_sign", params: { message: accept.to_json }, chain_type: "ethereum" }.to_json
|
|
109
|
+
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true,
|
|
110
|
+
verify_mode: OpenSSL::SSL::VERIFY_PEER) { |h| h.request(req) }
|
|
111
|
+
raise "dispatch.wallet_hook(privy): provider #{res.code}" unless res.code.start_with?("2")
|
|
112
|
+
j = (JSON.parse(res.body) rescue {})
|
|
113
|
+
sig = (j.dig("data", "signature") || j["signature"])
|
|
114
|
+
{ provider: "privy", signature: sig, accept: accept }.to_json
|
|
115
|
+
when :bridge
|
|
116
|
+
raise "dispatch.wallet_hook(bridge): :api_key required" unless creds[:api_key]
|
|
117
|
+
uri = URI("https://api.bridge.xyz/v0/transfers")
|
|
118
|
+
req = Net::HTTP::Post.new(uri, "content-type" => "application/json", "api-key" => creds[:api_key])
|
|
119
|
+
req.body = { source: creds[:source_wallet], destination: creds[:destination] || accept["payTo"],
|
|
120
|
+
amount: accept["maxAmountRequired"] }.to_json
|
|
121
|
+
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true,
|
|
122
|
+
verify_mode: OpenSSL::SSL::VERIFY_PEER) { |h| h.request(req) }
|
|
123
|
+
raise "dispatch.wallet_hook(bridge): provider #{res.code}" unless res.code.start_with?("2")
|
|
124
|
+
j = (JSON.parse(res.body) rescue {})
|
|
125
|
+
{ provider: "bridge", id: j["id"], accept: accept }.to_json
|
|
126
|
+
else
|
|
127
|
+
raise "dispatch.wallet_sign: unsupported provider #{provider.inspect}"
|
|
128
|
+
end
|
|
129
|
+
end
|
|
55
130
|
end
|