k9o 1.2.0 → 1.3.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/CHANGELOG.md +35 -0
- data/README.md +90 -52
- data/lib/k9o/audit.rb +12 -14
- data/lib/k9o/client.rb +3 -5
- data/lib/k9o/compute.rb +32 -15
- data/lib/k9o/functions.rb +48 -9
- data/lib/k9o/gate.rb +9 -18
- data/lib/k9o/http_transport.rb +10 -5
- data/lib/k9o/kibo.rb +3 -3
- data/lib/k9o/me.rb +27 -87
- data/lib/k9o/org.rb +81 -68
- data/lib/k9o/orgs.rb +1 -0
- data/lib/k9o/policies.rb +25 -6
- data/lib/k9o/projects.rb +43 -8
- data/lib/k9o/realtime.rb +13 -35
- data/lib/k9o/region.rb +20 -18
- data/lib/k9o/studio.rb +21 -11
- data/lib/k9o/url_grammar.rb +46 -20
- data/lib/k9o/vault.rb +76 -80
- data/lib/k9o/version.rb +1 -1
- data/sig/k9o/gate.rbs +7 -6
- data/sig/k9o/region.rbs +14 -3
- 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: fddde5f475c121ba44d0e069107845e30e0104613b717edb809813d152e2882a
|
|
4
|
+
data.tar.gz: af7564a794aa6c40150a09bf38800dad60e092afb26dfdf8e918524124b4a68c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c3735d8f97f9a0eacc9fa02dcf897ac0182056c606179b7cdcc104cc32ebba9ddb14b433c7522e30e4a10deadbd8b13022de1db262f4abccb2912b9edb05ac4a
|
|
7
|
+
data.tar.gz: f4f833edfa3f6fc6ea2d75efccfb85f09c902e0e2fb79cc43c8edad7db02bcd3817832087227005944fb4714f198ac4b0dd6f88134248698c9d2b4e3674aa1a0
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
# Changelog — k9o (Ruby)
|
|
2
2
|
|
|
3
|
+
## 1.3.0 — 2026-10-26
|
|
4
|
+
|
|
5
|
+
Every method now matches the gateway.
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- `projects.create` takes `display_name:` (and optional `slug:`, `region:`, `custody_mode:`, ...);
|
|
10
|
+
`projects.get` and `projects.update` use the projects routes.
|
|
11
|
+
- `functions.deploy` takes `name:`, `runtime:`, `tier:` (`gateway` or `sandbox`), `artifact_key:`, `version:` and `description:`.
|
|
12
|
+
Inline code is gone.
|
|
13
|
+
- `functions.invoke` sends the payload as `{payload}`.
|
|
14
|
+
- `policies.create`, `update` and `validate` take a `policy:` document (plus optional `rules:`, `editor_mode:`);
|
|
15
|
+
`policies.list` takes only `status:`.
|
|
16
|
+
- `audit.query` filters are `event_type:`, `actor_id:`, `after:`, `before:`, `limit:` and `cursor:`.
|
|
17
|
+
- Org roles, identity providers and default model use the current routes; `smtp.set` uses `password_ref:` and `from_address:`;
|
|
18
|
+
`smtp.test` takes `to:` and `subject:`.
|
|
19
|
+
- `me.api_tokens.create` takes `label:` and `scope_manifest:` (and `ttl_seconds:`); `me.sessions.revoke` and `revoke_all` use new routes.
|
|
20
|
+
- `compute.llms.invoke` settings are sent flat, and the result has `content` and `usage`.
|
|
21
|
+
- `studio.workflows.trigger` sends the context as `triggerContext`.
|
|
22
|
+
- `gate.sign_out` requires the refresh token.
|
|
23
|
+
- The API host comes from the base URL: any `<org>.api.<apex>` host works and no apex is hardcoded.
|
|
24
|
+
`https://api.k9o.io` is only the default.
|
|
25
|
+
|
|
26
|
+
### Removed
|
|
27
|
+
|
|
28
|
+
- `gate.verify_session`, `gate.hosted`, `realtime.replay` and `realtime.subscribe_poll`.
|
|
29
|
+
- `me.notifications`, `me.audit_log` and `me.data_export`.
|
|
30
|
+
- `org.groups.create`, `org.groups.delete`, `org.members.get` and `org.smtp.get`.
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- `functions.upload_artifact(bytes, kind)` uploads a build and returns the `artifactKey` for `deploy`.
|
|
35
|
+
- `UrlGrammar.parse_api_host` reads the org, region and apex from any gateway host.
|
|
36
|
+
- A contract test checks every method against the gateway's API description.
|
|
37
|
+
|
|
3
38
|
## 1.2.0 — 2026-10-25
|
|
4
39
|
|
|
5
40
|
- The README now introduces the Kilimanjaro products (Keep, Pulse, Gate, Kibo). No change to the public API.
|
data/README.md
CHANGED
|
@@ -2,7 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
The Kilimanjaro platform SDK for Ruby.
|
|
4
4
|
|
|
5
|
-
Kilimanjaro is
|
|
5
|
+
Kilimanjaro is the AI-native foundation for building critical applications.
|
|
6
|
+
Its products are:
|
|
7
|
+
|
|
8
|
+
- Keep: AI Security and Governance
|
|
9
|
+
- Pulse: Data Intelligence
|
|
10
|
+
- Gate: Customer Identity and Access Management
|
|
11
|
+
- Kibo: Keys and Secrets Management
|
|
6
12
|
|
|
7
13
|
## Contents
|
|
8
14
|
|
|
@@ -97,27 +103,28 @@ The client needs a key and, for project calls, an org and project.
|
|
|
97
103
|
| Option | What it does |
|
|
98
104
|
|---|---|
|
|
99
105
|
| `api_key:` | Bearer token sent with every request. |
|
|
100
|
-
| `org_slug:` | Organization.
|
|
106
|
+
| `org_slug:` | Organization. The SDK builds `https://<org_slug>.<host>` from the platform host. |
|
|
101
107
|
| `project_slug:` | Project. Required by every project-scoped namespace. |
|
|
102
108
|
|
|
103
109
|
See [client options](#client-options) for every option.
|
|
104
110
|
|
|
105
111
|
### Choose a host
|
|
106
112
|
|
|
107
|
-
Pick one of two forms.
|
|
113
|
+
The API host always comes from the base URL. Pick one of two forms.
|
|
108
114
|
|
|
109
|
-
- Default
|
|
110
|
-
-
|
|
115
|
+
- Default public host: pass the org slug, project slug and key.
|
|
116
|
+
- Any other platform, internal or air-gapped host: pass its `base_url` as well.
|
|
111
117
|
|
|
112
118
|
```ruby
|
|
113
119
|
k9o = K9o.create_client(
|
|
114
|
-
base_url: "https://
|
|
120
|
+
base_url: "https://api.corp.internal",
|
|
121
|
+
org_slug: "acme",
|
|
115
122
|
project_slug: "task-app",
|
|
116
123
|
api_key: ENV.fetch("K9O_API_KEY")
|
|
117
124
|
)
|
|
118
125
|
```
|
|
119
126
|
|
|
120
|
-
|
|
127
|
+
This client talks to `https://acme.api.corp.internal`. See [regional hosts](#regional-hosts) for the rules.
|
|
121
128
|
|
|
122
129
|
## Running queries
|
|
123
130
|
|
|
@@ -179,9 +186,9 @@ Collection not found
|
|
|
179
186
|
| Option | What it does |
|
|
180
187
|
|---|---|
|
|
181
188
|
| `api_key:` | Bearer token sent with every request. |
|
|
182
|
-
| `org_slug:` | Organization.
|
|
189
|
+
| `org_slug:` | Organization. Prefixed to a platform host (`api.<apex>`) to form the org host. |
|
|
183
190
|
| `project_slug:` | Project. Required by every project-scoped namespace. |
|
|
184
|
-
| `base_url:` | Scheme and host only. Defaults to `https://api.k9o.io`. |
|
|
191
|
+
| `base_url:` | Scheme and host only. Defaults to the public host `https://api.k9o.io`. |
|
|
185
192
|
| `default_headers:` | Extra headers added to every request. |
|
|
186
193
|
| `http_client:` | A `Net::HTTP`-compatible object used instead of the default connection. |
|
|
187
194
|
|
|
@@ -236,9 +243,18 @@ Chain filters before `execute`. Values are matched against one column.
|
|
|
236
243
|
| Method | What it does |
|
|
237
244
|
|---|---|
|
|
238
245
|
| `k9o.projects.list` | List projects you can access. |
|
|
239
|
-
| `k9o.projects.create` | Create a project from `slug`
|
|
246
|
+
| `k9o.projects.create` | Create a project from `display_name:` (optional `slug:`, `region:`, `custody_mode:`, more). |
|
|
240
247
|
| `k9o.projects.get` | Read a project by slug. |
|
|
241
|
-
| `k9o.projects.update` | Change a project
|
|
248
|
+
| `k9o.projects.update` | Change a project by slug: `display_name:`, `slug:` (rename), `description:`, `region:`, more. |
|
|
249
|
+
|
|
250
|
+
```ruby
|
|
251
|
+
project = k9o.projects.create(display_name: "Task app", slug: "task-app")
|
|
252
|
+
puts project.data["slug"]
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
```text
|
|
256
|
+
task-app
|
|
257
|
+
```
|
|
242
258
|
|
|
243
259
|
#### Gate (end-user sign-in)
|
|
244
260
|
|
|
@@ -248,11 +264,9 @@ Chain filters before `execute`. Values are matched against one column.
|
|
|
248
264
|
|---|---|
|
|
249
265
|
| `k9o.gate.sign_up` | Create an end user from email and password. |
|
|
250
266
|
| `k9o.gate.sign_in` | Sign in with email and password. |
|
|
251
|
-
| `k9o.gate.sign_out` | Revoke
|
|
267
|
+
| `k9o.gate.sign_out` | Revoke a session. Requires `refresh_token:`. |
|
|
252
268
|
| `k9o.gate.whoami` | Return the signed-in identity. |
|
|
253
269
|
| `k9o.gate.refresh_token` | Exchange a refresh token for a new access token. |
|
|
254
|
-
| `k9o.gate.verify_session` | Check that the current session is valid. |
|
|
255
|
-
| `k9o.gate.hosted` | Return the hosted sign-in `signInUrl` and `rpId`. |
|
|
256
270
|
|
|
257
271
|
```ruby
|
|
258
272
|
session = k9o.gate.sign_in(email: "ada@example.com", password: "correct-horse")
|
|
@@ -303,11 +317,32 @@ signed = avatars.create_signed_url("ada.png", expires_in: 300)
|
|
|
303
317
|
|
|
304
318
|
| Method | What it does |
|
|
305
319
|
|---|---|
|
|
306
|
-
| `k9o.functions.
|
|
307
|
-
| `k9o.functions.
|
|
320
|
+
| `k9o.functions.upload_artifact` | Upload the build (`bytes`, `kind` of `"wasm"` or `"tar"`). Returns `artifactKey`. |
|
|
321
|
+
| `k9o.functions.deploy` | Deploy from `name:`, `runtime:`, `tier:`, `artifact_key:`, `version:`, optional `description:`. |
|
|
322
|
+
| `k9o.functions.list` | List functions. Options: `status:`, `limit:`, `cursor:`. |
|
|
308
323
|
| `k9o.functions.get` | Read one function by name. |
|
|
309
324
|
| `k9o.functions.delete` | Remove a function. |
|
|
310
|
-
| `k9o.functions.invoke` | Run a function
|
|
325
|
+
| `k9o.functions.invoke` | Run a function. The `payload` argument is sent as `{payload}`. |
|
|
326
|
+
|
|
327
|
+
`tier:` is `"gateway"` (WebAssembly, `"wasm"`) or `"sandbox"` (OCI image archive, `"tar"`).
|
|
328
|
+
|
|
329
|
+
```ruby
|
|
330
|
+
artifact = k9o.functions.upload_artifact(File.binread("hello.wasm"), "wasm")
|
|
331
|
+
|
|
332
|
+
k9o.functions.deploy(
|
|
333
|
+
name: "hello",
|
|
334
|
+
runtime: "bun",
|
|
335
|
+
tier: "gateway",
|
|
336
|
+
artifact_key: artifact.data["artifactKey"],
|
|
337
|
+
version: "1.0.0"
|
|
338
|
+
)
|
|
339
|
+
result = k9o.functions.invoke("hello", { "name" => "Ada" })
|
|
340
|
+
puts result.data["greeting"]
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
```text
|
|
344
|
+
Hello, Ada
|
|
345
|
+
```
|
|
311
346
|
|
|
312
347
|
#### Migrations
|
|
313
348
|
|
|
@@ -331,11 +366,11 @@ signed = avatars.create_signed_url("ada.png", expires_in: 300)
|
|
|
331
366
|
| Method | What it does |
|
|
332
367
|
|---|---|
|
|
333
368
|
| `k9o.policies.current` | Read the published bundle. |
|
|
334
|
-
| `k9o.policies.list` | List bundles. |
|
|
335
|
-
| `k9o.policies.create` | Create a bundle from `
|
|
336
|
-
| `k9o.policies.validate` | Check rules without saving. |
|
|
369
|
+
| `k9o.policies.list` | List bundles. Only option: `status:`. |
|
|
370
|
+
| `k9o.policies.create` | Create a bundle from a `policy:` document (optional `rules:`, `editor_mode:`). |
|
|
371
|
+
| `k9o.policies.validate` | Check a `policy:` document (and optional `rules:`) without saving. |
|
|
337
372
|
| `k9o.policies.get` | Read one bundle. |
|
|
338
|
-
| `k9o.policies.update` |
|
|
373
|
+
| `k9o.policies.update` | Replace a bundle's `policy:` (optional `rules:`, `editor_mode:`). |
|
|
339
374
|
| `k9o.policies.delete` | Remove a bundle. |
|
|
340
375
|
| `k9o.policies.publish` | Make a bundle current. |
|
|
341
376
|
|
|
@@ -345,7 +380,9 @@ signed = avatars.create_signed_url("ada.png", expires_in: 300)
|
|
|
345
380
|
|
|
346
381
|
| Method | What it does |
|
|
347
382
|
|---|---|
|
|
348
|
-
| `k9o.audit.query` |
|
|
383
|
+
| `k9o.audit.query` | Filter by `event_type:`, `actor_id:`, `after:`, `before:`, `limit:`, `cursor:`. |
|
|
384
|
+
|
|
385
|
+
`after:` and `before:` are ISO 8601 timestamps.
|
|
349
386
|
|
|
350
387
|
#### AI embeddings
|
|
351
388
|
|
|
@@ -364,7 +401,18 @@ signed = avatars.create_signed_url("ada.png", expires_in: 300)
|
|
|
364
401
|
| `k9o.compute.llms.list` | List available models. |
|
|
365
402
|
| `k9o.compute.llms.read` | Read one model. |
|
|
366
403
|
| `k9o.compute.llms.invoke` | Send a prompt and get the reply. |
|
|
367
|
-
| `k9o.compute.llms.invoke_stream` | Stream the reply
|
|
404
|
+
| `k9o.compute.llms.invoke_stream` | Stream the reply chunk by chunk (returns an `Enumerator`). |
|
|
405
|
+
|
|
406
|
+
Pass settings in `params:` (`max_tokens`, `temperature`, `usage_type`). The result `data` has `content` and `usage`.
|
|
407
|
+
|
|
408
|
+
```ruby
|
|
409
|
+
reply = k9o.compute.llms.invoke("llama-3-8b", prompt: "Say hi", params: { max_tokens: 20 })
|
|
410
|
+
puts "#{reply.data['content']} #{reply.data['usage']['totalTokens']}"
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
```text
|
|
414
|
+
Hi there! 14
|
|
415
|
+
```
|
|
368
416
|
|
|
369
417
|
#### Realtime
|
|
370
418
|
|
|
@@ -372,9 +420,7 @@ signed = avatars.create_signed_url("ada.png", expires_in: 300)
|
|
|
372
420
|
|
|
373
421
|
| Method | What it does |
|
|
374
422
|
|---|---|
|
|
375
|
-
| `k9o.realtime.subscribe` | Stream live events (returns an `Enumerator`). |
|
|
376
|
-
| `k9o.realtime.replay` | Read past events from a sequence number. |
|
|
377
|
-
| `k9o.realtime.subscribe_poll` | Poll for events from a sequence number. |
|
|
423
|
+
| `k9o.realtime.subscribe` | Stream live events over SSE (returns an `Enumerator`). |
|
|
378
424
|
|
|
379
425
|
`K9o::Realtime::Topic` builds topic names, for example `Topic.collection("tasks")`.
|
|
380
426
|
|
|
@@ -391,7 +437,7 @@ signed = avatars.create_signed_url("ada.png", expires_in: 300)
|
|
|
391
437
|
| `k9o.studio.workflows.read` | Read one workflow. |
|
|
392
438
|
| `k9o.studio.workflows.update` | Change a workflow. |
|
|
393
439
|
| `k9o.studio.workflows.delete` | Delete a workflow. |
|
|
394
|
-
| `k9o.studio.workflows.trigger` | Start a run. |
|
|
440
|
+
| `k9o.studio.workflows.trigger` | Start a run, with an optional context hash. |
|
|
395
441
|
| `k9o.studio.workflows.runs.list` | List a workflow's runs. |
|
|
396
442
|
| `k9o.studio.workflows.runs.read` | Read one run. |
|
|
397
443
|
| `k9o.studio.workflows.runs.cancel` | Cancel a run. |
|
|
@@ -415,19 +461,13 @@ signed = avatars.create_signed_url("ada.png", expires_in: 300)
|
|
|
415
461
|
| Method | What it does |
|
|
416
462
|
|---|---|
|
|
417
463
|
| `k9o.me.profile.get` | Read the profile. |
|
|
418
|
-
| `k9o.me.profile.update` | Change
|
|
464
|
+
| `k9o.me.profile.update` | Change `display_name:`, `locale:` or `timezone:` (only those given). |
|
|
419
465
|
| `k9o.me.sessions.list` | List active sessions. |
|
|
420
466
|
| `k9o.me.sessions.revoke` | End one session. |
|
|
421
467
|
| `k9o.me.sessions.revoke_all` | End all sessions. |
|
|
422
468
|
| `k9o.me.api_tokens.list` | List API tokens. |
|
|
423
|
-
| `k9o.me.api_tokens.create` | Create
|
|
469
|
+
| `k9o.me.api_tokens.create` | Create a token from `label:` and `scope_manifest:` (optional `ttl_seconds:`). |
|
|
424
470
|
| `k9o.me.api_tokens.delete` | Delete an API token. |
|
|
425
|
-
| `k9o.me.notifications.list` | List notifications. |
|
|
426
|
-
| `k9o.me.notifications.mark_read` | Mark one as read. |
|
|
427
|
-
| `k9o.me.notifications.mark_all_read` | Mark all as read. |
|
|
428
|
-
| `k9o.me.audit_log.query` | Search your own audit events. |
|
|
429
|
-
| `k9o.me.data_export.request` | Request a data export. |
|
|
430
|
-
| `k9o.me.data_export.status` | Check an export job. |
|
|
431
471
|
|
|
432
472
|
#### Organization
|
|
433
473
|
|
|
@@ -447,30 +487,26 @@ signed = avatars.create_signed_url("ada.png", expires_in: 300)
|
|
|
447
487
|
| Method | What it does |
|
|
448
488
|
|---|---|
|
|
449
489
|
| `k9o.org.get` | Read the active organization. |
|
|
450
|
-
| `k9o.org.members.list` | List members. |
|
|
490
|
+
| `k9o.org.members.list` | List members. Options: `user_id:`, `role:`, `acceptance_status:`, `status:`, `state:`. |
|
|
451
491
|
| `k9o.org.members.invite` | Invite a member by email and role. |
|
|
452
|
-
| `k9o.org.members.get` | Read one member. |
|
|
453
492
|
| `k9o.org.members.remove` | Remove a member. |
|
|
454
|
-
| `k9o.org.groups.list` | List groups
|
|
455
|
-
| `k9o.org.groups.create` | Create a group. |
|
|
493
|
+
| `k9o.org.groups.list` | List groups, optionally for one `user_id:`. |
|
|
456
494
|
| `k9o.org.groups.get` | Read one group. |
|
|
457
|
-
| `k9o.org.
|
|
458
|
-
| `k9o.org.audit_log.query` | Search organization audit events. |
|
|
495
|
+
| `k9o.org.audit_log.query` | Search organization audit events by `type:` and `limit:`. |
|
|
459
496
|
| `k9o.org.gate.roles.list` | List role grants. |
|
|
460
|
-
| `k9o.org.gate.roles.grant` | Grant a role to a user. |
|
|
497
|
+
| `k9o.org.gate.roles.grant` | Grant a role to a user (`user_id:`, `role:`, optional `decision_reason:`). |
|
|
461
498
|
| `k9o.org.gate.roles.get` | Read one grant. |
|
|
462
499
|
| `k9o.org.gate.roles.revoke` | Revoke a grant. |
|
|
463
500
|
| `k9o.org.gate.idp_providers.list` | List identity providers. |
|
|
464
|
-
| `k9o.org.gate.idp_providers.create` | Add
|
|
501
|
+
| `k9o.org.gate.idp_providers.create` | Add a provider: `idp_provider:`, `display_name:`, `client_id:`, `client_secret_ref:`, `tier_gating:`. |
|
|
465
502
|
| `k9o.org.gate.idp_providers.get` | Read one provider. |
|
|
466
503
|
| `k9o.org.gate.idp_providers.configure` | Change a provider. |
|
|
467
504
|
| `k9o.org.gate.idp_providers.delete` | Remove a provider. |
|
|
468
|
-
| `k9o.org.smtp.
|
|
469
|
-
| `k9o.org.smtp.
|
|
470
|
-
| `k9o.org.smtp.test` | Send a test email. |
|
|
505
|
+
| `k9o.org.smtp.set` | Save the mail server: `host:`, `port:`, `username:`, `password_ref:`, `from_address:`. |
|
|
506
|
+
| `k9o.org.smtp.test` | Send a test email to `to:`, with an optional `subject:`. |
|
|
471
507
|
| `k9o.org.smtp.unset` | Remove the mail server settings. |
|
|
472
508
|
| `k9o.org.default_llm.get` | Read the default model. |
|
|
473
|
-
| `k9o.org.default_llm.set` | Set the default model. |
|
|
509
|
+
| `k9o.org.default_llm.set` | Set the default model from a model id. |
|
|
474
510
|
|
|
475
511
|
#### Vault secrets
|
|
476
512
|
|
|
@@ -479,7 +515,7 @@ signed = avatars.create_signed_url("ada.png", expires_in: 300)
|
|
|
479
515
|
| Method | What it does |
|
|
480
516
|
|---|---|
|
|
481
517
|
| `k9o.kibo.vault.secrets.list` | List secrets by prefix. |
|
|
482
|
-
| `k9o.kibo.vault.secrets.get` | Read a secret
|
|
518
|
+
| `k9o.kibo.vault.secrets.get` | Read a secret by name (`region:` is required). |
|
|
483
519
|
| `k9o.kibo.vault.secrets.create` | Store a new secret. |
|
|
484
520
|
| `k9o.kibo.vault.secrets.reveal_challenge` | Get a one-time challenge for revealing. |
|
|
485
521
|
| `k9o.kibo.vault.secrets.reveal` | Reveal the value using the challenge. |
|
|
@@ -534,10 +570,12 @@ The server may send codes not listed here, so keep a fallback branch.
|
|
|
534
570
|
|
|
535
571
|
### Regional hosts
|
|
536
572
|
|
|
537
|
-
-
|
|
538
|
-
-
|
|
539
|
-
-
|
|
540
|
-
-
|
|
573
|
+
- A platform host is `api.<apex>` or `api-staging.<apex>`, on any apex.
|
|
574
|
+
- With `org_slug`, the SDK builds the org host `<org_slug>.<host>`.
|
|
575
|
+
- A region-free org host gets one credential-free `HEAD` request to learn its region.
|
|
576
|
+
- Later requests go to the regional host, which must share the org, family and apex.
|
|
577
|
+
- Any other base URL (internal, air-gapped, localhost) is used exactly as given.
|
|
578
|
+
- `https://api.k9o.io` is only the default when `base_url:` is omitted.
|
|
541
579
|
|
|
542
580
|
### Related packages
|
|
543
581
|
|
data/lib/k9o/audit.rb
CHANGED
|
@@ -2,27 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
module K9o
|
|
4
4
|
# Project-plane audit log namespace — k9o.audit.*.
|
|
5
|
-
# Distinct from k9o.org.audit_log
|
|
5
|
+
# Distinct from k9o.org.audit_log.
|
|
6
6
|
class Audit
|
|
7
7
|
def initialize(config)
|
|
8
8
|
@config = config
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
# GET /@<slug>/audit-log — reverse-chronological; filters AND-compose. Requires the AUDIT_READER role.
|
|
12
|
+
# event_type: audit-event name prefix (e.g. 'data.write.access') actor_id: filter by actor
|
|
13
|
+
# after / before: ISO 8601 bounds (events after / before this instant)
|
|
14
|
+
# limit: page size (bounded server-side) cursor: from the prior response's meta.cursor.next
|
|
15
|
+
def query(event_type: nil, actor_id: nil, after: nil, before: nil, limit: nil, cursor: nil)
|
|
16
16
|
slug = Studio.require_project_slug(@config, 'audit')
|
|
17
17
|
q = {}
|
|
18
|
-
q['
|
|
19
|
-
q['
|
|
20
|
-
q['
|
|
21
|
-
q['
|
|
22
|
-
q['
|
|
23
|
-
q['
|
|
24
|
-
q['limit'] = limit if limit
|
|
25
|
-
q['cursor'] = cursor if cursor
|
|
18
|
+
q['eventType'] = event_type unless event_type.nil?
|
|
19
|
+
q['actorId'] = actor_id unless actor_id.nil?
|
|
20
|
+
q['after'] = after unless after.nil?
|
|
21
|
+
q['before'] = before unless before.nil?
|
|
22
|
+
q['limit'] = limit unless limit.nil?
|
|
23
|
+
q['cursor'] = cursor unless cursor.nil?
|
|
26
24
|
HttpTransport.request(config: @config, method: 'GET', path: UrlGrammar.project_path(slug, 'audit-log'), query: q)
|
|
27
25
|
end
|
|
28
26
|
end
|
data/lib/k9o/client.rb
CHANGED
|
@@ -36,17 +36,15 @@ module K9o
|
|
|
36
36
|
|
|
37
37
|
# Factory — construct a K9oClient bound to the supplied options.
|
|
38
38
|
#
|
|
39
|
-
# Per spec sample:
|
|
40
39
|
# k9o = K9o.create_client(
|
|
41
40
|
# api_key: ENV["K9O_API_KEY"],
|
|
42
41
|
# org_slug: "acme",
|
|
43
42
|
# project_slug: "member-portal",
|
|
44
43
|
# )
|
|
45
44
|
#
|
|
46
|
-
# base_url is OPTIONAL. When omitted it
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
# https://<org_slug>.api.k9o.io is composed. Any other host passes through.
|
|
45
|
+
# base_url is OPTIONAL. When omitted it defaults to the public-cloud platform host (a convenience only, never used
|
|
46
|
+
# to parse a host). When the host is a platform host (+api.<apex>+ or +api-staging.<apex>+, any apex) and org_slug
|
|
47
|
+
# is set, the org host +<org_slug>.<host>+ is composed. Any other host passes through unchanged.
|
|
50
48
|
def self.create_client(base_url: nil, api_key: nil, org_slug: nil, project_slug: nil, default_headers: {}, http_client: nil)
|
|
51
49
|
normalised = UrlGrammar.resolve_base_url(base_url, org_slug)
|
|
52
50
|
config = HttpClientConfig.new(
|
data/lib/k9o/compute.rb
CHANGED
|
@@ -4,30 +4,37 @@ module K9o
|
|
|
4
4
|
# Compute namespace (Compute LLMs registry).
|
|
5
5
|
# k9o.compute.llms.*
|
|
6
6
|
class Compute
|
|
7
|
-
#
|
|
7
|
+
# The curated model ids (closed enum).
|
|
8
8
|
LLM_MODEL_IDS = %w[
|
|
9
9
|
mistral-7b mistral-8x7b llama-3-8b llama-3-70b
|
|
10
10
|
claude-3-5-sonnet gpt-4o gpt-4o-mini
|
|
11
11
|
text-embedding-3-small text-embedding-3-large
|
|
12
12
|
].freeze
|
|
13
13
|
|
|
14
|
+
# Inference parameters the gateway reads (top-level body fields next to +prompt+). Snake_case spellings map to
|
|
15
|
+
# the gateway's.
|
|
16
|
+
PARAM_FIELDS = {
|
|
17
|
+
'max_tokens' => 'maxTokens', 'maxTokens' => 'maxTokens',
|
|
18
|
+
'temperature' => 'temperature',
|
|
19
|
+
'usage_type' => 'usageType', 'usageType' => 'usageType'
|
|
20
|
+
}.freeze
|
|
21
|
+
|
|
14
22
|
attr_reader :llms
|
|
15
23
|
|
|
16
24
|
def initialize(config)
|
|
17
25
|
@llms = Llms.new(config)
|
|
18
26
|
end
|
|
19
27
|
|
|
20
|
-
# k9o.compute.llms
|
|
28
|
+
# k9o.compute.llms.*
|
|
21
29
|
class Llms
|
|
22
30
|
def initialize(config)
|
|
23
31
|
@config = config
|
|
24
32
|
end
|
|
25
33
|
|
|
26
|
-
|
|
34
|
+
# GET /@<slug>/compute/llms
|
|
35
|
+
def list
|
|
27
36
|
slug = Studio.require_project_slug(@config, 'compute.llms')
|
|
28
|
-
|
|
29
|
-
HttpTransport.request(config: @config, method: 'GET',
|
|
30
|
-
path: UrlGrammar.project_path(slug, 'compute/llms'), query: q)
|
|
37
|
+
HttpTransport.request(config: @config, method: 'GET', path: UrlGrammar.project_path(slug, 'compute/llms'))
|
|
31
38
|
end
|
|
32
39
|
|
|
33
40
|
def read(model_id)
|
|
@@ -36,29 +43,39 @@ module K9o
|
|
|
36
43
|
path: UrlGrammar.project_path(slug, "compute/llms/#{enc(model_id)}"))
|
|
37
44
|
end
|
|
38
45
|
|
|
39
|
-
# Synchronous inference
|
|
46
|
+
# Synchronous inference: POST /@<slug>/compute/llms/<id>/invoke { prompt, maxTokens?, temperature?,
|
|
47
|
+
# usageType? }. +params+ takes max_tokens / temperature / usage_type (the gateway's spellings also work).
|
|
48
|
+
# The envelope's data is { 'modelId', 'content', 'finishReason', 'usage' => { 'promptTokens',
|
|
49
|
+
# 'completionTokens', 'totalTokens' }, 'usageType' }.
|
|
40
50
|
def invoke(model_id, prompt:, params: nil)
|
|
41
51
|
slug = Studio.require_project_slug(@config, 'compute.llms')
|
|
42
|
-
body = { 'prompt' => prompt }
|
|
43
|
-
body['params'] = params if params
|
|
44
52
|
HttpTransport.request(config: @config, method: 'POST',
|
|
45
|
-
path: UrlGrammar.project_path(slug, "compute/llms/#{enc(model_id)}/invoke"),
|
|
53
|
+
path: UrlGrammar.project_path(slug, "compute/llms/#{enc(model_id)}/invoke"),
|
|
54
|
+
body: invoke_body(prompt, params))
|
|
46
55
|
end
|
|
47
56
|
|
|
48
|
-
# Streaming inference (SSE per-token). Returns an
|
|
49
|
-
# Enumerator of chunk hashes (Ruby idiom). Consume with
|
|
57
|
+
# Streaming inference (SSE per-token). Returns an Enumerator of chunk hashes. Consume with
|
|
50
58
|
# `.each { |chunk| ... }`.
|
|
51
59
|
def invoke_stream(model_id, prompt:, params: nil)
|
|
52
60
|
slug = Studio.require_project_slug(@config, 'compute.llms')
|
|
53
|
-
body = { 'prompt' => prompt }
|
|
54
|
-
body['params'] = params if params
|
|
55
61
|
HttpTransport.stream_sse(config: @config, method: 'POST',
|
|
56
62
|
path: UrlGrammar.project_path(slug, "compute/llms/#{enc(model_id)}/invoke-stream"),
|
|
57
|
-
body:
|
|
63
|
+
body: invoke_body(prompt, params))
|
|
58
64
|
end
|
|
59
65
|
|
|
60
66
|
private
|
|
61
67
|
|
|
68
|
+
def invoke_body(prompt, params)
|
|
69
|
+
body = { 'prompt' => prompt }
|
|
70
|
+
(params || {}).each do |key, value|
|
|
71
|
+
wire = PARAM_FIELDS[key.to_s]
|
|
72
|
+
raise ArgumentError, "unknown inference parameter: #{key}" if wire.nil?
|
|
73
|
+
|
|
74
|
+
body[wire] = value unless value.nil?
|
|
75
|
+
end
|
|
76
|
+
body
|
|
77
|
+
end
|
|
78
|
+
|
|
62
79
|
def enc(s)
|
|
63
80
|
::URI.encode_www_form_component(s)
|
|
64
81
|
end
|
data/lib/k9o/functions.rb
CHANGED
|
@@ -2,22 +2,52 @@
|
|
|
2
2
|
|
|
3
3
|
module K9o
|
|
4
4
|
# Function lifecycle namespace — k9o.functions.*.
|
|
5
|
+
#
|
|
6
|
+
# Upload the function's bytes first (+upload_artifact+ returns the +artifactKey+), then +deploy+ references that
|
|
7
|
+
# artifact. Deploy requires PROJECT_ADMIN and returns with status +deploying+; the function is ready when status
|
|
8
|
+
# flips to +active+.
|
|
5
9
|
class Functions
|
|
10
|
+
ARTIFACT_CONTENT_TYPES = { 'wasm' => 'application/wasm', 'tar' => 'application/x-tar' }.freeze
|
|
11
|
+
|
|
6
12
|
def initialize(config)
|
|
7
13
|
@config = config
|
|
8
14
|
end
|
|
9
15
|
|
|
10
|
-
|
|
16
|
+
# POST /@<slug>/functions/artifacts with the raw bytes: a WebAssembly module for the gateway tier (up to 16 MiB,
|
|
17
|
+
# +kind+ 'wasm') or an OCI image archive for the sandbox tier (up to 256 MiB, +kind+ 'tar'). Uploading the same
|
|
18
|
+
# bytes again returns the same key. The envelope's data is { 'artifactKey' => String }.
|
|
19
|
+
def upload_artifact(bytes, kind)
|
|
20
|
+
content_type = ARTIFACT_CONTENT_TYPES[kind.to_s]
|
|
21
|
+
raise ArgumentError, "kind must be 'wasm' or 'tar'" if content_type.nil?
|
|
22
|
+
|
|
23
|
+
bytes = bytes.read if bytes.respond_to?(:read)
|
|
24
|
+
raise ArgumentError, 'bytes must be a String or an IO' unless bytes.is_a?(String)
|
|
25
|
+
|
|
26
|
+
slug = require_project_slug
|
|
27
|
+
HttpTransport.request(config: @config, method: 'POST',
|
|
28
|
+
path: UrlGrammar.project_path(slug, 'functions/artifacts'),
|
|
29
|
+
raw_body: bytes, headers: { 'content-type' => content_type })
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# POST /@<slug>/functions — deploy or redeploy from an uploaded artifact.
|
|
33
|
+
# runtime: bun python go java kotlin dotnet swift php ruby rust tinygo zig assemblyscript
|
|
34
|
+
# tier: 'gateway' (WebAssembly) or 'sandbox' (OCI image archive)
|
|
35
|
+
# artifact_key: the key +upload_artifact+ returned; version: label for this deploy (1 to 64 characters)
|
|
36
|
+
def deploy(name:, runtime:, tier:, artifact_key:, version:, description: nil)
|
|
11
37
|
slug = require_project_slug
|
|
12
|
-
body = { 'name' => name, 'runtime' => runtime, '
|
|
13
|
-
|
|
14
|
-
body['
|
|
38
|
+
body = { 'name' => name, 'runtime' => runtime, 'tier' => tier, 'artifactKey' => artifact_key,
|
|
39
|
+
'version' => version }
|
|
40
|
+
body['description'] = description unless description.nil?
|
|
15
41
|
HttpTransport.request(config: @config, method: 'POST', path: UrlGrammar.project_path(slug, 'functions'), body: body)
|
|
16
42
|
end
|
|
17
43
|
|
|
18
|
-
|
|
44
|
+
# GET /@<slug>/functions (cursor pagination).
|
|
45
|
+
def list(status: nil, limit: nil, cursor: nil)
|
|
19
46
|
slug = require_project_slug
|
|
20
|
-
q =
|
|
47
|
+
q = {}
|
|
48
|
+
q['status'] = status unless status.nil?
|
|
49
|
+
q['limit'] = limit unless limit.nil?
|
|
50
|
+
q['cursor'] = cursor unless cursor.nil?
|
|
21
51
|
HttpTransport.request(config: @config, method: 'GET', path: UrlGrammar.project_path(slug, 'functions'), query: q)
|
|
22
52
|
end
|
|
23
53
|
|
|
@@ -26,16 +56,25 @@ module K9o
|
|
|
26
56
|
HttpTransport.request(config: @config, method: 'GET', path: UrlGrammar.project_path(slug, "functions/#{enc(name)}"))
|
|
27
57
|
end
|
|
28
58
|
|
|
59
|
+
# Retire a function; its name enters a 24h cooldown.
|
|
29
60
|
def delete(name)
|
|
30
61
|
slug = require_project_slug
|
|
31
62
|
HttpTransport.request(config: @config, method: 'DELETE', path: UrlGrammar.project_path(slug, "functions/#{enc(name)}"))
|
|
32
63
|
end
|
|
33
64
|
|
|
34
|
-
#
|
|
35
|
-
|
|
65
|
+
# POST /@<slug>/functions/<name>/invoke. +payload+ is sent as the +payload+ field of the request body and passed
|
|
66
|
+
# to the function runtime verbatim. Optional controls: +background+ (run asynchronously), +delegation+
|
|
67
|
+
# ({ 'token' => ..., 'invokeSessionId' => ... }), +on_behalf_of+, +confined_to+.
|
|
68
|
+
def invoke(name, payload = nil, background: nil, delegation: nil, on_behalf_of: nil, confined_to: nil)
|
|
36
69
|
slug = require_project_slug
|
|
70
|
+
body = {}
|
|
71
|
+
body['payload'] = payload unless payload.nil?
|
|
72
|
+
body['background'] = background unless background.nil?
|
|
73
|
+
body['delegation'] = delegation unless delegation.nil?
|
|
74
|
+
body['onBehalfOf'] = on_behalf_of unless on_behalf_of.nil?
|
|
75
|
+
body['confinedTo'] = confined_to unless confined_to.nil?
|
|
37
76
|
HttpTransport.request(config: @config, method: 'POST',
|
|
38
|
-
path: UrlGrammar.project_path(slug, "functions/#{enc(name)}/invoke"), body:
|
|
77
|
+
path: UrlGrammar.project_path(slug, "functions/#{enc(name)}/invoke"), body: body)
|
|
39
78
|
end
|
|
40
79
|
|
|
41
80
|
private
|
data/lib/k9o/gate.rb
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module K9o
|
|
4
|
-
# Gate (Identity-Aware Kernel) namespace. Project-scoped.
|
|
4
|
+
# Gate (Identity-Aware Kernel) namespace. Project-scoped; the gateway closes each body to the fields shown.
|
|
5
5
|
class Gate
|
|
6
6
|
def initialize(config)
|
|
7
7
|
@config = config
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
+
# POST /@<slug>/gate/signup { email, password, displayName? }
|
|
10
11
|
def sign_up(email:, password:, display_name: nil)
|
|
11
12
|
body = { 'email' => email, 'password' => password }
|
|
12
13
|
body['displayName'] = display_name if display_name
|
|
13
14
|
HttpTransport.request(config: @config, method: 'POST', path: gate_path('signup'), body: body)
|
|
14
15
|
end
|
|
15
16
|
|
|
17
|
+
# POST /@<slug>/gate/signin { email, password }
|
|
16
18
|
def sign_in(email:, password:)
|
|
17
19
|
HttpTransport.request(
|
|
18
20
|
config: @config, method: 'POST', path: gate_path('signin'),
|
|
@@ -20,15 +22,18 @@ module K9o
|
|
|
20
22
|
)
|
|
21
23
|
end
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
HttpTransport.request(config: @config, method: 'POST', path: gate_path('signout'),
|
|
25
|
+
# POST /@<slug>/gate/signout { refreshToken } — the refresh token is required (the gateway answers 400 without it).
|
|
26
|
+
def sign_out(refresh_token:)
|
|
27
|
+
HttpTransport.request(config: @config, method: 'POST', path: gate_path('signout'),
|
|
28
|
+
body: { 'refreshToken' => refresh_token })
|
|
26
29
|
end
|
|
27
30
|
|
|
31
|
+
# GET /@<slug>/gate/whoami
|
|
28
32
|
def whoami
|
|
29
33
|
HttpTransport.request(config: @config, method: 'GET', path: gate_path('whoami'))
|
|
30
34
|
end
|
|
31
35
|
|
|
36
|
+
# POST /@<slug>/gate/refresh { refreshToken }
|
|
32
37
|
def refresh_token(refresh_token:)
|
|
33
38
|
HttpTransport.request(
|
|
34
39
|
config: @config, method: 'POST', path: gate_path('refresh'),
|
|
@@ -36,20 +41,6 @@ module K9o
|
|
|
36
41
|
)
|
|
37
42
|
end
|
|
38
43
|
|
|
39
|
-
def verify_session
|
|
40
|
-
HttpTransport.request(config: @config, method: 'GET', path: gate_path('verify'))
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
# The project's hosted sign-in URL and WebAuthn rpId:
|
|
44
|
-
# GET /@<slug>/gate/hosted. Unauthenticated and cacheable; no credential is
|
|
45
|
-
# sent even when an api_key is configured. Returns an Envelope whose `data`
|
|
46
|
-
# is { 'signInUrl' => String, 'rpId' => String }. Take both values as given:
|
|
47
|
-
# an unknown org, a deleted project and a non-Gate project all answer the
|
|
48
|
-
# same 404 K9O_PROJECT_NOT_FOUND.
|
|
49
|
-
def hosted
|
|
50
|
-
HttpTransport.request(config: @config, method: 'GET', path: gate_path('hosted'), authenticated: false)
|
|
51
|
-
end
|
|
52
|
-
|
|
53
44
|
private
|
|
54
45
|
|
|
55
46
|
def gate_path(action)
|