withruntime 0.16.0 → 0.17.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 +4 -4
- data/lib/withruntime/agents.rb +129 -10
- data/lib/withruntime/apps.rb +14 -4
- data/lib/withruntime/ci.rb +39 -5
- data/lib/withruntime/client.rb +14 -0
- data/lib/withruntime/products.rb +30 -4
- data/lib/withruntime/sandbox.rb +2 -2
- data/lib/withruntime/transport.rb +4 -2
- data/lib/withruntime/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1c1a17e5090c78b095eebfac78fff07cbd5222b14ba973cc71c619fec9ed4722
|
|
4
|
+
data.tar.gz: ee609373f8a0c8dbfb12ede50c25930647456031fef41576d411a60e5743dfbb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f4ecd87a36108a2a1856e9e0051d010bdeae9393e5ada0681264d6bf81758afdabd33b3f4148e08a08ca94eb5018d718c2601bbf9f2cc1b7972a4b95886534d2
|
|
7
|
+
data.tar.gz: 555018e2954343260c2f88f6661d800ce8ecbd482f91acfdfe7c5f2e9b96ecd8880dfb2a16ace2c65264fd9d3311a361f398ab3637a257effd1d521e74b0062b
|
data/lib/withruntime/agents.rb
CHANGED
|
@@ -65,7 +65,10 @@ module WithRuntime
|
|
|
65
65
|
# /workspace/repo), +vcpu+ and +memory_mib+ (4 vCPU and 8 GiB unless set; a
|
|
66
66
|
# definition from a snapshot takes neither, InvalidRequestError, since a
|
|
67
67
|
# snapshot keeps its own size), +instructions+, +env+, +secrets+ (names of
|
|
68
|
-
# egress secrets: model keys go here), +
|
|
68
|
+
# egress secrets: model keys go here), +pool+ (1 to 16 Claude accounts,
|
|
69
|
+
# each an egress secret's name: each session takes the one with the most
|
|
70
|
+
# usage left, and the review template moves to the next when one is out
|
|
71
|
+
# of usage), +mcp+ ([{ name:, url: }]), +volumes+
|
|
69
72
|
# ([{ volume:, path: }]), +idle_pause_seconds+, +max_session_seconds+,
|
|
70
73
|
# +max_concurrent+, +max_session_dollars+ (more than 0, at most 10000, 5
|
|
71
74
|
# unless set: the session and every session under it, the root's covering
|
|
@@ -75,7 +78,7 @@ module WithRuntime
|
|
|
75
78
|
# (402), until the next day) and +template+ ("support" or
|
|
76
79
|
# "team").
|
|
77
80
|
class Agents < Product
|
|
78
|
-
attr_reader :sessions, :triggers, :templates, :memory
|
|
81
|
+
attr_reader :sessions, :triggers, :templates, :memory, :asks
|
|
79
82
|
|
|
80
83
|
def initialize(transport)
|
|
81
84
|
super
|
|
@@ -83,7 +86,7 @@ module WithRuntime
|
|
|
83
86
|
@triggers = AgentTriggers.new(transport)
|
|
84
87
|
@templates = AgentTemplates.new
|
|
85
88
|
@memory = AgentMemory.new(transport)
|
|
86
|
-
|
|
89
|
+
@asks = AgentAsks.new(transport)
|
|
87
90
|
end
|
|
88
91
|
|
|
89
92
|
# Makes an agent at version 1. A name already used is a ConflictError
|
|
@@ -297,20 +300,105 @@ module WithRuntime
|
|
|
297
300
|
#
|
|
298
301
|
# A slack or github trigger whose app is not installed for the account is
|
|
299
302
|
# refused +not_installed+ (ConflictError).
|
|
303
|
+
#
|
|
304
|
+
# Each trigger has +rules+, every one optional: +priority+ (-100 to 100:
|
|
305
|
+
# queue order when the agent's maxConcurrent is full, higher first),
|
|
306
|
+
# +max_starts_per_hour+ (a call past it is held for you), +on_ended+
|
|
307
|
+
# ("start", "note" or "ask": what a call does when its key's session has
|
|
308
|
+
# ended), +notify+ ("email" or "webhook": where a held call is told) and,
|
|
309
|
+
# a webhook's only, +key+ ("{cause}": the key of a call that sends none,
|
|
310
|
+
# from its JSON's fields). A call no session can take is held, never
|
|
311
|
+
# dropped: +held+, +start_held+, +dismiss_held+.
|
|
300
312
|
class AgentTriggers < Product
|
|
301
|
-
def create(agent, kind, config = {})
|
|
302
|
-
|
|
313
|
+
def create(agent, kind, config = {}, rules: nil)
|
|
314
|
+
body = { "kind" => kind, "config" => Fields.convert(config.to_h) }
|
|
315
|
+
body["rules"] = Fields.convert(rules.to_h) if rules
|
|
316
|
+
Record.new(@t.json("POST", "/v1/agents/#{seg(agent)}/triggers", body: body))
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
# Its rules, whole, in place: its secret stays. +{}+ is every default.
|
|
320
|
+
def set_rules(id, rules)
|
|
321
|
+
Record.new(@t.json("PATCH", "/v1/agents/triggers/#{seg(id)}", body: { "rules" => Fields.convert(rules.to_h) }))
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
# Calls a webhook trigger as its sender would, by its +secret+, never your
|
|
325
|
+
# key: +outcome+ started, joined (the open session with +key+), noted or
|
|
326
|
+
# held, with +session_id+, +seq+, +call_id+ and +why+. The same
|
|
327
|
+
# +idempotency_key+ is the same call.
|
|
328
|
+
def call(id, secret, text, key: nil, labels: nil, idempotency_key: nil)
|
|
329
|
+
body = Fields.body(text: text, key: key, labels: labels)
|
|
330
|
+
Record.new(@t.json("POST", "/v1/agents/triggers/#{seg(id)}/calls", body: body, bearer: secret,
|
|
331
|
+
key: idempotency_key))
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# The calls it held for you, newest first; +each+ walks every page.
|
|
335
|
+
def held(id, state: nil, cursor: nil, limit: nil)
|
|
336
|
+
held_page(id, { "state" => state, "limit" => limit }, cursor)
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
# Starts a held call: one session with it and every call held for its key
|
|
340
|
+
# since, oldest first. +session+, +joined+ and +calls+.
|
|
341
|
+
def start_held(id, call_id)
|
|
342
|
+
Record.new(@t.json("POST", "/v1/agents/triggers/#{seg(id)}/held/#{seg(call_id)}/start", body: {}))
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
# Dismisses a held call: nothing runs.
|
|
346
|
+
def dismiss_held(id, call_id)
|
|
347
|
+
Record.new(@t.json("POST", "/v1/agents/triggers/#{seg(id)}/held/#{seg(call_id)}/dismiss", body: {}))
|
|
303
348
|
end
|
|
304
349
|
|
|
305
350
|
def list(agent) = @t.json("GET", "/v1/agents/#{seg(agent)}/triggers")["data"].map { |row| Record.new(row) }
|
|
306
351
|
|
|
307
|
-
# Holds this trigger alone
|
|
308
|
-
# +
|
|
352
|
+
# Holds this trigger alone, saying +why+: a webhook's calls are held for
|
|
353
|
+
# you (+held+), never dropped. Nothing else is held.
|
|
309
354
|
def pause(id, why) = Record.new(@t.json("POST", "/v1/agents/triggers/#{seg(id)}/pause", body: { "why" => why }))
|
|
310
355
|
|
|
311
356
|
def resume(id) = Record.new(@t.json("POST", "/v1/agents/triggers/#{seg(id)}/resume", body: {}))
|
|
312
357
|
|
|
313
358
|
def delete(id) = @t.json("DELETE", "/v1/agents/triggers/#{seg(id)}") && nil
|
|
359
|
+
|
|
360
|
+
private
|
|
361
|
+
|
|
362
|
+
def held_page(id, query, cursor)
|
|
363
|
+
body = @t.json("GET", "/v1/agents/triggers/#{seg(id)}/held", query: query.merge("cursor" => cursor))
|
|
364
|
+
Page.new(body["data"].map { |item| Record.new(item) }, body["nextCursor"]) { |after| held_page(id, query, after) }
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
# +runtime.agents.asks+: the account's inbox, the questions its agents'
|
|
369
|
+
# sessions need answered to go on. Each has +why+ it matters, +options+
|
|
370
|
+
# ({label, detail}), a recommended +pick+ and maybe an expiry. Asks of
|
|
371
|
+
# one agent's sessions sharing a memory, with the same +step+, are one
|
|
372
|
+
# row while it is open, and one answer reaches every session asking. +state+: open, answered,
|
|
373
|
+
# withdrawn, expired (no pick) or closed (every asking session ended).
|
|
374
|
+
class AgentAsks < Product
|
|
375
|
+
# The first page, newest first: open asks unless +state+ (one or a list)
|
|
376
|
+
# says otherwise; of one +agent+; carrying every one of +labels+ (who sees
|
|
377
|
+
# an ask). +each+ walks every page.
|
|
378
|
+
def list(state: nil, agent: nil, labels: nil, cursor: nil, limit: nil)
|
|
379
|
+
label = labels&.map { |k, v| "#{k}:#{v}" }
|
|
380
|
+
asks_page({ "state" => state, "agent" => agent, "label" => label, "limit" => limit }, cursor)
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
# One ask, with the sessions asking it.
|
|
384
|
+
def get(id) = Record.new(@t.json("GET", "/v1/agents/asks/#{seg(id)}"))
|
|
385
|
+
|
|
386
|
+
# Answers with an +option+'s label, +text+, or both: every session asking
|
|
387
|
+
# reads it next and wakes. One no longer open is refused +ask_closed+
|
|
388
|
+
# (ConflictError).
|
|
389
|
+
def answer(id, option = nil, text: nil, idempotency_key: nil)
|
|
390
|
+
raise ArgumentError, "an answer is an option's label, text, or both" if option.nil? && text.nil?
|
|
391
|
+
|
|
392
|
+
body = Fields.body(option: option, text: text)
|
|
393
|
+
Record.new(@t.json("POST", "/v1/agents/asks/#{seg(id)}/answer", body: body, key: idempotency_key))
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
private
|
|
397
|
+
|
|
398
|
+
def asks_page(query, cursor)
|
|
399
|
+
body = @t.json("GET", "/v1/agents/asks", query: query.merge("cursor" => cursor))
|
|
400
|
+
Page.new(body["data"].map { |item| Record.new(item) }, body["nextCursor"]) { |after| asks_page(query, after) }
|
|
401
|
+
end
|
|
314
402
|
end
|
|
315
403
|
|
|
316
404
|
# A page of an agent's memory files; +usage+ is the agent's whole memory,
|
|
@@ -376,10 +464,11 @@ module WithRuntime
|
|
|
376
464
|
end
|
|
377
465
|
end
|
|
378
466
|
|
|
379
|
-
# +runtime.agents.templates+: the
|
|
380
|
-
#
|
|
467
|
+
# +runtime.agents.templates+: the harnesses an agent can run, each usable
|
|
468
|
+
# for either use, and the two uses. +review+ is a CI/CD agent step's
|
|
469
|
+
# reviewer, on a +pool+ of Claude accounts.
|
|
381
470
|
#
|
|
382
|
-
# runtime.agents.templates.map(&:key) # claude-code, codex, gemini-cli, claude-agent-sdk, openai-agents-sdk
|
|
471
|
+
# runtime.agents.templates.map(&:key) # claude-code, codex, gemini-cli, claude-agent-sdk, openai-agents-sdk, review, judge
|
|
383
472
|
# runtime.agents.templates["codex"].api_key_secret
|
|
384
473
|
# runtime.agents.templates.uses["support"].session_key
|
|
385
474
|
# runtime.agents.templates.definition("claude-code", use: "support")
|
|
@@ -454,6 +543,36 @@ module WithRuntime
|
|
|
454
543
|
"command" => "python3 ~/agent.py",
|
|
455
544
|
"secrets" => ["OPENAI_API_KEY"]
|
|
456
545
|
}
|
|
546
|
+
},
|
|
547
|
+
"review" => {
|
|
548
|
+
"key" => "review",
|
|
549
|
+
"title" => "Code review",
|
|
550
|
+
"command" => "python3 ~/.runtime-agent/runtime-agent review",
|
|
551
|
+
"apiKeySecret" => "ANTHROPIC_API_KEY",
|
|
552
|
+
"apiKeyHost" => "api.anthropic.com",
|
|
553
|
+
"subscription" => "a token per account: run `claude setup-token` on your own computer (Pro/Max) for each, store each with `runtime secrets set CLAUDE_1` scoped to api.anthropic.com, then name them all in pool (--pool CLAUDE_1 --pool CLAUDE_2)",
|
|
554
|
+
"definition" => {
|
|
555
|
+
"command" => "python3 ~/.runtime-agent/runtime-agent review",
|
|
556
|
+
"pool" => ["ANTHROPIC_API_KEY"],
|
|
557
|
+
"vcpu" => 2,
|
|
558
|
+
"memoryMiB" => 2048,
|
|
559
|
+
"maxSessionSeconds" => 1800
|
|
560
|
+
}
|
|
561
|
+
},
|
|
562
|
+
"judge" => {
|
|
563
|
+
"key" => "judge",
|
|
564
|
+
"title" => "Your rules",
|
|
565
|
+
"command" => "python3 ~/.runtime-agent/runtime-agent judge",
|
|
566
|
+
"apiKeySecret" => "ANTHROPIC_API_KEY",
|
|
567
|
+
"apiKeyHost" => "api.anthropic.com",
|
|
568
|
+
"subscription" => "a token per account, as for review: `runtime secrets set CLAUDE_1` scoped to api.anthropic.com for each, then name them all in pool; its instructions are the rules that block",
|
|
569
|
+
"definition" => {
|
|
570
|
+
"command" => "python3 ~/.runtime-agent/runtime-agent judge",
|
|
571
|
+
"pool" => ["ANTHROPIC_API_KEY"],
|
|
572
|
+
"vcpu" => 2,
|
|
573
|
+
"memoryMiB" => 2048,
|
|
574
|
+
"maxSessionSeconds" => 1800
|
|
575
|
+
}
|
|
457
576
|
}
|
|
458
577
|
},
|
|
459
578
|
"uses" => {
|
data/lib/withruntime/apps.rb
CHANGED
|
@@ -158,13 +158,17 @@ module WithRuntime
|
|
|
158
158
|
# memory_mib, port, health_path, cdn, live_version, previous_version, error
|
|
159
159
|
# ({code, message} or nil), line (while a copy waits for room: lane,
|
|
160
160
|
# place, ahead by lane and words; nil otherwise), created_at, updated_at,
|
|
161
|
-
# deleted_at, and release_watch (alert, the default; rollback; or off).
|
|
162
|
-
# also has
|
|
161
|
+
# deleted_at, and release_watch (alert, the default; rollback; or off). An App
|
|
162
|
+
# also has release_stages (at_once, the default, or staged), release_soak_seconds
|
|
163
|
+
# (60 to 3600) and staged_release (its staged release under way or its last:
|
|
164
|
+
# version, from, state rolling/back/done/failed/ended, stage, stages, copies,
|
|
165
|
+
# of, soak_seconds, detail, started_at, updated_at; nil before its first). A
|
|
166
|
+
# Function also has sleep_after_ms (250 to 5000).
|
|
163
167
|
#
|
|
164
168
|
# A deploy: id, version, url and state (building, warming, switching, live,
|
|
165
169
|
# failed, superseded); a failed or superseded one has error with code
|
|
166
170
|
# (deploy_build_failed, deploy_health_failed, deploy_stop_failed,
|
|
167
|
-
# deploy_switch_failed, deploy_superseded), message, hint and last_output. A version: version,
|
|
171
|
+
# deploy_stage_failed, deploy_switch_failed, deploy_superseded), message, hint and last_output. A version: version,
|
|
168
172
|
# live, previous, saved, deploy (its deploy's id) and build (nil for an
|
|
169
173
|
# image). A copy: id, version,
|
|
170
174
|
# region, state (starting, serving, draining, frozen, parked) and since.
|
|
@@ -186,7 +190,13 @@ module WithRuntime
|
|
|
186
190
|
# nothing cached), +name:+, +labels:+, +release_watch:+ ("alert" by default: tell the
|
|
187
191
|
# owners when a new version gets clearly worse in its first 15 minutes; "rollback" also
|
|
188
192
|
# goes back to the previous version; "off" does neither), and for an App
|
|
189
|
-
# +min_copies:+ (0).
|
|
193
|
+
# +min_copies:+ (0). An App also takes +release_stages:+ ("at_once" by default;
|
|
194
|
+
# "staged" starts one copy of a new version beside the live version's, then
|
|
195
|
+
# about 10%, 50% and all of them, each stage judged on its visitors' errors
|
|
196
|
+
# and speed before the next; a stage that does worse goes back, its copies
|
|
197
|
+
# drain and the deploy fails with deploy_stage_failed, the live version never
|
|
198
|
+
# stopping) and +release_soak_seconds:+ (60 to 3600, default 300: how long
|
|
199
|
+
# each stage runs once its copies serve). A Function leaves +min_copies+ out, zero included.
|
|
190
200
|
# A Function also takes +sleep_after_ms:+ (250 to 5000, default 1000): at
|
|
191
201
|
# most how long a copy stays awake after its last request ends; awake time
|
|
192
202
|
# with no request in flight is not billed. An App is refused it.
|
data/lib/withruntime/ci.rb
CHANGED
|
@@ -10,7 +10,7 @@ module WithRuntime
|
|
|
10
10
|
# job = runtime.ci.checks.get(check.id).jobs.first
|
|
11
11
|
# puts runtime.ci.jobs.get(job.id).log
|
|
12
12
|
class CI < Product
|
|
13
|
-
attr_reader :repositories, :checks, :jobs, :tests, :flakes
|
|
13
|
+
attr_reader :repositories, :checks, :jobs, :tests, :flakes, :releases
|
|
14
14
|
|
|
15
15
|
def initialize(transport)
|
|
16
16
|
super
|
|
@@ -19,13 +19,15 @@ module WithRuntime
|
|
|
19
19
|
@jobs = CIJobs.new(transport)
|
|
20
20
|
@tests = CITests.new(transport)
|
|
21
21
|
@flakes = CIFlakes.new(transport)
|
|
22
|
+
@releases = CIReleases.new(transport)
|
|
22
23
|
end
|
|
23
24
|
end
|
|
24
25
|
|
|
25
26
|
# +runtime.ci.repositories+: the repositories whose checks run.
|
|
26
27
|
#
|
|
27
28
|
# A repository: id, name, labels, repository (owner/name), production_branch,
|
|
28
|
-
# config_path, installation, github_repository_id,
|
|
29
|
+
# config_path, installation, github_repository_id, undo_culprits (whether a
|
|
30
|
+
# red production branch's culprit is undone: set_undo), state (connected or
|
|
29
31
|
# disconnected), error, created_at, updated_at and deleted_at.
|
|
30
32
|
class CIRepositories < Product
|
|
31
33
|
# Connects +repository+ ("owner/name"), read through Runtime's GitHub App.
|
|
@@ -45,6 +47,15 @@ module WithRuntime
|
|
|
45
47
|
page("/v1/ci/repositories", { "name" => name, "label" => label, "state" => state, "limit" => limit, "cursor" => cursor })
|
|
46
48
|
end
|
|
47
49
|
|
|
50
|
+
# Turns on (or off) the undo of a red production branch's culprit: when
|
|
51
|
+
# main's check fails, CI/CD finds the first commit that fails it and opens
|
|
52
|
+
# a pull request reverting it, which lands once its own check passes. Off
|
|
53
|
+
# until set; only an owner's or admin's full key sets it.
|
|
54
|
+
def set_undo(id, on, idempotency_key: nil)
|
|
55
|
+
Record.new(@t.json("POST", "/v1/ci/repositories/#{seg(id)}:update", body: { "undoCulprits" => on },
|
|
56
|
+
key: idempotency_key))
|
|
57
|
+
end
|
|
58
|
+
|
|
48
59
|
# Stops running its checks; the checks already run stay readable.
|
|
49
60
|
def disconnect(id, idempotency_key: nil)
|
|
50
61
|
Record.new(@t.json("DELETE", "/v1/ci/repositories/#{seg(id)}", key: idempotency_key))
|
|
@@ -68,15 +79,24 @@ module WithRuntime
|
|
|
68
79
|
|
|
69
80
|
# The check with its +jobs+ in plan order (no log).
|
|
70
81
|
def get(id) = Record.new(@t.json("GET", "/v1/ci/checks/#{seg(id)}"))
|
|
82
|
+
|
|
83
|
+
# Cancels a queued or running check: its jobs end and its machines are let
|
|
84
|
+
# go. A check that has ended is answered as it is.
|
|
85
|
+
def cancel(id) = Record.new(@t.json("POST", "/v1/ci/checks/#{seg(id)}:cancel"))
|
|
86
|
+
|
|
87
|
+
# Runs a check again, as GitHub's Re-run does: a new check of the same
|
|
88
|
+
# commit, trusted only as the commit's last check was.
|
|
89
|
+
def rerun(id) = Record.new(@t.json("POST", "/v1/ci/checks/#{seg(id)}:rerun"))
|
|
71
90
|
end
|
|
72
91
|
|
|
73
92
|
# +runtime.ci.jobs+: one command of a check, run in sandboxes.
|
|
74
93
|
class CIJobs < Product
|
|
75
94
|
# The job with its +log+ (the tail, at most 256 KiB). A failed job's
|
|
76
95
|
# sandbox_ids may be kept paused: wake one or exec in it with
|
|
77
|
-
# +runtime.sandboxes+.
|
|
78
|
-
# (pass or block), summary, findings (kind,
|
|
79
|
-
#
|
|
96
|
+
# +runtime.sandboxes+. An agent step's job (+review:+ is its short form)
|
|
97
|
+
# has its +review+: the verdict (pass or block), summary, findings (kind,
|
|
98
|
+
# file, line, what, fix), notes, and the agent, the model the step asked
|
|
99
|
+
# for (nil when none) and the session that gave it; nil otherwise.
|
|
80
100
|
def get(id) = Record.new(@t.json("GET", "/v1/ci/jobs/#{seg(id)}"))
|
|
81
101
|
end
|
|
82
102
|
|
|
@@ -119,4 +139,18 @@ module WithRuntime
|
|
|
119
139
|
Record.new(@t.json("POST", "/v1/ci/flakes/#{seg(id)}:update", body: body, key: idempotency_key))
|
|
120
140
|
end
|
|
121
141
|
end
|
|
142
|
+
|
|
143
|
+
# +runtime.ci.releases+: the steps each release took, as it took them: a
|
|
144
|
+
# migration's copy test or apply, a deploy ref moved, a server's canary or
|
|
145
|
+
# release, the website's deploy or promote, or a wait and why. Only
|
|
146
|
+
# repositories whose releases CI/CD runs have any.
|
|
147
|
+
#
|
|
148
|
+
# A step: id, repository_id, commit, source (mover, or the deploy target
|
|
149
|
+
# that walked it), host (or nil), line and at.
|
|
150
|
+
class CIReleases < Product
|
|
151
|
+
# Steps, newest first; +commit:+ keeps one release's.
|
|
152
|
+
def list(repository_id: nil, commit: nil, limit: nil, cursor: nil)
|
|
153
|
+
page("/v1/ci/releases", { "repositoryId" => repository_id, "commit" => commit, "limit" => limit, "cursor" => cursor })
|
|
154
|
+
end
|
|
155
|
+
end
|
|
122
156
|
end
|
data/lib/withruntime/client.rb
CHANGED
|
@@ -96,6 +96,20 @@ module WithRuntime
|
|
|
96
96
|
# The account's credit, holds, free time and per-resource charges.
|
|
97
97
|
def usage = Record.new(@transport.json("GET", "/v1/usage"))
|
|
98
98
|
|
|
99
|
+
# One page of settled usage for every product the key sees: settled at
|
|
100
|
+
# +since+ or later and before +until+ (UTC, ending in Z). Follow
|
|
101
|
+
# +next_cursor+ with the same range until it is nil; concatenate each
|
|
102
|
+
# page's +csv+ (the header is on the first only). Money and quantities are
|
|
103
|
+
# exact decimal strings. +final_through+: no row settled before it can
|
|
104
|
+
# still appear or change, so the range is final once it reaches +until+.
|
|
105
|
+
# A row's +final+ is true once its resource is deleted and can be charged
|
|
106
|
+
# nothing more.
|
|
107
|
+
def usage_export(since:, until:, cursor: nil, limit: nil)
|
|
108
|
+
# until is a Ruby keyword: its argument is read through the binding.
|
|
109
|
+
query = { "since" => since, "until" => binding.local_variable_get(:until), "cursor" => cursor, "limit" => limit }
|
|
110
|
+
Record.new(@transport.json("GET", "/v1/usage/export", query: query))
|
|
111
|
+
end
|
|
112
|
+
|
|
99
113
|
# Calls any API path with this client's key, retries, idempotency keys and
|
|
100
114
|
# errors, and returns the parsed JSON answer. For an endpoint newer than this SDK.
|
|
101
115
|
def request(method, path, query: nil, body: nil)
|
data/lib/withruntime/products.rb
CHANGED
|
@@ -81,7 +81,20 @@ module WithRuntime
|
|
|
81
81
|
def backup(id, name: nil, labels: nil, retention_days: nil, wait: 60, idempotency_key: nil)
|
|
82
82
|
Record.new(@t.json("POST", "/v1/volumes/#{seg(id)}:backup", body: Fields.body(name: name, labels: labels, retention_days: retention_days), wait: wait, key: idempotency_key))
|
|
83
83
|
end
|
|
84
|
-
|
|
84
|
+
# Automatic backups on or off and the days a daily one is kept; or a
|
|
85
|
+
# schedule of the volume's own: every_minutes (15 or more) or a five-field
|
|
86
|
+
# UTC cron, keeping the keep counts (last, hourly, daily, weekly, monthly,
|
|
87
|
+
# yearly). no_schedule: true goes back to daily backups.
|
|
88
|
+
def set_backup_policy(id, daily: nil, retention_days: nil, every_minutes: nil, cron: nil, keep: nil, no_schedule: false)
|
|
89
|
+
raise ArgumentError, "Give one of every_minutes, cron and no_schedule." if [every_minutes, cron, (no_schedule || nil)].compact.size > 1
|
|
90
|
+
|
|
91
|
+
body = Fields.body(daily: daily, retention_days: retention_days, keep: keep)
|
|
92
|
+
if every_minutes then body["schedule"] = { "everyMinutes" => every_minutes }
|
|
93
|
+
elsif cron then body["schedule"] = { "cron" => cron }
|
|
94
|
+
elsif no_schedule then body["schedule"] = nil
|
|
95
|
+
end
|
|
96
|
+
Record.new(@t.json("POST", "/v1/volumes/#{seg(id)}:backup-policy", body: body))
|
|
97
|
+
end
|
|
85
98
|
def backups(volume_id: nil, state: nil, limit: nil) = page("/v1/volume-backups", { "volumeId" => volume_id, "state" => state, "limit" => limit })
|
|
86
99
|
def get_backup(id) = Record.new(@t.json("GET", "/v1/volume-backups/#{seg(id)}"))
|
|
87
100
|
def delete_backup(id) = Record.new(@t.json("POST", "/v1/volume-backups/#{seg(id)}:delete", body: {}))
|
|
@@ -146,8 +159,20 @@ module WithRuntime
|
|
|
146
159
|
Record.new(@t.json("PUT", "/v1/egress-secrets/#{seg(name)}/rules", body: body))
|
|
147
160
|
end
|
|
148
161
|
|
|
149
|
-
# Names, hosts and placeholders. Never values.
|
|
150
|
-
|
|
162
|
+
# Names, hosts and placeholders. Never values. With +limit:+ (1 to 100)
|
|
163
|
+
# or +after:+, one page by name: the secrets named after +after+; the next
|
|
164
|
+
# page's +after+ is this one's last name. Without, every secret.
|
|
165
|
+
def list(limit: nil, after: nil)
|
|
166
|
+
secrets = []
|
|
167
|
+
start = after
|
|
168
|
+
loop do
|
|
169
|
+
page = @t.json("GET", "/v1/egress-secrets", query: { "limit" => limit, "after" => start })
|
|
170
|
+
secrets.concat(page["secrets"].map { |secret| Record.new(secret) })
|
|
171
|
+
return secrets if limit || after || !page["next"]
|
|
172
|
+
|
|
173
|
+
start = page["next"]
|
|
174
|
+
end
|
|
175
|
+
end
|
|
151
176
|
|
|
152
177
|
# Erases a secret; its placeholder stops working.
|
|
153
178
|
def delete(name)
|
|
@@ -377,7 +402,8 @@ module WithRuntime
|
|
|
377
402
|
|
|
378
403
|
# Replaces the rules: +internet:+, +allow:+ (example.com, *.example.com, an
|
|
379
404
|
# address or a CIDR range), +deny:+, and +connect:+ (host:port pairs beyond
|
|
380
|
-
# 80 and 443; paid accounts only).
|
|
405
|
+
# 80 and 443; paid accounts only). +internet: false+ with +allow:+ is
|
|
406
|
+
# allow-list only, stored as +internet: true+ with that list.
|
|
381
407
|
def set(internet:, allow: nil, deny: nil, connect: nil)
|
|
382
408
|
body = { "internet" => internet, "allow" => allow, "deny" => deny, "connect" => connect }.compact
|
|
383
409
|
Record.new(@sandbox.transport.json("PUT", @sandbox.path("/network"), body: body))
|
data/lib/withruntime/sandbox.rb
CHANGED
|
@@ -214,8 +214,8 @@ module WithRuntime
|
|
|
214
214
|
end
|
|
215
215
|
|
|
216
216
|
# Starts copies of this sandbox as it is now (files, memory, running
|
|
217
|
-
# processes), each its own sandbox, on
|
|
218
|
-
# run. One copy without +count+; an array with it. If a copy fails, the
|
|
217
|
+
# processes), each its own sandbox, on its server while it has room and on
|
|
218
|
+
# another when it is full, answered once they run. One copy without +count+; an array with it. If a copy fails, the
|
|
219
219
|
# error's +details["startedSandboxIds"]+ names the copies that did start.
|
|
220
220
|
# +funding+ is accepted and ignored: copies run on free usage first, then credit.
|
|
221
221
|
# +secrets: [name, ...]+ is the egress secrets the copies may use, by name:
|
|
@@ -378,8 +378,10 @@ module WithRuntime
|
|
|
378
378
|
|
|
379
379
|
# Sends a call, retrying as the rules say, and returns the response once it
|
|
380
380
|
# succeeded. With a block, the body is streamed to it.
|
|
381
|
+
# +bearer+ is sent instead of the API key: a webhook trigger's secret,
|
|
382
|
+
# which is all its calls carry.
|
|
381
383
|
def send_call(http, method, path, query: nil, body: nil, raw: nil, accept: "application/json", wait: nil,
|
|
382
|
-
key: nil, no_retry: false, room: nil, timeout: nil, cancel: nil, &stream)
|
|
384
|
+
key: nil, no_retry: false, room: nil, timeout: nil, cancel: nil, bearer: nil, &stream)
|
|
383
385
|
write = method != "GET"
|
|
384
386
|
key = write ? (key || SecureRandom.uuid) : nil
|
|
385
387
|
# room: nil for a call with no room wait; 0 for a create, wake, restart
|
|
@@ -400,7 +402,7 @@ module WithRuntime
|
|
|
400
402
|
raise connection_error(true, write, key) if left <= 0
|
|
401
403
|
|
|
402
404
|
request = Net::HTTPGenericRequest.new(method, !payload.nil?, true, target(path, query))
|
|
403
|
-
request["Authorization"] = "Bearer #{api_key}"
|
|
405
|
+
request["Authorization"] = "Bearer #{bearer || api_key}"
|
|
404
406
|
request["Accept"] = accept
|
|
405
407
|
request["X-Runtime-Client"] = @client
|
|
406
408
|
request["User-Agent"] = "withruntime-ruby/#{VERSION}"
|
data/lib/withruntime/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: withruntime
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.17.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Runtime
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-25 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: 'Linux sandboxes for agents, and every other Runtime Cloud product: streaming
|
|
14
14
|
commands and terminals, files and watches, images, durable volumes and backups,
|