nylorun 0.14.0-beta → 0.15.1-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 +131 -0
- package/README.md +226 -144
- package/dist/cli.js +142 -47
- package/dist/client/agents.d.ts +6 -0
- package/dist/client/agents.js +148 -0
- package/dist/client/artifacts.d.ts +4 -0
- package/dist/client/artifacts.js +210 -0
- package/dist/client/doctor.d.ts +7 -2
- package/dist/client/doctor.js +14 -13
- package/dist/client/environment-manifests.d.ts +10 -0
- package/dist/client/environment-manifests.js +173 -0
- package/dist/client/environments.d.ts +4 -0
- package/dist/client/environments.js +295 -0
- package/dist/client/io.d.ts +31 -0
- package/dist/client/io.js +72 -0
- package/dist/{stack → client}/mcp.d.ts +2 -2
- package/dist/{stack → client}/mcp.js +14 -12
- package/dist/client/model/configure.d.ts +1 -1
- package/dist/client/model/configure.js +1 -1
- package/dist/client/model/pi-ai.d.ts +3 -3
- package/dist/client/model/pi-ai.js +4 -4
- package/dist/client/models.d.ts +13 -0
- package/dist/client/models.js +203 -0
- package/dist/client/runtime.d.ts +40 -0
- package/dist/client/runtime.js +134 -0
- package/dist/client/sessions.d.ts +4 -0
- package/dist/client/sessions.js +295 -0
- package/dist/client/settings.d.ts +12 -0
- package/dist/client/settings.js +103 -0
- package/dist/client/signing-keys.d.ts +3 -0
- package/dist/client/signing-keys.js +32 -0
- package/dist/client/target.d.ts +33 -0
- package/dist/client/target.js +65 -0
- package/dist/client/tenant.d.ts +22 -0
- package/dist/client/tenant.js +176 -0
- package/dist/doctor.js +3 -3
- package/dist/nylo.js +20 -4
- package/dist/sandbox/cluster-file.d.ts +1 -1
- package/dist/sandbox/commands.d.ts +1 -1
- package/dist/sandbox/commands.js +7 -7
- package/dist/sandbox/manifests.d.ts +1 -1
- package/dist/sandbox/manifests.js +1 -1
- package/dist/sandbox/probe.d.ts +1 -1
- package/dist/stack/commands.d.ts +9 -4
- package/dist/stack/commands.js +22 -26
- package/dist/stack/compose-file.d.ts +1 -1
- package/dist/stack/compose-file.js +12 -12
- package/dist/stack/env-file.d.ts +3 -3
- package/dist/stack/env-file.js +2 -2
- package/dist/stack/images.d.ts +1 -1
- package/dist/stack/images.js +1 -1
- package/dist/stack/index.d.ts +1 -10
- package/dist/stack/index.js +3 -19
- package/dist/stack/keys.d.ts +2 -2
- package/dist/stack/keys.js +14 -11
- package/dist/stack/paths.d.ts +1 -1
- package/dist/stack/prepare.d.ts +1 -1
- package/dist/stack/stacks.js +1 -1
- package/dist/telemetry.d.ts +2 -2
- package/dist/telemetry.js +4 -4
- package/package.json +7 -6
- package/dist/client/access/commands.d.ts +0 -8
- package/dist/client/access/commands.js +0 -33
- package/dist/client/cli.d.ts +0 -2
- package/dist/client/cli.js +0 -106
- package/dist/client/installation/commands.d.ts +0 -7
- package/dist/client/installation/commands.js +0 -82
- package/dist/client/project/connection.d.ts +0 -36
- package/dist/client/project/connection.js +0 -80
- package/dist/client/project/env.d.ts +0 -3
- package/dist/client/project/env.js +0 -19
- package/dist/stack/sandbox.d.ts +0 -4
- package/dist/stack/sandbox.js +0 -107
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `nylorun mcp inspect <url>`: a remote MCP server's tools before an agent names it (R2b
|
|
3
|
-
* through the
|
|
4
|
-
*
|
|
5
|
-
* Tenant, and the Runtime never calls a tool. `--environment <id>` sends that Environment's
|
|
2
|
+
* `nylorun tenant mcp inspect <url>`: a remote MCP server's tools before an agent names it (R2b
|
|
3
|
+
* C12), through the Management API (`POST /v1/tenant/mcp/preview`) of the installation
|
|
4
|
+
* `target.ts` picks. It never starts a local Tenant, and the Runtime never calls a tool. `--environment <id>` sends that Environment's
|
|
6
5
|
* credential for the URL (protocol 11; it replaced `--vault`); an Environment's secrets are
|
|
7
6
|
* written on the Runtime API (`client.environments.secrets.put` of `@nylorun/agents`). Its
|
|
8
7
|
* `connect` subcommand (MCP OAuth) was removed in protocol 10, and says so.
|
|
9
8
|
*/
|
|
10
9
|
import { PROTOCOL_HEADER, PROTOCOL_VERSION } from "@nylorun/core/compatibility";
|
|
11
10
|
import { CliError } from "../errors.js";
|
|
12
|
-
import { parseStackFlags
|
|
11
|
+
import { parseStackFlags } from "../stack/commands.js";
|
|
12
|
+
import { connect } from "./target.js";
|
|
13
13
|
export const mcpUsage = ` mcp inspect <url> [--server <name>] [--environment <id>] [--sse] [--tenant <name>] [--json]
|
|
14
14
|
list a remote MCP server's tools, with Environment <id>'s
|
|
15
15
|
credential for <url> (none without it): model names, schema
|
|
16
16
|
sizes, renames, or that it needs a person's sign-in (the
|
|
17
17
|
Runtime calls no tool)`;
|
|
18
|
-
const INSPECT_USAGE = "nylorun mcp inspect <url> [--server <name>] [--environment <id>] [--sse] [--tenant <name>] [--json]";
|
|
18
|
+
const INSPECT_USAGE = "nylorun tenant mcp inspect <url> [--server <name>] [--environment <id>] [--sse] [--tenant <name>] [--json]";
|
|
19
19
|
/** Where a credential for an MCP server goes since protocol 11. */
|
|
20
|
-
const SECRET_HINT = "write it as a bearer or headers secret of an Environment (client.environments.secrets.put of @nylorun/agents, or Studio)";
|
|
20
|
+
const SECRET_HINT = "write it as a bearer or headers secret of an Environment (nylorun environments secrets put, client.environments.secrets.put of @nylorun/agents, or Studio)";
|
|
21
21
|
/** What `--vault` says since protocol 11. */
|
|
22
22
|
export const MCP_VAULT_REMOVED = "--vault was removed in protocol 11: a credential is a secret of an Environment, in its own vault. Pass --environment <id> to send that Environment's credential for the URL.";
|
|
23
23
|
/** What the removed `connect` subcommand says since protocol 10. */
|
|
24
|
-
export const MCP_CONNECT_REMOVED = `The connect subcommand of nylorun mcp was removed with MCP OAuth (protocol 10): ${SECRET_HINT.replace("write it", "write the server's key")}, or reach a server that needs a person's sign-in through a gateway (a secret with via). See its tools with nylorun mcp inspect <url> --environment <id>.`;
|
|
24
|
+
export const MCP_CONNECT_REMOVED = `The connect subcommand of nylorun mcp was removed with MCP OAuth (protocol 10): ${SECRET_HINT.replace("write it", "write the server's key")}, or reach a server that needs a person's sign-in through a gateway (a secret with via). See its tools with nylorun tenant mcp inspect <url> --environment <id>.`;
|
|
25
25
|
/** The preview takes up to 15 s in the Runtime; the hop gets a margin. */
|
|
26
26
|
const PREVIEW_TIMEOUT_MS = 30_000;
|
|
27
27
|
const DESCRIPTION_CHARS = 60;
|
|
@@ -36,8 +36,10 @@ async function inspect(deps, args) {
|
|
|
36
36
|
const name = flags.values.get("--server");
|
|
37
37
|
const environmentId = flags.values.get("--environment");
|
|
38
38
|
const tenant = flags.values.get("--tenant");
|
|
39
|
-
const api = await
|
|
40
|
-
|
|
39
|
+
const api = await connect(deps, "management", tenant);
|
|
40
|
+
if (!api.managementKey)
|
|
41
|
+
throw new CliError(`No management key for ${api.label}: set NYLORUN_MANAGEMENT_KEY.`, 1);
|
|
42
|
+
const response = await deps.fetch(`${api.url}/v1/tenant/mcp/preview`, {
|
|
41
43
|
method: "POST",
|
|
42
44
|
headers: {
|
|
43
45
|
authorization: `Bearer ${api.managementKey}`,
|
|
@@ -56,7 +58,7 @@ async function inspect(deps, args) {
|
|
|
56
58
|
});
|
|
57
59
|
const body = (await response.json().catch(() => ({})));
|
|
58
60
|
if (response.status === 404 && body.code === "not_found")
|
|
59
|
-
throw new CliError(
|
|
61
|
+
throw new CliError(`${api.label} runs a Runtime without MCP previews. Update it.`, 1);
|
|
60
62
|
if (!response.ok) {
|
|
61
63
|
const details = body.details;
|
|
62
64
|
const failure = typeof details?.failure === "string" ? ` (${details.failure})` : "";
|
|
@@ -143,7 +145,7 @@ function oneLine(text) {
|
|
|
143
145
|
const flat = text.replace(/\s+/g, " ").trim();
|
|
144
146
|
return flat.length > DESCRIPTION_CHARS ? `${flat.slice(0, DESCRIPTION_CHARS - 1)}…` : flat;
|
|
145
147
|
}
|
|
146
|
-
/** `nylorun mcp <inspect|connect> ...` */
|
|
148
|
+
/** `nylorun tenant mcp <inspect|connect> ...` */
|
|
147
149
|
export async function mcpCommand(deps, args) {
|
|
148
150
|
const [sub, ...rest] = args;
|
|
149
151
|
if (sub === "inspect")
|
|
@@ -46,7 +46,7 @@ export declare function configureProvider(options?: {
|
|
|
46
46
|
root?: string;
|
|
47
47
|
input?: Readable;
|
|
48
48
|
output?: Writable;
|
|
49
|
-
/**
|
|
49
|
+
/** Management API catalog from GET /v1/tenant/models. */
|
|
50
50
|
catalog?: ModelCatalog;
|
|
51
51
|
/** This installation's stable id, for OAuth flows that need one (OpenAI). */
|
|
52
52
|
deviceId?: () => string;
|
|
@@ -16,7 +16,7 @@ export class ConfigurationCancelled extends CliError {
|
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
18
|
* Build a pi-ai registry for interactive login. Catalog listing prefers the
|
|
19
|
-
*
|
|
19
|
+
* Management API payload when provided (F2-4).
|
|
20
20
|
*/
|
|
21
21
|
function modelsFor({ builtinModels, createProvider, stream, streamSimple }, selection, credentials, catalog) {
|
|
22
22
|
const models = builtinModels({ credentials });
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The pi-ai `
|
|
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
5
|
export declare const PI_AI_VERSION = "0.99.1";
|
|
6
|
-
/** What `
|
|
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;
|
|
9
9
|
builtinModels: typeof import("@earendil-works/pi-ai/providers/all").builtinModels;
|
|
@@ -12,7 +12,7 @@ export interface PiAi {
|
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
14
14
|
* pi-ai, with its provider SDKs about 100 MB, is not a dependency of nylorun, so
|
|
15
|
-
* `npx nylorun` stays small: only `
|
|
15
|
+
* `npx nylorun` stays small: only `nylorun tenant models set` needs it. It loads pi-ai from beside nylorun
|
|
16
16
|
* when it resolves there (the workspace, or a project that installs it), else from
|
|
17
17
|
* `<Nylorun home>/lib/pi-ai-<version>/`, which it installs with npm on first use.
|
|
18
18
|
*/
|
|
@@ -7,7 +7,7 @@ import { pathToFileURL } from "node:url";
|
|
|
7
7
|
import { nylorunHome } from "@nylorun/core/project";
|
|
8
8
|
import { CliError } from "../../errors.js";
|
|
9
9
|
/**
|
|
10
|
-
* The pi-ai `
|
|
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
13
|
export const PI_AI_VERSION = "0.99.1";
|
|
@@ -35,7 +35,7 @@ const notInstalled = (error) => error?.code === "ERR_MODULE_NOT_FOUND" &&
|
|
|
35
35
|
String(error.message).includes(`'${PI_AI}'`);
|
|
36
36
|
/**
|
|
37
37
|
* pi-ai, with its provider SDKs about 100 MB, is not a dependency of nylorun, so
|
|
38
|
-
* `npx nylorun` stays small: only `
|
|
38
|
+
* `npx nylorun` stays small: only `nylorun tenant models set` needs it. It loads pi-ai from beside nylorun
|
|
39
39
|
* when it resolves there (the workspace, or a project that installs it), else from
|
|
40
40
|
* `<Nylorun home>/lib/pi-ai-<version>/`, which it installs with npm on first use.
|
|
41
41
|
*/
|
|
@@ -49,7 +49,7 @@ export async function loadPiAi(options = {}) {
|
|
|
49
49
|
}
|
|
50
50
|
const directory = join(nylorunHome(options.home), "lib", `pi-ai-${PI_AI_VERSION}`);
|
|
51
51
|
if (!existsSync(join(directory, ENTRY))) {
|
|
52
|
-
(options.log ?? ((line) => console.error(line)))(`
|
|
52
|
+
(options.log ?? ((line) => console.error(line)))(`nylorun tenant models set uses ${PI_AI}@${PI_AI_VERSION} for the model providers' sign-in, which nylorun does not install. Installing it once into ${directory}…`);
|
|
53
53
|
await installInto(directory, options.install ?? npmInstall);
|
|
54
54
|
}
|
|
55
55
|
return (await import(pathToFileURL(join(directory, ENTRY)).href));
|
|
@@ -68,7 +68,7 @@ async function installInto(directory, install) {
|
|
|
68
68
|
});
|
|
69
69
|
}
|
|
70
70
|
catch (error) {
|
|
71
|
-
throw new CliError(`Could not install ${PI_AI}@${PI_AI_VERSION} for
|
|
71
|
+
throw new CliError(`Could not install ${PI_AI}@${PI_AI_VERSION} for nylorun tenant models set: ${error instanceof Error ? error.message : String(error)}. Install it beside nylorun (npm install ${PI_AI}@${PI_AI_VERSION}), or set the model provider in Studio.`, 1);
|
|
72
72
|
}
|
|
73
73
|
finally {
|
|
74
74
|
await rm(temporary, { recursive: true, force: true });
|
|
@@ -0,0 +1,13 @@
|
|
|
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)";
|
|
3
|
+
export declare const usageUsage = " usage [--scope tenant|agent|turn] [--id <id>] [--period day|month|total] [--tenant <name>]\n what the model calls used and cost (JSON)\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
|
+
/** `nylorun tenant models <verb> ...` */
|
|
5
|
+
export declare function modelsCommand(deps: StackDeps, args: readonly string[]): Promise<number>;
|
|
6
|
+
/** `nylorun tenant usage`: one scope's totals for one period, as JSON. */
|
|
7
|
+
export declare function usageCommand(deps: StackDeps, args: readonly string[]): Promise<number>;
|
|
8
|
+
/**
|
|
9
|
+
* `nylorun tenant calls`: every recorded model call from `--after`, one JSON object per line
|
|
10
|
+
* (NDJSON) on stdout, page by page (`--limit` a page) until caught up; then the cursor to resume
|
|
11
|
+
* from on stderr. Rows may repeat across runs: deduplicate by id.
|
|
12
|
+
*/
|
|
13
|
+
export declare function callsCommand(deps: StackDeps, args: readonly string[]): Promise<number>;
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nylorun tenant models | usage | calls`: the Tenant's model, what its model calls used, and the
|
|
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,
|
|
5
|
+
* for CI. A key is never an argument.
|
|
6
|
+
*/
|
|
7
|
+
import { randomUUID } from "node:crypto";
|
|
8
|
+
import { CliError } from "../errors.js";
|
|
9
|
+
import { parseStackFlags } from "../stack/commands.js";
|
|
10
|
+
import { printJson, printTable, readSecret } from "./io.js";
|
|
11
|
+
import { connect, managementClient } from "./target.js";
|
|
12
|
+
export const modelsUsage = ` models catalog [--tenant <name>] [--json]
|
|
13
|
+
every provider and model the Runtime knows
|
|
14
|
+
models providers [--tenant <name>] [--json]
|
|
15
|
+
the providers with a credential, and the one in use
|
|
16
|
+
models get [--tenant <name>] [--json]
|
|
17
|
+
the Tenant's model
|
|
18
|
+
models set [--tenant <name>] sign in to a model provider and set the Tenant's model
|
|
19
|
+
models set --provider <id> --model <id> [--base-url <url>] [--tenant <name>]
|
|
20
|
+
the same with an API key piped in on stdin (CI)
|
|
21
|
+
models select <provider> [--model <id>] [--tenant <name>]
|
|
22
|
+
use a provider that has a credential (default: its model)`;
|
|
23
|
+
export const usageUsage = ` usage [--scope tenant|agent|turn] [--id <id>] [--period day|month|total] [--tenant <name>]
|
|
24
|
+
what the model calls used and cost (JSON)
|
|
25
|
+
calls [--after <cursor>] [--limit <n>] [--tenant <name>]
|
|
26
|
+
every recorded model call, one JSON line each, until
|
|
27
|
+
caught up; prints the cursor to resume from on stderr`;
|
|
28
|
+
const usageError = (message = `Usage:\n${modelsUsage}`) => new CliError(message, 2);
|
|
29
|
+
async function client(deps, tenant) {
|
|
30
|
+
return managementClient(deps, await connect(deps, "management", tenant));
|
|
31
|
+
}
|
|
32
|
+
/** The Tenant's model in one line. */
|
|
33
|
+
function describe(view) {
|
|
34
|
+
if (!view.configured)
|
|
35
|
+
return "No model is set: run nylorun tenant models set.";
|
|
36
|
+
return [view.provider, view.model, view.authType, ...(view.baseUrl ? [view.baseUrl] : [])].join(" ");
|
|
37
|
+
}
|
|
38
|
+
/** `models catalog | providers | get`: a table or a line, else `--json`. */
|
|
39
|
+
async function read(deps, verb, args) {
|
|
40
|
+
const usage = `nylorun tenant models ${verb} [--tenant <name>] [--json]`;
|
|
41
|
+
const flags = parseStackFlags(args, { booleans: ["--json"], values: ["--tenant"] }, usage);
|
|
42
|
+
if (flags.rest.length)
|
|
43
|
+
throw usageError(`Usage: ${usage}`);
|
|
44
|
+
const admin = await client(deps, flags.values.get("--tenant"));
|
|
45
|
+
const json = flags.booleans.has("--json");
|
|
46
|
+
if (verb === "catalog") {
|
|
47
|
+
const catalog = await admin.models.catalog();
|
|
48
|
+
if (json)
|
|
49
|
+
printJson(deps, catalog);
|
|
50
|
+
else
|
|
51
|
+
printTable(deps, [
|
|
52
|
+
["PROVIDER", "MODEL", "READS"],
|
|
53
|
+
...catalog.providers.flatMap((provider) => provider.models.map((model) => [provider.id, model.id, model.inputs.join(",")])),
|
|
54
|
+
]);
|
|
55
|
+
}
|
|
56
|
+
else if (verb === "providers") {
|
|
57
|
+
const { providers } = await admin.models.providers();
|
|
58
|
+
if (json)
|
|
59
|
+
printJson(deps, providers);
|
|
60
|
+
else if (!providers.length)
|
|
61
|
+
deps.err("No provider has a credential: run nylorun tenant models set.");
|
|
62
|
+
else
|
|
63
|
+
printTable(deps, [
|
|
64
|
+
["PROVIDER", "MODEL", "AUTH", "IN USE"],
|
|
65
|
+
...providers.map((p) => [p.id, p.model, p.authType, p.active ? "yes" : ""]),
|
|
66
|
+
]);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
const view = await admin.models.get();
|
|
70
|
+
if (json)
|
|
71
|
+
printJson(deps, view);
|
|
72
|
+
else
|
|
73
|
+
deps.out(describe(view));
|
|
74
|
+
}
|
|
75
|
+
return 0;
|
|
76
|
+
}
|
|
77
|
+
const SET_USAGE = "nylorun tenant models set [--tenant <name>] | nylorun tenant models set --provider <id> --model <id> [--base-url <url>] [--tenant <name>]";
|
|
78
|
+
/**
|
|
79
|
+
* `models set`: at a terminal, the sign-in prompts; with `--provider` and `--model`, the API key
|
|
80
|
+
* on stdin, set as `PUT /v1/tenant/model` with an `api_key` credential (OAuth needs the prompts).
|
|
81
|
+
*/
|
|
82
|
+
async function set(deps, args) {
|
|
83
|
+
const flags = parseStackFlags(args, { values: ["--tenant", "--provider", "--model", "--base-url"] }, SET_USAGE);
|
|
84
|
+
if (flags.rest.length)
|
|
85
|
+
throw usageError(`Usage: ${SET_USAGE}`);
|
|
86
|
+
const provider = flags.values.get("--provider");
|
|
87
|
+
const model = flags.values.get("--model");
|
|
88
|
+
const baseUrl = flags.values.get("--base-url");
|
|
89
|
+
if (provider !== undefined || model !== undefined || baseUrl !== undefined) {
|
|
90
|
+
if (provider === undefined || model === undefined)
|
|
91
|
+
throw usageError(`Pass both --provider and --model. Usage: ${SET_USAGE}`);
|
|
92
|
+
const key = await readSecret(deps, "API key", `printenv OPENAI_API_KEY | nylorun tenant models set --provider ${provider} --model ${model}`);
|
|
93
|
+
const admin = await client(deps, flags.values.get("--tenant"));
|
|
94
|
+
const view = await admin.models.put({
|
|
95
|
+
idempotencyKey: randomUUID(),
|
|
96
|
+
provider,
|
|
97
|
+
model,
|
|
98
|
+
...(baseUrl ? { baseUrl } : {}),
|
|
99
|
+
auth: { type: "api_key", key },
|
|
100
|
+
});
|
|
101
|
+
deps.out(describe(view));
|
|
102
|
+
return 0;
|
|
103
|
+
}
|
|
104
|
+
const admin = await client(deps, flags.values.get("--tenant"));
|
|
105
|
+
const { ConfigurationCancelled, configureProvider, fetchModelCatalog } = await import("./model/configure.js");
|
|
106
|
+
const { putHostModel } = await import("./model/host-model.js");
|
|
107
|
+
const controller = new AbortController();
|
|
108
|
+
const cancel = (signal) => controller.abort(new ConfigurationCancelled(signal));
|
|
109
|
+
process.once("SIGINT", () => cancel("SIGINT"));
|
|
110
|
+
process.once("SIGTERM", () => cancel("SIGTERM"));
|
|
111
|
+
const catalog = await fetchModelCatalog(admin);
|
|
112
|
+
const prompted = await configureProvider({ signal: controller.signal, catalog });
|
|
113
|
+
await putHostModel(admin, prompted);
|
|
114
|
+
return 0;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* `models select <provider>`: the provider's stored model and endpoint unless `--model` says
|
|
118
|
+
* otherwise (the Runtime keeps only the endpoint the request names).
|
|
119
|
+
*/
|
|
120
|
+
async function select(deps, args) {
|
|
121
|
+
const usage = "nylorun tenant models select <provider> [--model <id>] [--tenant <name>]";
|
|
122
|
+
const flags = parseStackFlags(args, { values: ["--tenant", "--model"] }, usage);
|
|
123
|
+
if (flags.rest.length !== 1)
|
|
124
|
+
throw usageError(`Usage: ${usage}`);
|
|
125
|
+
const id = flags.rest[0];
|
|
126
|
+
const admin = await client(deps, flags.values.get("--tenant"));
|
|
127
|
+
const stored = (await admin.models.providers()).providers.find((p) => p.id === id);
|
|
128
|
+
if (!stored)
|
|
129
|
+
throw new CliError(`Provider ${id} has no credential: run nylorun tenant models set (nylorun tenant models providers lists those that do).`, 1);
|
|
130
|
+
const view = await admin.models.select({
|
|
131
|
+
idempotencyKey: randomUUID(),
|
|
132
|
+
provider: id,
|
|
133
|
+
model: flags.values.get("--model") ?? stored.model,
|
|
134
|
+
...(stored.baseUrl ? { baseUrl: stored.baseUrl } : {}),
|
|
135
|
+
});
|
|
136
|
+
deps.out(describe(view));
|
|
137
|
+
return 0;
|
|
138
|
+
}
|
|
139
|
+
/** `nylorun tenant models <verb> ...` */
|
|
140
|
+
export async function modelsCommand(deps, args) {
|
|
141
|
+
const [verb, ...rest] = args;
|
|
142
|
+
if (verb === "catalog" || verb === "providers" || verb === "get")
|
|
143
|
+
return read(deps, verb, rest);
|
|
144
|
+
if (verb === "set")
|
|
145
|
+
return set(deps, rest);
|
|
146
|
+
if (verb === "select")
|
|
147
|
+
return select(deps, rest);
|
|
148
|
+
throw usageError();
|
|
149
|
+
}
|
|
150
|
+
const SCOPES = ["tenant", "agent", "turn"];
|
|
151
|
+
const PERIODS = ["day", "month", "total"];
|
|
152
|
+
const USAGE_USAGE = "nylorun tenant usage [--scope tenant|agent|turn] [--id <id>] [--period day|month|total] [--tenant <name>]";
|
|
153
|
+
/** `nylorun tenant usage`: one scope's totals for one period, as JSON. */
|
|
154
|
+
export async function usageCommand(deps, args) {
|
|
155
|
+
const flags = parseStackFlags(args, { values: ["--tenant", "--scope", "--id", "--period"] }, USAGE_USAGE);
|
|
156
|
+
if (flags.rest.length)
|
|
157
|
+
throw usageError(`Usage: ${USAGE_USAGE}`);
|
|
158
|
+
const scope = flags.values.get("--scope") ?? "tenant";
|
|
159
|
+
const period = flags.values.get("--period") ?? "total";
|
|
160
|
+
const id = flags.values.get("--id");
|
|
161
|
+
if (!SCOPES.includes(scope))
|
|
162
|
+
throw usageError(`--scope is one of ${SCOPES.join(", ")}.`);
|
|
163
|
+
if (!PERIODS.includes(period))
|
|
164
|
+
throw usageError(`--period is one of ${PERIODS.join(", ")}.`);
|
|
165
|
+
if ((scope === "tenant") !== (id === undefined))
|
|
166
|
+
throw usageError(scope === "tenant" ? "--id names an agent or a turn: pass --scope agent or turn." : `--scope ${scope} needs --id.`);
|
|
167
|
+
const query = {
|
|
168
|
+
scope: scope,
|
|
169
|
+
period: period,
|
|
170
|
+
...(id === undefined ? {} : { id }),
|
|
171
|
+
};
|
|
172
|
+
printJson(deps, await (await client(deps, flags.values.get("--tenant"))).models.usage(query));
|
|
173
|
+
return 0;
|
|
174
|
+
}
|
|
175
|
+
const CALLS_USAGE = "nylorun tenant calls [--after <cursor>] [--limit <n>] [--tenant <name>]";
|
|
176
|
+
/**
|
|
177
|
+
* `nylorun tenant calls`: every recorded model call from `--after`, one JSON object per line
|
|
178
|
+
* (NDJSON) on stdout, page by page (`--limit` a page) until caught up; then the cursor to resume
|
|
179
|
+
* from on stderr. Rows may repeat across runs: deduplicate by id.
|
|
180
|
+
*/
|
|
181
|
+
export async function callsCommand(deps, args) {
|
|
182
|
+
const flags = parseStackFlags(args, { values: ["--tenant", "--after", "--limit"] }, CALLS_USAGE);
|
|
183
|
+
if (flags.rest.length)
|
|
184
|
+
throw usageError(`Usage: ${CALLS_USAGE}`);
|
|
185
|
+
const after = flags.values.get("--after");
|
|
186
|
+
const limitFlag = flags.values.get("--limit");
|
|
187
|
+
const limit = limitFlag === undefined ? undefined : Number(limitFlag);
|
|
188
|
+
if (limit !== undefined && !(Number.isInteger(limit) && limit >= 1 && limit <= 1000))
|
|
189
|
+
throw usageError("--limit is a whole number from 1 to 1000.");
|
|
190
|
+
const admin = await client(deps, flags.values.get("--tenant"));
|
|
191
|
+
let next = null;
|
|
192
|
+
for await (const page of admin.models.exportCalls({
|
|
193
|
+
...(after === undefined ? {} : { after }),
|
|
194
|
+
...(limit === undefined ? {} : { limit }),
|
|
195
|
+
})) {
|
|
196
|
+
for (const call of page.calls)
|
|
197
|
+
deps.out(JSON.stringify(call));
|
|
198
|
+
next = page.next;
|
|
199
|
+
}
|
|
200
|
+
if (next !== null)
|
|
201
|
+
deps.err(`Caught up. Resume with --after ${next}`);
|
|
202
|
+
return 0;
|
|
203
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { AgentsClient } from "@nylorun/agents";
|
|
2
|
+
import { CliError } from "../errors.js";
|
|
3
|
+
import { parseStackFlags, type StackDeps } from "../stack/commands.js";
|
|
4
|
+
import { type Connection } from "./target.js";
|
|
5
|
+
/** Flags every Runtime API command takes. */
|
|
6
|
+
export declare const RUNTIME_FLAGS: {
|
|
7
|
+
readonly values: readonly ["--tenant", "--as"];
|
|
8
|
+
readonly lists: readonly ["--scope"];
|
|
9
|
+
};
|
|
10
|
+
export declare const runtimeFlagsUsage: string;
|
|
11
|
+
export declare const usageError: (message: string) => CliError;
|
|
12
|
+
export type Flags = ReturnType<typeof parseStackFlags>;
|
|
13
|
+
/** `args` parsed with the Runtime flags added to `allowed`. */
|
|
14
|
+
export declare function parseFlags(args: readonly string[], allowed: {
|
|
15
|
+
booleans?: readonly string[];
|
|
16
|
+
values?: readonly string[];
|
|
17
|
+
lists?: readonly string[];
|
|
18
|
+
}, usage: string): Flags;
|
|
19
|
+
/** Exactly `count` positional arguments, else a usage error. */
|
|
20
|
+
export declare function positional(flags: Flags, count: number, usage: string): string[];
|
|
21
|
+
/** The Runtime API client of the installation the flags (or the environment) name. */
|
|
22
|
+
export declare function runtimeClient(deps: StackDeps, flags: Flags): Promise<{
|
|
23
|
+
client: AgentsClient;
|
|
24
|
+
connection: Connection;
|
|
25
|
+
}>;
|
|
26
|
+
/** Runs `command`, turning a Runtime's refusal into a `CliError` with its message (exit 1). */
|
|
27
|
+
export declare function runtimeCall<T>(command: () => Promise<T>): Promise<T>;
|
|
28
|
+
/** Standard input, whole. */
|
|
29
|
+
export declare function readStdin(): Promise<Buffer>;
|
|
30
|
+
/** A JSON object from file `path`, or from standard input when `path` is `-`. */
|
|
31
|
+
export declare function readJson(path: string, what: string): Promise<Record<string, unknown>>;
|
|
32
|
+
/** A JSON value given on the command line. */
|
|
33
|
+
export declare function parseJsonArgument(text: string, what: string): unknown;
|
|
34
|
+
/** `key=value` pairs (`--label`), as a record. */
|
|
35
|
+
export declare function labelsOf(flags: Flags, name?: string): Record<string, string> | undefined;
|
|
36
|
+
/** A positive integer flag, when given. */
|
|
37
|
+
export declare function integerFlag(flags: Flags, name: string): number | undefined;
|
|
38
|
+
export { printJson, printTable } from "./io.js";
|
|
39
|
+
/** A page's `next cursor` hint, when there is one. */
|
|
40
|
+
export declare function printCursor(deps: StackDeps, cursor: string | null | undefined): void;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the Runtime API groups (`agents`, `sessions`, `environments`, `artifacts`) share: their
|
|
3
|
+
* flags, the `@nylorun/agents` client of the installation `target.ts` picks, acting for a subject
|
|
4
|
+
* with `--as`, and how they read input and print output.
|
|
5
|
+
*/
|
|
6
|
+
import { readFile } from "node:fs/promises";
|
|
7
|
+
import { AgentsClient, RuntimeError, SUBJECT_SCOPES, } from "@nylorun/agents";
|
|
8
|
+
import { CliError } from "../errors.js";
|
|
9
|
+
import { parseStackFlags } from "../stack/commands.js";
|
|
10
|
+
import { connect } from "./target.js";
|
|
11
|
+
/** Flags every Runtime API command takes. */
|
|
12
|
+
export const RUNTIME_FLAGS = { values: ["--tenant", "--as"], lists: ["--scope"] };
|
|
13
|
+
export const runtimeFlagsUsage = `Every command takes --tenant <name>, and --as <subject> [--scope <scope>]... to act for a
|
|
14
|
+
person (default scope sessions:own; scopes: ${SUBJECT_SCOPES.join(", ")}).`;
|
|
15
|
+
export const usageError = (message) => new CliError(message, 2);
|
|
16
|
+
/** `args` parsed with the Runtime flags added to `allowed`. */
|
|
17
|
+
export function parseFlags(args, allowed, usage) {
|
|
18
|
+
return parseStackFlags(args, {
|
|
19
|
+
booleans: allowed.booleans ?? [],
|
|
20
|
+
values: [...RUNTIME_FLAGS.values, ...(allowed.values ?? [])],
|
|
21
|
+
lists: [...RUNTIME_FLAGS.lists, ...(allowed.lists ?? [])],
|
|
22
|
+
}, usage);
|
|
23
|
+
}
|
|
24
|
+
/** Exactly `count` positional arguments, else a usage error. */
|
|
25
|
+
export function positional(flags, count, usage) {
|
|
26
|
+
if (flags.rest.length !== count)
|
|
27
|
+
throw usageError(`Usage: ${usage}`);
|
|
28
|
+
return flags.rest;
|
|
29
|
+
}
|
|
30
|
+
/** The Runtime API client of the installation the flags (or the environment) name. */
|
|
31
|
+
export async function runtimeClient(deps, flags) {
|
|
32
|
+
const scopes = flags.lists.get("--scope");
|
|
33
|
+
const subject = flags.values.get("--as");
|
|
34
|
+
if (scopes && subject === undefined)
|
|
35
|
+
throw usageError("--scope needs --as <subject>.");
|
|
36
|
+
for (const scope of scopes ?? [])
|
|
37
|
+
if (!SUBJECT_SCOPES.includes(scope))
|
|
38
|
+
throw usageError(`Unknown scope ${scope}: one of ${SUBJECT_SCOPES.join(", ")}.`);
|
|
39
|
+
const connection = await connect(deps, "runtime", flags.values.get("--tenant"));
|
|
40
|
+
const fetch = (input, init) => deps.fetch(input instanceof Request ? input.url : String(input), init);
|
|
41
|
+
const token = connection.token;
|
|
42
|
+
if (!token && !connection.applicationKey)
|
|
43
|
+
throw new CliError(`No application key for ${connection.label}: set NYLORUN_SERVER_KEY.`, 1);
|
|
44
|
+
const base = new AgentsClient({
|
|
45
|
+
url: connection.url,
|
|
46
|
+
fetch,
|
|
47
|
+
...(token
|
|
48
|
+
? { token: { get: async () => token, invalidate: () => { } } }
|
|
49
|
+
: { key: connection.applicationKey }),
|
|
50
|
+
});
|
|
51
|
+
const client = subject === undefined
|
|
52
|
+
? base
|
|
53
|
+
: base.as(subject, scopes ? { scopes: scopes } : {});
|
|
54
|
+
return { client, connection };
|
|
55
|
+
}
|
|
56
|
+
/** Runs `command`, turning a Runtime's refusal into a `CliError` with its message (exit 1). */
|
|
57
|
+
export async function runtimeCall(command) {
|
|
58
|
+
try {
|
|
59
|
+
return await command();
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (!(error instanceof RuntimeError))
|
|
63
|
+
throw error;
|
|
64
|
+
const body = error.body;
|
|
65
|
+
const message = typeof body?.message === "string" ? body.message : error.message;
|
|
66
|
+
const code = typeof body?.code === "string" ? ` (${body.code})` : "";
|
|
67
|
+
throw new CliError(`${message.replace(/\.$/, "")}${code}. HTTP ${error.status}.`, 1);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/** Standard input, whole. */
|
|
71
|
+
export async function readStdin() {
|
|
72
|
+
const chunks = [];
|
|
73
|
+
for await (const chunk of process.stdin)
|
|
74
|
+
chunks.push(chunk);
|
|
75
|
+
return Buffer.concat(chunks);
|
|
76
|
+
}
|
|
77
|
+
/** A JSON object from file `path`, or from standard input when `path` is `-`. */
|
|
78
|
+
export async function readJson(path, what) {
|
|
79
|
+
let text;
|
|
80
|
+
try {
|
|
81
|
+
text = path === "-" ? (await readStdin()).toString("utf8") : await readFile(path, "utf8");
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
throw new CliError(`Cannot read ${what.toLowerCase()} from ${path}: ${error.message}`, 1);
|
|
85
|
+
}
|
|
86
|
+
let value;
|
|
87
|
+
try {
|
|
88
|
+
value = JSON.parse(text);
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
throw usageError(`${what} is not JSON: ${error.message}`);
|
|
92
|
+
}
|
|
93
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
94
|
+
throw usageError(`${what} must be a JSON object.`);
|
|
95
|
+
return value;
|
|
96
|
+
}
|
|
97
|
+
/** A JSON value given on the command line. */
|
|
98
|
+
export function parseJsonArgument(text, what) {
|
|
99
|
+
try {
|
|
100
|
+
return JSON.parse(text);
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
throw usageError(`${what} is not JSON: ${error.message}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/** `key=value` pairs (`--label`), as a record. */
|
|
107
|
+
export function labelsOf(flags, name = "--label") {
|
|
108
|
+
const pairs = flags.lists.get(name);
|
|
109
|
+
if (!pairs)
|
|
110
|
+
return undefined;
|
|
111
|
+
const labels = {};
|
|
112
|
+
for (const pair of pairs) {
|
|
113
|
+
const at = pair.indexOf("=");
|
|
114
|
+
if (at <= 0)
|
|
115
|
+
throw usageError(`${name} must be key=value, not ${pair}`);
|
|
116
|
+
labels[pair.slice(0, at)] = pair.slice(at + 1);
|
|
117
|
+
}
|
|
118
|
+
return labels;
|
|
119
|
+
}
|
|
120
|
+
/** A positive integer flag, when given. */
|
|
121
|
+
export function integerFlag(flags, name) {
|
|
122
|
+
const raw = flags.values.get(name);
|
|
123
|
+
if (raw === undefined)
|
|
124
|
+
return undefined;
|
|
125
|
+
if (!/^\d+$/.test(raw))
|
|
126
|
+
throw usageError(`${name} must be a whole number, not ${raw}`);
|
|
127
|
+
return Number(raw);
|
|
128
|
+
}
|
|
129
|
+
export { printJson, printTable } from "./io.js";
|
|
130
|
+
/** A page's `next cursor` hint, when there is one. */
|
|
131
|
+
export function printCursor(deps, cursor) {
|
|
132
|
+
if (cursor)
|
|
133
|
+
deps.err(`More: --cursor ${cursor}`);
|
|
134
|
+
}
|