nylorun 0.20.0-beta → 0.22.0-beta
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.
- package/CHANGELOG.md +32 -0
- package/README.md +15 -1
- package/dist/client/model/pi-ai.d.ts +1 -1
- package/dist/client/model/pi-ai.js +1 -1
- package/dist/client/models.d.ts +1 -1
- package/dist/client/models.js +30 -4
- package/dist/client/tenant.d.ts +1 -1
- package/dist/sandbox/commands.d.ts +10 -2
- package/dist/sandbox/commands.js +29 -7
- package/dist/sandbox/probe.d.ts +13 -2
- package/dist/sandbox/probe.js +17 -3
- package/dist/stack/compose-file.js +5 -1
- package/dist/tuning.d.ts +1 -1
- package/dist/tuning.js +1 -1
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# nylorun
|
|
2
2
|
|
|
3
|
+
## 0.22.0-beta
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 77902da: `nylorun local sandbox enable --probe-ports <allowed>,<denied>` publishes the NetworkPolicy probe's listener on those two host ports instead of two free ones. A firewall in front of the Docker host that opens only named ports to the pods, such as a cloud VPC's, can then open them; without it, the probe's random ports never reach the host and `enable` refuses the cluster. The ports must differ from each other and from the sandbox ports.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- 41b5741: `nylorun local sandbox enable` waits for a newly installed agent-sandbox CRD by polling its `Established` condition. It used `kubectl wait --for=condition=Established`, which fails at once, with ".status.conditions accessor error", when the API server has not yet given the CRD any conditions; GKE answers that way right after the apply.
|
|
12
|
+
|
|
13
|
+
## 0.21.0-beta
|
|
14
|
+
|
|
15
|
+
### Minor Changes
|
|
16
|
+
|
|
17
|
+
- 95f98f1: A model can use the Host's own identity (ADR-71, Host feature `model-host-identity`). `PUT /v1/tenant/model` takes `auth: { type: "host_identity", project, location }` for `google-vertex`. It keeps no secret, and the model gate calls Agent Platform with the Host's Google Application Default Credentials: on Google Cloud, the attached service account from the metadata server. It's off unless the operator lists the provider in `NYLORUN_MODEL_HOST_IDENTITY` on the process that runs the gates, which `nylorun start` passes from its shell to the `gateway` container. Every Tenant on that Host may use it. `nylorun tenant models set --provider google-vertex --model <id> --host-identity --project <id> --location <location>` sets it with no key.
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- 683de1a: The model catalog comes from pi-ai 1.3.0, which adds Gemini 4 Argon: `google-vertex/gemini-4-argon` on Google's Agent Platform (Vertex AI) and `google/gemini-4-argon` on the Gemini API, with a 1,048,576-token context and thinking. `nylorun tenant models set --provider google-vertex --model gemini-4-argon` takes a Google Cloud API key for the Agent Platform API; the Runtime still reads no ambient Google credentials.
|
|
22
|
+
- Pin core to the tested release.
|
|
23
|
+
- Pin agents to the tested release.
|
|
24
|
+
- Pin admin to the tested release.
|
|
25
|
+
- Pin runtime to the tested release.
|
|
26
|
+
- Pin studio to the tested release.
|
|
27
|
+
- Updated dependencies [95f98f1]
|
|
28
|
+
- Updated dependencies [d99b3ac]
|
|
29
|
+
- Updated dependencies
|
|
30
|
+
- Updated dependencies
|
|
31
|
+
- @nylorun/core@0.29.0-beta
|
|
32
|
+
- @nylorun/agents@0.29.0-beta
|
|
33
|
+
- @nylorun/admin@0.19.5-beta
|
|
34
|
+
|
|
3
35
|
## 0.20.0-beta
|
|
4
36
|
|
|
5
37
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -102,6 +102,7 @@ nylorun tenant signing-keys list|rotate [--force]|revoke <kid> [--tenant <name>]
|
|
|
102
102
|
nylorun tenant models catalog|providers|get [--tenant <name>] [--json] # the models the Runtime knows, the providers with a credential, the Tenant's model
|
|
103
103
|
nylorun tenant models set [--tenant <name>] # sign in to a model provider and set the Tenant's model
|
|
104
104
|
nylorun tenant models set --provider <id> --model <id> [--base-url <url>] [--tenant <name>] # the same, with an API key piped in on stdin (CI)
|
|
105
|
+
nylorun tenant models set --provider google-vertex --model <id> --host-identity --project <id> --location <location> # the Host's Google identity, no key
|
|
105
106
|
nylorun tenant models select <provider> [--model <id>] [--tenant <name>] # use another provider that has a credential
|
|
106
107
|
nylorun tenant usage [--scope tenant|manifest|turn] [--id <id>] [--period day|month|total] [--tenant <name>] # calls, tokens and cost (JSON); manifest: the Agents made from agent manifest <id>
|
|
107
108
|
nylorun tenant calls [--after <cursor>] [--limit <n>] [--tenant <name>] # every recorded model call as NDJSON, until caught up
|
|
@@ -116,7 +117,7 @@ nylorun local ls [--json] # the Tenants on this machine, with their sta
|
|
|
116
117
|
nylorun local delete <tenant> --yes # remove a Tenant: containers, volumes, Host root and vault key
|
|
117
118
|
nylorun local logs [service] [--tenant <name>] [-f] [--tail <n>] # postgres, restate, s2-lite, rustfs, gateway, runtime, harness, studio, sandboxes
|
|
118
119
|
nylorun local reset [--tenant <name>] [--yes] # delete the Tenant's volumes, Tenant directory and vault key; the next start creates it anew
|
|
119
|
-
nylorun local sandbox enable --context <name> [--tenant <name>] [--host-address <ip>] [--bind-address <ip>] [--no-pull]
|
|
120
|
+
nylorun local sandbox enable --context <name> [--tenant <name>] [--host-address <ip>] [--bind-address <ip>] [--probe-ports <allowed>,<denied>] [--no-pull]
|
|
120
121
|
nylorun local sandbox disable [--tenant <name>] [--delete-namespace]
|
|
121
122
|
nylorun local sandbox status [--tenant <name>] [--json] # pods on a Kubernetes context (see Sandboxes)
|
|
122
123
|
nylorun local telemetry [status|enable|disable] # Studio's anonymous usage analytics
|
|
@@ -427,6 +428,10 @@ with a probe that the cluster enforces NetworkPolicy (and refuses it otherwise),
|
|
|
427
428
|
lets pods reach only three ports on the Docker host, and adds the `sandboxes`
|
|
428
429
|
service to the Tenant: the one container holding the cluster credentials
|
|
429
430
|
(`<Host root>/sandboxes/`). On kind under Linux, pass `--host-address 172.17.0.1`.
|
|
431
|
+
The probe publishes two free ports on the Docker host. Behind a firewall that
|
|
432
|
+
opens only named ports to the pods, such as a cloud VPC's, pass two of them with
|
|
433
|
+
`--probe-ports <allowed>,<denied>`. Open both, so that only NetworkPolicy can
|
|
434
|
+
block the denied one.
|
|
430
435
|
`nylorun local sandbox disable` removes the service (`--delete-namespace` also deletes
|
|
431
436
|
the namespace and every sandbox in it). Sessions run in a pod when their
|
|
432
437
|
Agent's manifest has `runtime.kind` `pod`.
|
|
@@ -717,6 +722,15 @@ providers need the prompts.
|
|
|
717
722
|
printenv OPENAI_API_KEY | nylorun tenant models set --provider openai --model gpt-5
|
|
718
723
|
```
|
|
719
724
|
|
|
725
|
+
On a Host whose operator lends its identity (`NYLORUN_MODEL_HOST_IDENTITY=google-vertex`),
|
|
726
|
+
`--host-identity` with `--project` and `--location` sets a `google-vertex` model with no key: the
|
|
727
|
+
Runtime calls Agent Platform with the Host's Google Application Default Credentials.
|
|
728
|
+
|
|
729
|
+
```sh
|
|
730
|
+
nylorun tenant models set --provider google-vertex --model gemini-4-argon \
|
|
731
|
+
--host-identity --project my-project --location global
|
|
732
|
+
```
|
|
733
|
+
|
|
720
734
|
`nylorun tenant models select <provider>` switches to another provider that
|
|
721
735
|
already has a credential, keeping its model (or `--model`) and endpoint.
|
|
722
736
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* The pi-ai `nylorun tenant models set` loads; nylorun's devDependency has the same version
|
|
3
3
|
* (scripts/check-package.mjs).
|
|
4
4
|
*/
|
|
5
|
-
export declare const PI_AI_VERSION = "1.
|
|
5
|
+
export declare const PI_AI_VERSION = "1.3.0";
|
|
6
6
|
/** What `nylorun tenant models set` uses of pi-ai: its provider registry and login flows. */
|
|
7
7
|
export interface PiAi {
|
|
8
8
|
createProvider: typeof import("@earendil-works/pi-ai").createProvider;
|
|
@@ -10,7 +10,7 @@ import { CliError } from "../../errors.js";
|
|
|
10
10
|
* The pi-ai `nylorun tenant models set` loads; nylorun's devDependency has the same version
|
|
11
11
|
* (scripts/check-package.mjs).
|
|
12
12
|
*/
|
|
13
|
-
export const PI_AI_VERSION = "1.
|
|
13
|
+
export const PI_AI_VERSION = "1.3.0";
|
|
14
14
|
const PI_AI = "@earendil-works/pi-ai";
|
|
15
15
|
/** The module written beside an on-demand install, which resolves pi-ai from there. */
|
|
16
16
|
const ENTRY = "pi-ai.js";
|
package/dist/client/models.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type StackDeps } from "../stack/commands.js";
|
|
2
|
-
export declare const modelsUsage = " models catalog [--tenant <name>] [--json]\n every provider and model the Runtime knows\n models providers [--tenant <name>] [--json]\n the providers with a credential, and the one in use\n models get [--tenant <name>] [--json]\n the Tenant's model\n models set [--tenant <name>] sign in to a model provider and set the Tenant's model\n models set --provider <id> --model <id> [--base-url <url>] [--tenant <name>]\n the same with an API key piped in on stdin (CI)\n models select <provider> [--model <id>] [--tenant <name>]\n use a provider that has a credential (default: its model)";
|
|
2
|
+
export declare const modelsUsage = " models catalog [--tenant <name>] [--json]\n every provider and model the Runtime knows\n models providers [--tenant <name>] [--json]\n the providers with a credential, and the one in use\n models get [--tenant <name>] [--json]\n the Tenant's model\n models set [--tenant <name>] sign in to a model provider and set the Tenant's model\n models set --provider <id> --model <id> [--base-url <url>] [--tenant <name>]\n the same with an API key piped in on stdin (CI)\n models set --provider google-vertex --model <id> --host-identity --project <id> --location <location> [--tenant <name>]\n the same with the Host's Google identity, no key: on a Host\n whose operator sets NYLORUN_MODEL_HOST_IDENTITY\n models select <provider> [--model <id>] [--tenant <name>]\n use a provider that has a credential (default: its model)";
|
|
3
3
|
export declare const usageUsage = " usage [--scope tenant|manifest|turn] [--id <id>] [--period day|month|total] [--tenant <name>]\n what the model calls used and cost (JSON); --scope\n manifest: the Agents made from agent manifest <id>\n calls [--after <cursor>] [--limit <n>] [--tenant <name>]\n every recorded model call, one JSON line each, until\n caught up; prints the cursor to resume from on stderr";
|
|
4
4
|
/** `nylorun tenant models <verb> ...` */
|
|
5
5
|
export declare function modelsCommand(deps: StackDeps, args: readonly string[]): Promise<number>;
|
package/dist/client/models.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `nylorun tenant models | usage | calls`: the Tenant's model, what its model calls used, and the
|
|
3
3
|
* calls themselves, through the Management API. `models set` signs in with pi-ai (loaded only
|
|
4
|
-
* there) at a terminal; with `--provider` and `--model` it takes an API key piped in instead,
|
|
4
|
+
* there) at a terminal; with `--provider` and `--model` it takes an API key piped in instead, or
|
|
5
|
+
* with `--host-identity` the Host's own Google identity (ADR-71),
|
|
5
6
|
* for CI. A key is never an argument.
|
|
6
7
|
*/
|
|
7
8
|
import { randomUUID } from "node:crypto";
|
|
@@ -18,6 +19,9 @@ export const modelsUsage = ` models catalog [--tenant <name>] [--json]
|
|
|
18
19
|
models set [--tenant <name>] sign in to a model provider and set the Tenant's model
|
|
19
20
|
models set --provider <id> --model <id> [--base-url <url>] [--tenant <name>]
|
|
20
21
|
the same with an API key piped in on stdin (CI)
|
|
22
|
+
models set --provider google-vertex --model <id> --host-identity --project <id> --location <location> [--tenant <name>]
|
|
23
|
+
the same with the Host's Google identity, no key: on a Host
|
|
24
|
+
whose operator sets NYLORUN_MODEL_HOST_IDENTITY
|
|
21
25
|
models select <provider> [--model <id>] [--tenant <name>]
|
|
22
26
|
use a provider that has a credential (default: its model)`;
|
|
23
27
|
export const usageUsage = ` usage [--scope tenant|manifest|turn] [--id <id>] [--period day|month|total] [--tenant <name>]
|
|
@@ -75,18 +79,40 @@ async function read(deps, verb, args) {
|
|
|
75
79
|
}
|
|
76
80
|
return 0;
|
|
77
81
|
}
|
|
78
|
-
const SET_USAGE = "nylorun tenant models set [--tenant <name>] | nylorun tenant models set --provider <id> --model <id> [--base-url <url>] [--tenant <name>]";
|
|
82
|
+
const SET_USAGE = "nylorun tenant models set [--tenant <name>] | nylorun tenant models set --provider <id> --model <id> [--base-url <url>] [--tenant <name>] | nylorun tenant models set --provider google-vertex --model <id> --host-identity --project <id> --location <location> [--tenant <name>]";
|
|
79
83
|
/**
|
|
80
84
|
* `models set`: at a terminal, the sign-in prompts; with `--provider` and `--model`, the API key
|
|
81
|
-
* on stdin, set as `PUT /v1/tenant/model` with an `api_key` credential (OAuth needs the prompts)
|
|
85
|
+
* on stdin, set as `PUT /v1/tenant/model` with an `api_key` credential (OAuth needs the prompts),
|
|
86
|
+
* or with `--host-identity` a `host_identity` credential: no key, only `--project` and `--location`.
|
|
82
87
|
*/
|
|
83
88
|
async function set(deps, args) {
|
|
84
|
-
const flags = parseStackFlags(args, {
|
|
89
|
+
const flags = parseStackFlags(args, {
|
|
90
|
+
values: ["--tenant", "--provider", "--model", "--base-url", "--project", "--location"],
|
|
91
|
+
booleans: ["--host-identity"],
|
|
92
|
+
}, SET_USAGE);
|
|
85
93
|
if (flags.rest.length)
|
|
86
94
|
throw usageError(`Usage: ${SET_USAGE}`);
|
|
87
95
|
const provider = flags.values.get("--provider");
|
|
88
96
|
const model = flags.values.get("--model");
|
|
89
97
|
const baseUrl = flags.values.get("--base-url");
|
|
98
|
+
const project = flags.values.get("--project");
|
|
99
|
+
const location = flags.values.get("--location");
|
|
100
|
+
const hostIdentity = flags.booleans.has("--host-identity");
|
|
101
|
+
if (hostIdentity || project !== undefined || location !== undefined) {
|
|
102
|
+
if (!hostIdentity || provider === undefined || model === undefined || !project || !location)
|
|
103
|
+
throw usageError(`Pass --host-identity with --provider, --model, --project and --location. Usage: ${SET_USAGE}`);
|
|
104
|
+
if (baseUrl !== undefined)
|
|
105
|
+
throw usageError("--host-identity takes no --base-url");
|
|
106
|
+
const admin = await client(deps, flags.values.get("--tenant"));
|
|
107
|
+
const view = await admin.models.put({
|
|
108
|
+
idempotencyKey: randomUUID(),
|
|
109
|
+
provider,
|
|
110
|
+
model,
|
|
111
|
+
auth: { type: "host_identity", project, location },
|
|
112
|
+
});
|
|
113
|
+
deps.out(describe(view));
|
|
114
|
+
return 0;
|
|
115
|
+
}
|
|
90
116
|
if (provider !== undefined || model !== undefined || baseUrl !== undefined) {
|
|
91
117
|
if (provider === undefined || model === undefined)
|
|
92
118
|
throw usageError(`Pass both --provider and --model. Usage: ${SET_USAGE}`);
|
package/dist/client/tenant.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import type { ManagementClient } from "@nylorun/admin";
|
|
7
7
|
import { type StackDeps } from "../stack/commands.js";
|
|
8
8
|
import { type Connection } from "./target.js";
|
|
9
|
-
export declare const tenantUsage = "nylorun tenant <command>\n\nThe Management API of the installation's one Tenant, with a management key: the local Tenant\n--tenant names (else the linked project's, else the default one), or the installation that\nNYLORUN_RUNTIME_URL and NYLORUN_MANAGEMENT_KEY name.\n\n reset [--sessions|--agents|--all] [--yes] [--tenant <name>]\n clear the Tenant's sessions, its Agents (their vaults,\n disks and sessions) or all its data (the Project link and\n keys stay)\n keys put <id> [--management] [--tenant <name>]\n create the Tenant's application key <id> (--management: a\n management key, on the Runtime's machine only), or rotate it\n (the old key stops working); prints the key once\n keys list [--tenant <name>] [--json]\n the Tenant's keys: id, role, when issued (never the keys)\n keys rm <id> [--tenant <name>] delete key <id>: it stops working at once\n signing-keys list [--tenant <name>]\n the Tenant's token signing keys\n signing-keys rotate [--force] [--tenant <name>]\n rotate; --force ends outstanding links and run tokens\n signing-keys revoke <kid> [--tenant <name>]\n revoke a previous or standby key\n models catalog [--tenant <name>] [--json]\n every provider and model the Runtime knows\n models providers [--tenant <name>] [--json]\n the providers with a credential, and the one in use\n models get [--tenant <name>] [--json]\n the Tenant's model\n models set [--tenant <name>] sign in to a model provider and set the Tenant's model\n models set --provider <id> --model <id> [--base-url <url>] [--tenant <name>]\n the same with an API key piped in on stdin (CI)\n models select <provider> [--model <id>] [--tenant <name>]\n use a provider that has a credential (default: its model)\n usage [--scope tenant|manifest|turn] [--id <id>] [--period day|month|total] [--tenant <name>]\n what the model calls used and cost (JSON); --scope\n manifest: the Agents made from agent manifest <id>\n calls [--after <cursor>] [--limit <n>] [--tenant <name>]\n every recorded model call, one JSON line each, until\n caught up; prints the cursor to resume from on stderr\n budgets [get | put <file|->] [--tenant <name>]\n the model spend caps (scope tenant, manifest with id an\n agent manifest's name, or turn); put replaces them all\n tool-policy [get | put <file|->] [--tenant <name>]\n the Tool Gate's rules; put replaces them all\n settings sandbox [get | put <file|->] [--tenant <name>]\n the Agents' environments: limits, stop grace, placement\n settings artifacts [get | put <file|->] [--tenant <name>]\n the artifact store's limits\n settings skills [get | put <file|->] [--tenant <name>]\n the most bytes one skill may hold\n settings limits [get | put <file|->] [--tenant <name>]\n the Runtime limits: sizes, counts and deadlines (record\n entries, request bodies, Agents, model calls, tools)\n coverage --agent <id> [--tenant <name>] [--json]\n which secret of Agent <id>'s vault each MCP server and\n HTTP tool of its manifest would get\n mcp inspect <url> [--server <name>] [--agent <id>] [--sse] [--tenant <name>] [--json]\n list a remote MCP server's tools, with Agent <id>'s\n credential for <url> (none without it): model names, schema\n sizes, renames, or that it needs a person's sign-in (the\n Runtime calls no tool)\n\nA put reads a JSON body from a file, or from stdin for -, and replaces the whole document; what\nbudgets, tool-policy, settings artifacts, settings skills and settings limits get print, put takes back. An API key is read from\nstdin, never from the arguments.";
|
|
9
|
+
export declare const tenantUsage = "nylorun tenant <command>\n\nThe Management API of the installation's one Tenant, with a management key: the local Tenant\n--tenant names (else the linked project's, else the default one), or the installation that\nNYLORUN_RUNTIME_URL and NYLORUN_MANAGEMENT_KEY name.\n\n reset [--sessions|--agents|--all] [--yes] [--tenant <name>]\n clear the Tenant's sessions, its Agents (their vaults,\n disks and sessions) or all its data (the Project link and\n keys stay)\n keys put <id> [--management] [--tenant <name>]\n create the Tenant's application key <id> (--management: a\n management key, on the Runtime's machine only), or rotate it\n (the old key stops working); prints the key once\n keys list [--tenant <name>] [--json]\n the Tenant's keys: id, role, when issued (never the keys)\n keys rm <id> [--tenant <name>] delete key <id>: it stops working at once\n signing-keys list [--tenant <name>]\n the Tenant's token signing keys\n signing-keys rotate [--force] [--tenant <name>]\n rotate; --force ends outstanding links and run tokens\n signing-keys revoke <kid> [--tenant <name>]\n revoke a previous or standby key\n models catalog [--tenant <name>] [--json]\n every provider and model the Runtime knows\n models providers [--tenant <name>] [--json]\n the providers with a credential, and the one in use\n models get [--tenant <name>] [--json]\n the Tenant's model\n models set [--tenant <name>] sign in to a model provider and set the Tenant's model\n models set --provider <id> --model <id> [--base-url <url>] [--tenant <name>]\n the same with an API key piped in on stdin (CI)\n models set --provider google-vertex --model <id> --host-identity --project <id> --location <location> [--tenant <name>]\n the same with the Host's Google identity, no key: on a Host\n whose operator sets NYLORUN_MODEL_HOST_IDENTITY\n models select <provider> [--model <id>] [--tenant <name>]\n use a provider that has a credential (default: its model)\n usage [--scope tenant|manifest|turn] [--id <id>] [--period day|month|total] [--tenant <name>]\n what the model calls used and cost (JSON); --scope\n manifest: the Agents made from agent manifest <id>\n calls [--after <cursor>] [--limit <n>] [--tenant <name>]\n every recorded model call, one JSON line each, until\n caught up; prints the cursor to resume from on stderr\n budgets [get | put <file|->] [--tenant <name>]\n the model spend caps (scope tenant, manifest with id an\n agent manifest's name, or turn); put replaces them all\n tool-policy [get | put <file|->] [--tenant <name>]\n the Tool Gate's rules; put replaces them all\n settings sandbox [get | put <file|->] [--tenant <name>]\n the Agents' environments: limits, stop grace, placement\n settings artifacts [get | put <file|->] [--tenant <name>]\n the artifact store's limits\n settings skills [get | put <file|->] [--tenant <name>]\n the most bytes one skill may hold\n settings limits [get | put <file|->] [--tenant <name>]\n the Runtime limits: sizes, counts and deadlines (record\n entries, request bodies, Agents, model calls, tools)\n coverage --agent <id> [--tenant <name>] [--json]\n which secret of Agent <id>'s vault each MCP server and\n HTTP tool of its manifest would get\n mcp inspect <url> [--server <name>] [--agent <id>] [--sse] [--tenant <name>] [--json]\n list a remote MCP server's tools, with Agent <id>'s\n credential for <url> (none without it): model names, schema\n sizes, renames, or that it needs a person's sign-in (the\n Runtime calls no tool)\n\nA put reads a JSON body from a file, or from stdin for -, and replaces the whole document; what\nbudgets, tool-policy, settings artifacts, settings skills and settings limits get print, put takes back. An API key is read from\nstdin, never from the arguments.";
|
|
10
10
|
/** The Management API client of the installation `--tenant` (or the environment) names. */
|
|
11
11
|
export declare function tenantClient(deps: StackDeps, tenant: string | undefined): Promise<{
|
|
12
12
|
connection: Connection;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { type StackDeps } from "../stack/commands.js";
|
|
2
2
|
import type { FetchLike } from "../stack/studio-login.js";
|
|
3
3
|
import { type ClusterFile } from "./cluster-file.js";
|
|
4
|
-
import { type KubectlRunner } from "./kubectl.js";
|
|
5
|
-
|
|
4
|
+
import { type Kube, type KubectlRunner } from "./kubectl.js";
|
|
5
|
+
import { type ProbePorts } from "./probe.js";
|
|
6
|
+
export declare const sandboxUsage = " sandbox enable --context <name> [--tenant <name>] [--host-address <ip>] [--bind-address <ip>] [--probe-ports <allowed>,<denied>] [--no-pull]\n run the Tenant's sandboxes as pods on that kubeconfig context (agent-sandbox v1.0.5)\n sandbox disable [--tenant <name>] [--delete-namespace]\n stop the sandboxes service and forget the cluster\n sandbox status [--tenant <name>] [--json]\n the cluster, the sandboxes service's readiness and what it reports";
|
|
6
7
|
/** The default workload image (D36), pre-pulled with the Runtime image. */
|
|
7
8
|
export declare const DEFAULT_SANDBOX_IMAGE = "python:3.13-slim";
|
|
8
9
|
export interface SandboxDeps {
|
|
@@ -14,6 +15,12 @@ export interface SandboxDeps {
|
|
|
14
15
|
pollMs?: number;
|
|
15
16
|
policyTimeoutMs?: number;
|
|
16
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Waits until the Sandbox CRD is Established. Polled, not `kubectl wait`: a CRD the API server has
|
|
20
|
+
* not given any conditions yet makes `kubectl wait --for=condition` fail at once instead of waiting
|
|
21
|
+
* (seen on GKE right after the apply).
|
|
22
|
+
*/
|
|
23
|
+
export declare function waitForCrd(k: Kube, pollMs: number): Promise<void>;
|
|
17
24
|
export interface InstallResult {
|
|
18
25
|
cluster: ClusterFile;
|
|
19
26
|
/** The ServiceAccount token. */
|
|
@@ -30,6 +37,7 @@ export declare function installCluster(deps: SandboxDeps, input: {
|
|
|
30
37
|
tenant: string;
|
|
31
38
|
hostAddress?: string;
|
|
32
39
|
bindAddress?: string;
|
|
40
|
+
probePorts?: ProbePorts;
|
|
33
41
|
prePull: readonly string[];
|
|
34
42
|
ports: {
|
|
35
43
|
harness: number;
|
package/dist/sandbox/commands.js
CHANGED
|
@@ -9,15 +9,15 @@ import { readStackEnv } from "../stack/prepare.js";
|
|
|
9
9
|
import { apiRoute, readClusterFile, removeClusterFiles, writeClusterFiles, } from "./cluster-file.js";
|
|
10
10
|
import { contexts, kube } from "./kubectl.js";
|
|
11
11
|
import { CONTROLLER_DEPLOYMENT, CONTROLLER_NAMESPACE, CONTROLLER_SHA256, CONTROLLER_URL, CONTROLLER_VERSION, networkPolicy, SANDBOX_API_VERSION, SANDBOX_CRD, tenantLabel, tenantManifests, tenantNamespace, TOKEN_SECRET, } from "./manifests.js";
|
|
12
|
-
import { assertIPv4, runProbe } from "./probe.js";
|
|
12
|
+
import { assertIPv4, parseProbePorts, runProbe } from "./probe.js";
|
|
13
13
|
import { cliTuning } from "../tuning.js";
|
|
14
|
-
export const sandboxUsage = ` sandbox enable --context <name> [--tenant <name>] [--host-address <ip>] [--bind-address <ip>] [--no-pull]
|
|
14
|
+
export const sandboxUsage = ` sandbox enable --context <name> [--tenant <name>] [--host-address <ip>] [--bind-address <ip>] [--probe-ports <allowed>,<denied>] [--no-pull]
|
|
15
15
|
run the Tenant's sandboxes as pods on that kubeconfig context (agent-sandbox ${CONTROLLER_VERSION})
|
|
16
16
|
sandbox disable [--tenant <name>] [--delete-namespace]
|
|
17
17
|
stop the sandboxes service and forget the cluster
|
|
18
18
|
sandbox status [--tenant <name>] [--json]
|
|
19
19
|
the cluster, the sandboxes service's readiness and what it reports`;
|
|
20
|
-
const ENABLE_USAGE = "nylorun local sandbox enable --context <name> [--tenant <name>] [--host-address <ip>] [--bind-address <ip>] [--no-pull]";
|
|
20
|
+
const ENABLE_USAGE = "nylorun local sandbox enable --context <name> [--tenant <name>] [--host-address <ip>] [--bind-address <ip>] [--probe-ports <allowed>,<denied>] [--no-pull]";
|
|
21
21
|
/** The default workload image (D36), pre-pulled with the Runtime image. */
|
|
22
22
|
export const DEFAULT_SANDBOX_IMAGE = "python:3.13-slim";
|
|
23
23
|
/** Preferred host ports for the Harness API, gates and egress (pods reach them). */
|
|
@@ -40,7 +40,7 @@ async function readKubeconfig(k) {
|
|
|
40
40
|
* agent-sandbox: install the pinned release when absent; accept it when the pinned version
|
|
41
41
|
* serves v1beta1; refuse anything else. Never upgrades or uninstalls.
|
|
42
42
|
*/
|
|
43
|
-
async function ensureController(k, fetch, err) {
|
|
43
|
+
async function ensureController(k, fetch, err, pollMs) {
|
|
44
44
|
const crd = await k.getJson(["crd", SANDBOX_CRD]);
|
|
45
45
|
if (crd) {
|
|
46
46
|
if (!crd.spec?.versions?.some((v) => v.name === SANDBOX_API_VERSION && v.served))
|
|
@@ -61,10 +61,26 @@ async function ensureController(k, fetch, err) {
|
|
|
61
61
|
if (digest !== CONTROLLER_SHA256)
|
|
62
62
|
throw new CliError(`${CONTROLLER_URL} has sha256 ${digest}, not the pinned ${CONTROLLER_SHA256}; not applying it.`, 1);
|
|
63
63
|
await k.check("Installing agent-sandbox", ["apply", "--server-side", "-f", "-"], { input: manifest });
|
|
64
|
-
await k
|
|
64
|
+
await waitForCrd(k, pollMs);
|
|
65
65
|
const tuning = cliTuning().sandbox;
|
|
66
66
|
await k.check("Waiting for the agent-sandbox controller", ["rollout", "status", `deployment/${CONTROLLER_DEPLOYMENT}`, "-n", CONTROLLER_NAMESPACE, `--timeout=${tuning.rolloutWaitSeconds}s`], { timeoutMs: tuning.rolloutWaitSeconds * 1000 + tuning.kubectlMarginMs });
|
|
67
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Waits until the Sandbox CRD is Established. Polled, not `kubectl wait`: a CRD the API server has
|
|
70
|
+
* not given any conditions yet makes `kubectl wait --for=condition` fail at once instead of waiting
|
|
71
|
+
* (seen on GKE right after the apply).
|
|
72
|
+
*/
|
|
73
|
+
export async function waitForCrd(k, pollMs) {
|
|
74
|
+
const deadline = Date.now() + cliTuning().sandbox.crdWaitSeconds * 1000;
|
|
75
|
+
for (;;) {
|
|
76
|
+
const crd = await k.getJson(["crd", SANDBOX_CRD]);
|
|
77
|
+
if (crd?.status?.conditions?.some((c) => c.type === "Established" && c.status === "True"))
|
|
78
|
+
return;
|
|
79
|
+
if (Date.now() > deadline)
|
|
80
|
+
throw new CliError(`The ${SANDBOX_CRD} CRD in ${k.context} was not Established within ${cliTuning().sandbox.crdWaitSeconds}s.`, 6);
|
|
81
|
+
await new Promise((resolve) => setTimeout(resolve, pollMs));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
68
84
|
/** The ServiceAccount token the token controller writes into the Secret. */
|
|
69
85
|
async function serviceAccountToken(k, namespace, pollMs) {
|
|
70
86
|
const deadline = Date.now() + cliTuning().sandbox.tokenWaitMs;
|
|
@@ -93,7 +109,7 @@ export async function installCluster(deps, input) {
|
|
|
93
109
|
catch (error) {
|
|
94
110
|
throw new CliError(`Context ${input.context}: ${error instanceof Error ? error.message : String(error)}`, 6);
|
|
95
111
|
}
|
|
96
|
-
await ensureController(k, deps.fetch ?? stack.fetch, stack.err);
|
|
112
|
+
await ensureController(k, deps.fetch ?? stack.fetch, stack.err, pollMs);
|
|
97
113
|
const namespace = tenantNamespace(input.tenant);
|
|
98
114
|
const existing = await k.getJson(["namespace", namespace]);
|
|
99
115
|
const owner = existing?.metadata?.labels?.["dev.nylorun.tenant"];
|
|
@@ -112,6 +128,7 @@ export async function installCluster(deps, input) {
|
|
|
112
128
|
}, {
|
|
113
129
|
...(input.hostAddress ? { hostAddress: input.hostAddress } : {}),
|
|
114
130
|
...(input.bindAddress ? { bindAddress: input.bindAddress } : {}),
|
|
131
|
+
...(input.probePorts ? { probePorts: input.probePorts } : {}),
|
|
115
132
|
prePull: input.prePull,
|
|
116
133
|
});
|
|
117
134
|
await k.apply("Applying the namespace's NetworkPolicy", [
|
|
@@ -138,7 +155,7 @@ export async function installCluster(deps, input) {
|
|
|
138
155
|
}
|
|
139
156
|
async function enable(deps, args) {
|
|
140
157
|
const { stack } = deps;
|
|
141
|
-
const flags = parseStackFlags(args, { booleans: ["--no-pull"], values: ["--context", "--tenant", "--host-address", "--bind-address"] }, ENABLE_USAGE);
|
|
158
|
+
const flags = parseStackFlags(args, { booleans: ["--no-pull"], values: ["--context", "--tenant", "--host-address", "--bind-address", "--probe-ports"] }, ENABLE_USAGE);
|
|
142
159
|
if (flags.rest.length)
|
|
143
160
|
throw usageError(`Usage: ${ENABLE_USAGE}`);
|
|
144
161
|
const context = flags.values.get("--context");
|
|
@@ -150,6 +167,8 @@ async function enable(deps, args) {
|
|
|
150
167
|
assertIPv4(hostAddress, "--host-address");
|
|
151
168
|
if (bindAddress)
|
|
152
169
|
assertIPv4(bindAddress, "--bind-address");
|
|
170
|
+
const probePortsFlag = flags.values.get("--probe-ports");
|
|
171
|
+
const probePorts = probePortsFlag === undefined ? undefined : parseProbePorts(probePortsFlag);
|
|
153
172
|
const name = flags.values.get("--tenant");
|
|
154
173
|
const tenant = await selectTenant(stack, name === undefined ? {} : { name });
|
|
155
174
|
await dockerPreflight(stack.docker);
|
|
@@ -169,12 +188,15 @@ async function enable(deps, args) {
|
|
|
169
188
|
gates: await pick(PREFERRED_PORTS.gates, persisted?.gatesPort),
|
|
170
189
|
egress: await pick(PREFERRED_PORTS.egress, persisted?.egressPort),
|
|
171
190
|
};
|
|
191
|
+
if (probePorts && [probePorts.allowed, probePorts.denied].some((port) => taken.has(port)))
|
|
192
|
+
throw usageError(`--probe-ports ${probePortsFlag} overlaps the sandbox ports ${ports.harness}, ${ports.gates} and ${ports.egress}; pick two others.`);
|
|
172
193
|
const images = stackImages(stack.env, { runtime: stack.runtimeVersion, studio: stack.studioVersion });
|
|
173
194
|
const installed = await installCluster(deps, {
|
|
174
195
|
context,
|
|
175
196
|
tenant: tenant.name,
|
|
176
197
|
...(hostAddress ? { hostAddress } : {}),
|
|
177
198
|
...(bindAddress ? { bindAddress } : {}),
|
|
199
|
+
...(probePorts ? { probePorts } : {}),
|
|
178
200
|
prePull: flags.booleans.has("--no-pull") ? [] : [DEFAULT_SANDBOX_IMAGE, images.runtime],
|
|
179
201
|
ports,
|
|
180
202
|
});
|
package/dist/sandbox/probe.d.ts
CHANGED
|
@@ -11,8 +11,9 @@ import type { Kube } from "./kubectl.js";
|
|
|
11
11
|
* to `<host>/32` on one port), must reach the allowed host port and nothing else: not the
|
|
12
12
|
* peer pod, not another port on the host.
|
|
13
13
|
*
|
|
14
|
-
* The host ports are served by a temporary busybox container published on the bind address
|
|
15
|
-
*
|
|
14
|
+
* The host ports are served by a temporary busybox container published on the bind address: two
|
|
15
|
+
* free ports, or `--probe-ports` where a firewall in front of the host (a cloud VPC's) opens only
|
|
16
|
+
* named ports to the pods. The namespace and the container are always removed.
|
|
16
17
|
*/
|
|
17
18
|
export interface ProbeDeps {
|
|
18
19
|
kube: Kube;
|
|
@@ -35,10 +36,20 @@ export interface ProbeResult {
|
|
|
35
36
|
/** Docker Desktop and OrbStack forward the host address to this machine's loopback. */
|
|
36
37
|
export declare function defaultBind(context: string, hostAddress: string): string;
|
|
37
38
|
export declare function assertIPv4(value: string, option: string): string;
|
|
39
|
+
/** The probe's two host ports. */
|
|
40
|
+
export interface ProbePorts {
|
|
41
|
+
/** The port the probe's policy allows. */
|
|
42
|
+
allowed: number;
|
|
43
|
+
/** The port it denies. */
|
|
44
|
+
denied: number;
|
|
45
|
+
}
|
|
46
|
+
/** `--probe-ports <allowed>,<denied>`: two different TCP ports. */
|
|
47
|
+
export declare function parseProbePorts(value: string): ProbePorts;
|
|
38
48
|
/** The last IPv4 address `nslookup` prints for the name (the first is the DNS server). */
|
|
39
49
|
export declare function parseNslookup(output: string): string | undefined;
|
|
40
50
|
export declare function runProbe(deps: ProbeDeps, options: {
|
|
41
51
|
hostAddress?: string;
|
|
42
52
|
bindAddress?: string;
|
|
53
|
+
probePorts?: ProbePorts;
|
|
43
54
|
prePull?: readonly string[];
|
|
44
55
|
}): Promise<ProbeResult>;
|
package/dist/sandbox/probe.js
CHANGED
|
@@ -15,6 +15,13 @@ export function assertIPv4(value, option) {
|
|
|
15
15
|
throw new CliError(`${option} must be an IPv4 address: ${value}`, 2);
|
|
16
16
|
return value;
|
|
17
17
|
}
|
|
18
|
+
/** `--probe-ports <allowed>,<denied>`: two different TCP ports. */
|
|
19
|
+
export function parseProbePorts(value) {
|
|
20
|
+
const ports = value.split(",").map((part) => Number(part.trim()));
|
|
21
|
+
if (ports.length !== 2 || !ports.every((port) => Number.isInteger(port) && port > 0 && port < 65536) || ports[0] === ports[1])
|
|
22
|
+
throw new CliError(`--probe-ports must be two different TCP ports, <allowed>,<denied>: ${value}`, 2);
|
|
23
|
+
return { allowed: ports[0], denied: ports[1] };
|
|
24
|
+
}
|
|
18
25
|
/** The last IPv4 address `nslookup` prints for the name (the first is the DNS server). */
|
|
19
26
|
export function parseNslookup(output) {
|
|
20
27
|
const answer = output.split(/Name:\s*\S+/).slice(1).join("\n");
|
|
@@ -59,10 +66,17 @@ export async function runProbe(deps, options) {
|
|
|
59
66
|
throw fail("pods cannot resolve host.docker.internal. Pass --host-address with the Docker host's address as pods reach it (172.17.0.1 for kind on Linux).");
|
|
60
67
|
}
|
|
61
68
|
const bindAddress = options.bindAddress ?? defaultBind(kube.context, hostAddress);
|
|
62
|
-
|
|
63
|
-
let denied
|
|
64
|
-
if (
|
|
69
|
+
let allowed;
|
|
70
|
+
let denied;
|
|
71
|
+
if (options.probePorts) {
|
|
72
|
+
({ allowed, denied } = options.probePorts);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
allowed = await deps.ports.pickFree();
|
|
65
76
|
denied = await deps.ports.pickFree();
|
|
77
|
+
if (denied === allowed)
|
|
78
|
+
denied = await deps.ports.pickFree();
|
|
79
|
+
}
|
|
66
80
|
const started = await docker.run([
|
|
67
81
|
"run", "--detach", "--rm", "--name", listener, "--label", "dev.nylorun.probe=true",
|
|
68
82
|
"--publish", `${bindAddress}:${allowed}:8080`, "--publish", `${bindAddress}:${denied}:8081`,
|
|
@@ -182,7 +182,11 @@ ${healthcheck("rustfs")} restart: unless-stopped
|
|
|
182
182
|
# MCP servers and HTTP tools on this machine: \`localhost\` in their URLs means the Docker host.
|
|
183
183
|
NYLORUN_ENDPOINT_LOOPBACK: docker-host
|
|
184
184
|
# Host tuning (timings, retries, internal sizes; JSON): from the shell that runs \`nylorun start\`.
|
|
185
|
-
NYLORUN_TUNING: \${NYLORUN_TUNING:-}
|
|
185
|
+
NYLORUN_TUNING: \${NYLORUN_TUNING:-}
|
|
186
|
+
# Providers whose models may call with this machine's own identity, such as google-vertex
|
|
187
|
+
# with Google's Application Default Credentials (ADR-71): from the shell that runs
|
|
188
|
+
# \`nylorun start\`. Empty: none. Every Tenant on this Host may use it.
|
|
189
|
+
NYLORUN_MODEL_HOST_IDENTITY: \${NYLORUN_MODEL_HOST_IDENTITY:-}${sandboxes ? SANDBOXES_GATEWAY_ENV : ""}
|
|
186
190
|
extra_hosts:
|
|
187
191
|
host.docker.internal: host-gateway # model servers, MCP servers and HTTP tools on this machine
|
|
188
192
|
volumes:
|
package/dist/tuning.d.ts
CHANGED
|
@@ -55,7 +55,7 @@ export declare const DEFAULT_CLI_TUNING: {
|
|
|
55
55
|
kubectlMarginMs: number;
|
|
56
56
|
/** Downloading the agent-sandbox manifest. */
|
|
57
57
|
manifestDownloadMs: number;
|
|
58
|
-
/**
|
|
58
|
+
/** How long the CRD may take to be Established, and `rollout status` of the controller, in seconds. */
|
|
59
59
|
crdWaitSeconds: number;
|
|
60
60
|
rolloutWaitSeconds: number;
|
|
61
61
|
/** How long to wait for the service account's token Secret. */
|
package/dist/tuning.js
CHANGED
|
@@ -67,7 +67,7 @@ export const DEFAULT_CLI_TUNING = deepFreeze({
|
|
|
67
67
|
kubectlMarginMs: 20_000,
|
|
68
68
|
/** Downloading the agent-sandbox manifest. */
|
|
69
69
|
manifestDownloadMs: 60_000,
|
|
70
|
-
/**
|
|
70
|
+
/** How long the CRD may take to be Established, and `rollout status` of the controller, in seconds. */
|
|
71
71
|
crdWaitSeconds: 60,
|
|
72
72
|
rolloutWaitSeconds: 180,
|
|
73
73
|
/** How long to wait for the service account's token Secret. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nylorun",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0-beta",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"description": "Set up and run local Nylorun Tenants (Runtime and Studio) with Docker Compose, and act on a Tenant through its Runtime and Management APIs",
|
|
@@ -30,12 +30,12 @@
|
|
|
30
30
|
"prepack": "npm run build"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@nylorun/admin": "0.19.
|
|
34
|
-
"@nylorun/agents": "0.
|
|
35
|
-
"@nylorun/core": "0.
|
|
33
|
+
"@nylorun/admin": "0.19.5-beta",
|
|
34
|
+
"@nylorun/agents": "0.29.0-beta",
|
|
35
|
+
"@nylorun/core": "0.29.0-beta"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@earendil-works/pi-ai": "1.
|
|
38
|
+
"@earendil-works/pi-ai": "1.3.0",
|
|
39
39
|
"@nylorun/agents": "file:../../sdks/agents",
|
|
40
40
|
"typescript": "npm:@typescript/typescript6@^6.0.2",
|
|
41
41
|
"@types/node": "^26.6.5",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"nylo": "dist/nylo.js"
|
|
47
47
|
},
|
|
48
48
|
"nylorun": {
|
|
49
|
-
"runtime": "0.
|
|
50
|
-
"studio": "0.
|
|
49
|
+
"runtime": "0.33.0-beta",
|
|
50
|
+
"studio": "0.33.0-beta"
|
|
51
51
|
}
|
|
52
52
|
}
|