@nylorun/agents 0.10.0-beta → 0.11.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 CHANGED
@@ -1,5 +1,39 @@
1
1
  # @nylorun/agents
2
2
 
3
+ ## 0.11.0-beta
4
+
5
+ ### Major Changes
6
+
7
+ - 5ca1923: **Clients for one Tenant per installation: a stack per project, nothing selects a Tenant.** Upgrade these with the Runtime; they speak protocol 5. Existing stacks are left as they are: see MIGRATION.md.
8
+
9
+ - **Breaking (`nylorun`): one stack per project.** `nylorun start` in a project creates the project's stack (named after the project directory, or `--name`; Host root `~/.nylorun/stacks/<name>/`, Compose project `nylorun-<name>`, its own free ports and volumes), waits for its Runtime to create the stack's Tenant, writes the Project link (`.nylorun/link.json` format 2: `stack`, `hostUrl`, `hostId`, `tenantId`) and `.nylorun/credentials.json` (the key of the derived principal `project`, derived from the stack's admin key), and seeds the model provider from the project's `.env`. `nylorun ls` lists the machine's stacks and `nylorun delete <name>` removes one with its volumes and Host root. `nylorun status` shows the stack's Tenant; `nylorun studio` opens it. The old single stack under `~/.nylorun` is never touched: `start` notes it, and `nylorun legacy stop|delete` handles it. Every stack command takes `--name <stack>` (or `NYLORUN_STACK`, or the Project link's stack); `start --no-link` starts a stack without linking the directory; `nylorun reset` resets the selected stack only. The runtime container's healthcheck is now `/health`, so a Tenant that cannot open is reported by `start` from the Admin status at once instead of after a 300 s wait. `NYLORUN_HOME` still overrides the Host root. Stacks start and stop only when you say so.
10
+ - **Breaking (`@nylorun/cli`): no Tenant commands.** `nylo tenant create|use|list|current|delete` are removed; `nylo status`, `nylo reset` and `nylo endpoints` replace `nylo tenant status|reset|endpoints` on the linked installation. `nylo env` prints `NYLORUN_RUNTIME_URL` and `NYLORUN_SERVER_KEY` only. `nylo` no longer writes Project links.
11
+ - **Breaking (`@nylorun/agents`): no Tenant to name.** The `tenant` option (`createClient`, `Transport`, `resolveConnection`, `createActionHandler({ runtime })`, `JwksCache`) and `NYLORUN_TENANT` are gone, and no request sends `Nylorun-Tenant`; a connection is a URL and a key. A Project link of format 0 or 1 is refused with `connection_missing`, naming `npx nylorun start`. `verifyDeliveryToken`'s `tenantId` is optional: without it any Tenant issuer is accepted, since the installation's keys bind it. `TENANT_HEADER` is no longer re-exported.
12
+ - **Breaking (`@nylorun/admin`): status only.** `createTenant`, `listTenants`, `getTenant` and `deleteTenant` are removed; `status().tenant` names the Host's Tenant, and `deriveTenantKey` / `deriveStudioToken` derive its keys. Local Host resolution reads the stack's Host root (`stack` option, `NYLORUN_STACK`, or the Project link's `stack`); `NYLORUN_HOME` and `home` still override it. `stackHostRoot(name)` is exported.
13
+ - **Breaking (`@nylorun/studio`): Studio serves its installation's Tenant.** The Tenant picker, list and create are gone, with `/_studio/tenants`; `/` opens `/tenants/<id>`. The `/tenants/:tenant` routes and the login token's `tenant` claim stay for embedders and must name that Tenant. The proxy sends no `Nylorun-Tenant`.
14
+ - **`@nylorun/create-agent`:** the next steps are `npx nylorun start`, then `npm run dev`.
15
+ - `@nylorun/core`: `ProjectLinkFileSchema` accepts format 2 with `stack`, and `tenantId` is optional; `ERROR_CODES` loses `tenant_conflict` and `active_work`.
16
+
17
+ - 5ca1923: **One Tenant per installation: a database per Tenant, protocol 5.** A Runtime serves exactly one Tenant, the one its Postgres database holds. Two Tenants are two installations.
18
+
19
+ - **Breaking: fresh start.** The Tenant's state is in the fixed schema `nylorun` of its own database and its record in `nylorun_streams`, keyed by session (no `tenant_id`). A database written by an earlier Runtime (`tenant_<id>` schemas, or a record keyed by Tenant) is refused: the Host stays up but not ready, and `/v1/admin/status` names the cause `database-layout-old`. Point the Runtime at a new database (with the local stack, a new stack); the old one is never changed.
20
+ - **The Host creates its Tenant** on first start, in the migration transaction: `NYLORUN_TENANT_ID` (default a new id), `NYLORUN_TENANT_NAME` (default `default`), the Studio principal and the derived principals of `NYLORUN_DERIVED_PRINCIPALS` (comma-separated, default `project`), whose keys the admin key derives (`deriveTenantKey`). Later starts open the same Tenant and add derived principals configured since.
21
+ - **Breaking: no Tenant catalog.** `/v1/admin/tenants` and `/v1/admin/tenants/{tenantId}` are gone (404). `AdminStatus.tenants[]` is replaced by `AdminStatus.tenant` (`id`, `name`, `state: open | unavailable`, `envelope`, and `cause` when it could not be opened). `CreateTenantRequestSchema`, `AdminTenantSchema`, `AdminTenantStatusSchema`, `AdminTenantListSchema` and `QuarantineSchema` leave `@nylorun/core`; `HostTenantSchema` and `TenantCauseSchema` replace them. `@nylorun/admin`'s `listTenants`, `getTenant`, `deleteTenant` and `createTenant` are removed (see below).
22
+ - **Readiness instead of quarantine.** A Tenant that cannot be opened (`schema-too-new`, `kek-missing`, `migration-failed`, `envelope-invalid`, `database-layout-old`, …) fails `/ready` (check `tenant`, which replaces `discovery`) and is reported with its repair in `/v1/admin/status` and the log; every Tenant request gets the opaque 404. A failure outside it (Postgres unreachable) is retried.
23
+ - **Protocol 5.** `PROTOCOL_VERSION = 5`; the Host serves protocols 4 and 5 for one release. Clients no longer require `runtime-tenants`; the Host still advertises it. No request needs `Nylorun-Tenant`: a request without it reaches the Host's Tenant, and one naming another Tenant (or a malformed one), or a publishable key of another Tenant, gets the opaque 404. The OpenAPI documents drop the header parameter and the Admin Tenant routes.
24
+ - **Paths.** The Tenant directory is `<Host root>/tenant/` (was `tenants/<id>/`); `trash/` and the SQLite move to it are gone. The gates service serves its database's Tenant, and its `Nylorun-Tenant` header is optional (when sent, it must name that Tenant).
25
+ - **Breaking: `startEphemeralRuntime` needs a database; the in-memory Session Store is removed.** `StartEphemeralRuntimeOptions.database` is required: a Postgres URL, for which the Runtime opens a pool and ends it on `close()`, or a pool the caller ends. It creates its Tenant in that database through the same bootstrap as a Host, or serves the Tenant the database already holds; the data stays after `close()`, so give each test Tenant a database of its own. Durable Streams and scheduling stay in process. See `MIGRATION.md`.
26
+
27
+ ### Patch Changes
28
+
29
+ - Pin core to the tested release.
30
+ - Updated dependencies [fed780d]
31
+ - Updated dependencies [fed780d]
32
+ - Updated dependencies [7f4c3f1]
33
+ - Updated dependencies [5ca1923]
34
+ - Updated dependencies [5ca1923]
35
+ - @nylorun/core@0.10.0-beta
36
+
3
37
  ## 0.10.0-beta
4
38
 
5
39
  ### Major Changes
package/README.md CHANGED
@@ -24,11 +24,11 @@ The Runtime delivers each tool call, hook and workflow function of these agents
24
24
  to `url`, signed with a short-lived **delivery token** that the handler checks
25
25
  (Tenant, URL, Action, generation and body) before any code runs. `register`
26
26
  saves the definitions, registers the URL and pings it through the Runtime. A
27
- process that only serves Actions needs no key: pass `runtime: { url, tenant }`
28
- and it reads the Tenant's public keys. Mark a long tool
27
+ process that only serves Actions needs no key: pass `runtime: { url }` and it
28
+ reads the Tenant's public keys. Mark a long tool
29
29
  `tool({ …, background: true })`: the handler answers at once, heartbeats and
30
- posts the result. `npx @nylorun/cli tenant endpoints` shows each endpoint and
31
- how its deliveries are doing.
30
+ posts the result. `npx @nylorun/cli endpoints` shows each endpoint and how its
31
+ deliveries are doing.
32
32
 
33
33
  The URL must be one the Runtime can reach: `localhost` on the local stack
34
34
  (its Runtime runs in Docker and maps `localhost` to this machine), a public URL
@@ -36,21 +36,26 @@ in production, or a tunnel (ngrok, Cloudflare Tunnel) for a remote Runtime.
36
36
 
37
37
  `connectAgents` and executors were removed in protocol 3: mount
38
38
  `createActionHandler` instead (see [MIGRATION.md](../MIGRATION.md)). The handler
39
- finds the Runtime through the three `NYLORUN_*` variables or the Project link
40
- that `npx @nylorun/cli tenant create` writes; with neither, `register` fails
41
- with `connection_missing` and names those steps. See [MIGRATION.md](../MIGRATION.md#runtime-clients-and-admin-api-breaking-beta)
39
+ finds the Runtime through the two `NYLORUN_*` variables or the Project link
40
+ that `npx nylorun start` writes; with neither, `register` fails with
41
+ `connection_missing` and names those steps. See [MIGRATION.md](../MIGRATION.md#runtime-clients-and-admin-api-breaking-beta)
42
42
  for upgrading from `nylorun serve`.
43
43
 
44
44
  ## Connection resolution
45
45
 
46
46
  `resolveConnection` / `createClient()` / `createActionHandler({ agents })`:
47
47
 
48
- 1. Explicit `{ url, tenant, key }`
49
- 2. Environment — if any of `NYLORUN_RUNTIME_URL`, `NYLORUN_TENANT` or
50
- `NYLORUN_SERVER_KEY` is set, all three must be present
51
- 3. Project link — `.nylorun/link.json` + `credentials.json` (the application key)
48
+ 1. Explicit `{ url, key }`
49
+ 2. Environment — if `NYLORUN_RUNTIME_URL` or `NYLORUN_SERVER_KEY` is set, both
50
+ must be present
51
+ 3. Project link — `.nylorun/link.json` + `credentials.json` (the application
52
+ key), written by `npx nylorun start`. A link from before one Tenant per
53
+ installation (format 0 or 1) fails with `connection_missing`: run
54
+ `npx nylorun start` in the project again.
52
55
 
53
- Sources never mix. Partial environment fails with `connection_missing`.
56
+ Sources never mix. Partial environment fails with `connection_missing`. A
57
+ Runtime serves one Tenant, so nothing names it: the `tenant` option and
58
+ `NYLORUN_TENANT` are gone (protocol 5).
54
59
 
55
60
  ```ts
56
61
  import { Agent, createActionHandler, createClient, tool } from "@nylorun/agents";
@@ -72,7 +77,6 @@ const assistant = Agent({ id: "assistant", name: "Assistant" })
72
77
  const client = createClient({
73
78
  url: process.env.NYLORUN_RUNTIME_URL,
74
79
  key: process.env.NYLORUN_SERVER_KEY,
75
- tenant: process.env.NYLORUN_TENANT,
76
80
  });
77
81
  await client.saveAgent(assistant, { implementationVersion: "app-1" });
78
82
  const session = await client.createSession({
@@ -86,10 +90,7 @@ const history = await session.history();
86
90
  const actions = createActionHandler({
87
91
  agents: [assistant],
88
92
  implementationVersion: "app-1",
89
- runtime: {
90
- url: process.env.NYLORUN_RUNTIME_URL!,
91
- tenant: process.env.NYLORUN_TENANT!,
92
- },
93
+ runtime: { url: process.env.NYLORUN_RUNTIME_URL! },
93
94
  url: "https://app.example.com/nylorun/actions",
94
95
  onError: console.error,
95
96
  });
@@ -102,11 +103,11 @@ await createActionHandler({ agents: [assistant], client }).register({
102
103
 
103
104
  ```sh
104
105
  eval "$(npx @nylorun/cli env)"
105
- # → NYLORUN_RUNTIME_URL, NYLORUN_SERVER_KEY, NYLORUN_TENANT
106
+ # → NYLORUN_RUNTIME_URL, NYLORUN_SERVER_KEY
106
107
  ```
107
108
 
108
- Every request sets `Nylorun-Tenant` and `Nylorun-Protocol`. A `tenant` field in a
109
- body or query is never read from caller input. Before the first authenticated
109
+ Every request sets `Nylorun-Protocol` (5) and no `Nylorun-Tenant`: the Runtime
110
+ serves one Tenant. Before the first authenticated
110
111
  request, `Transport` fetches `/health` once, checks protocol compatibility, and
111
112
  throws `IncompatibleRuntimeError` / `incompatible_host` when the Host range or
112
113
  required features do not match. Re-exports include `PROTOCOL_FEATURES`,
@@ -494,7 +495,7 @@ const { token, expiresAt } = await app.tokens.create({
494
495
  });
495
496
  ```
496
497
 
497
- The client sends it as `Authorization: Bearer <token>` with `Nylorun-Tenant`.
498
+ The client sends it as `Authorization: Bearer <token>`.
498
499
  Tokens carry only `sessions:own`, `vaults:own` and `agents:read`, live at most
499
500
  15 minutes, and see only `{ agentId, name, description }` of their role's
500
501
  agents. A token that expired or was revoked answers `401` with
@@ -16,7 +16,6 @@ export interface ActionHandlerOptions {
16
16
  */
17
17
  runtime?: {
18
18
  url: string;
19
- tenant: string;
20
19
  fetch?: typeof fetch;
21
20
  };
22
21
  /** The Tenant's public keys, instead of reading them from the Runtime. */
@@ -41,13 +41,12 @@ export function createActionHandler(options) {
41
41
  if (request.method !== "POST")
42
42
  return answer(405, "method_not_allowed", "An Action endpoint accepts POST only");
43
43
  const body = new Uint8Array(await request.arrayBuffer());
44
- const { tenant, url, fetch, keys } = await verificationOf();
44
+ const { url, fetch, keys } = await verificationOf();
45
45
  const token = request.headers.get(SIGNATURE_HEADER);
46
46
  let claims;
47
47
  try {
48
48
  claims = await verifyDeliveryToken(token, {
49
49
  keys: keys.lookup,
50
- tenantId: tenant,
51
50
  body,
52
51
  ...(audience === undefined ? {} : { audience }),
53
52
  });
@@ -96,7 +95,7 @@ export function createActionHandler(options) {
96
95
  agent,
97
96
  token: token,
98
97
  sandbox: delivery.sandbox,
99
- runtime: { url, tenant, ...(fetch ? { fetch } : {}) },
98
+ runtime: { url, ...(fetch ? { fetch } : {}) },
100
99
  report,
101
100
  });
102
101
  options.waitUntil?.(work);
@@ -106,7 +105,7 @@ export function createActionHandler(options) {
106
105
  ? createActionSandbox({
107
106
  transport: (callbacks = callbacks
108
107
  ? callbacks.withKey(token)
109
- : new Transport({ url, tenant, key: token, ...(fetch ? { fetch } : {}) })),
108
+ : new Transport({ url, key: token, ...(fetch ? { fetch } : {}) })),
110
109
  actionId: action.actionId,
111
110
  signal: request.signal,
112
111
  })
@@ -254,22 +253,19 @@ async function runInBackground(input) {
254
253
  }
255
254
  async function resolveVerification(options, clientOf) {
256
255
  if (options.runtime) {
257
- const { url, tenant, fetch } = options.runtime;
256
+ const { url, fetch } = options.runtime;
258
257
  return {
259
- tenant,
260
258
  url,
261
259
  ...(fetch ? { fetch } : {}),
262
- keys: new JwksCache(options.jwks ?? { url, tenant, ...(fetch ? { fetch } : {}) }),
260
+ keys: new JwksCache(options.jwks ?? { url, ...(fetch ? { fetch } : {}) }),
263
261
  };
264
262
  }
265
263
  const { transport } = await clientOf();
266
264
  return {
267
- tenant: transport.tenant,
268
265
  url: transport.url,
269
266
  fetch: transport.fetcher,
270
267
  keys: new JwksCache(options.jwks ?? {
271
268
  url: transport.url,
272
- tenant: transport.tenant,
273
269
  key: transport.key,
274
270
  fetch: transport.fetcher,
275
271
  }),
package/dist/client.js CHANGED
@@ -2,15 +2,12 @@ export * from "./session-client.js";
2
2
  import { AgentsClient } from "./session-client.js";
3
3
  import { resolveConnection } from "./connection.js";
4
4
  export function createClient(destination = {}) {
5
- if (destination.url !== undefined ||
6
- destination.key !== undefined ||
7
- destination.tenant !== undefined) {
5
+ if (destination.url !== undefined || destination.key !== undefined) {
8
6
  return new AgentsClient(destination);
9
7
  }
10
8
  return resolveConnection().then((connection) => new AgentsClient({
11
9
  url: connection.url,
12
10
  key: connection.key,
13
- tenant: connection.tenant,
14
11
  ...(destination.fetch ? { fetch: destination.fetch } : {}),
15
12
  }));
16
13
  }
@@ -1,7 +1,6 @@
1
1
  import type { ErrorCode } from "@nylorun/core/compatibility";
2
2
  export interface ResolvedConnection {
3
3
  url: string;
4
- tenant: string;
5
4
  key: string;
6
5
  source: "options" | "environment" | "project-link";
7
6
  }
@@ -9,10 +8,12 @@ export declare class ConnectionError extends Error {
9
8
  readonly code: ErrorCode;
10
9
  constructor(message: string);
11
10
  }
12
- /** Resolve Tenant API connection: options → environment → project link (D§7.1). */
11
+ /**
12
+ * Resolve Tenant API connection: options → environment → project link (D§7.1). A connection is a
13
+ * Runtime URL and a key: the Runtime serves one Tenant, so nothing names it.
14
+ */
13
15
  export declare function resolveConnection(options?: {
14
16
  url?: string;
15
- tenant?: string;
16
17
  key?: string;
17
18
  cwd?: string;
18
19
  }): Promise<ResolvedConnection>;
@@ -11,8 +11,8 @@ export class ConnectionError extends Error {
11
11
  }
12
12
  function missing(tried) {
13
13
  throw new ConnectionError(`connection_missing: no Runtime connection found (tried ${tried.join(", ")}). ` +
14
- `Start the local stack with "npx nylorun up" and link this project with "npx @nylorun/cli tenant create", ` +
15
- `or set NYLORUN_RUNTIME_URL, NYLORUN_TENANT and NYLORUN_SERVER_KEY.`);
14
+ `Run "npx nylorun start" in this project to start its stack and link it, ` +
15
+ `or set NYLORUN_RUNTIME_URL and NYLORUN_SERVER_KEY.`);
16
16
  }
17
17
  function stripTrailingSlash(url) {
18
18
  return url.replace(/\/$/, "");
@@ -37,30 +37,32 @@ async function readProjectLink(startDir) {
37
37
  throw error;
38
38
  }
39
39
  const link = ProjectLinkFileSchema.parse(JSON.parse(linkRaw));
40
+ // Formats 0 and 1 named a Tenant on a multi-Tenant Host of an older Runtime.
41
+ if (link.format < 2)
42
+ throw new ConnectionError(`connection_missing: the Project link at ${linkPath} is for a stack of an older ` +
43
+ `Runtime, before one Tenant per installation. Run "npx nylorun start" in this project ` +
44
+ `to start its own stack and link it again.`);
40
45
  const credentials = ProjectCredentialsFileSchema.parse(JSON.parse(await readFile(join(nylorun, "credentials.json"), "utf8")));
41
46
  return {
42
47
  url: stripTrailingSlash(link.hostUrl),
43
- tenant: link.tenantId,
44
48
  key: credentials.applicationKey,
45
49
  source: "project-link",
46
50
  };
47
51
  }
48
52
  }
49
- /** Resolve Tenant API connection: options → environment → project link (D§7.1). */
53
+ /**
54
+ * Resolve Tenant API connection: options → environment → project link (D§7.1). A connection is a
55
+ * Runtime URL and a key: the Runtime serves one Tenant, so nothing names it.
56
+ */
50
57
  export async function resolveConnection(options) {
51
58
  const tried = [];
52
59
  const optionUrl = options?.url;
53
- const optionTenant = options?.tenant;
54
60
  const optionKey = options?.key;
55
- const anyOption = optionUrl !== undefined ||
56
- optionTenant !== undefined ||
57
- optionKey !== undefined;
58
61
  tried.push("options");
59
- if (anyOption) {
60
- if (optionUrl && optionTenant && optionKey) {
62
+ if (optionUrl !== undefined || optionKey !== undefined) {
63
+ if (optionUrl && optionKey) {
61
64
  return {
62
65
  url: stripTrailingSlash(optionUrl),
63
- tenant: optionTenant,
64
66
  key: optionKey,
65
67
  source: "options",
66
68
  };
@@ -68,17 +70,12 @@ export async function resolveConnection(options) {
68
70
  missing(tried.concat(["environment", "project-link"]));
69
71
  }
70
72
  const envUrl = env("NYLORUN_RUNTIME_URL");
71
- const envTenant = env("NYLORUN_TENANT");
72
73
  const envServerKey = env("NYLORUN_SERVER_KEY");
73
- const anyEnv = envUrl !== undefined ||
74
- envTenant !== undefined ||
75
- envServerKey !== undefined;
76
74
  tried.push("environment");
77
- if (anyEnv) {
78
- if (envUrl && envTenant && envServerKey) {
75
+ if (envUrl !== undefined || envServerKey !== undefined) {
76
+ if (envUrl && envServerKey) {
79
77
  return {
80
78
  url: stripTrailingSlash(envUrl),
81
- tenant: envTenant,
82
79
  key: envServerKey,
83
80
  source: "environment",
84
81
  };
@@ -22,9 +22,15 @@ export declare class DeliveryVerificationError extends Error {
22
22
  }
23
23
  /** Finds the public key for a `kid`, or `undefined` when the Tenant has none. */
24
24
  export type KeyLookup = (kid: string) => Promise<CryptoKey | undefined>;
25
+ /**
26
+ * Verifies `token` against `body`. The keys are the installation's, so a valid signature already
27
+ * binds the issuer to its one Tenant: any `urn:nylorun:tenant:<id>` issuer is accepted (and
28
+ * returned in `iss`) unless `tenantId` names the one expected.
29
+ */
25
30
  export declare function verifyDeliveryToken(token: string | null, options: {
26
31
  keys: KeyLookup;
27
- tenantId: string;
32
+ /** When set, `iss` must name this Tenant. */
33
+ tenantId?: string;
28
34
  body: Uint8Array;
29
35
  /** When set, `aud` must equal it: the URL this endpoint was registered with. */
30
36
  audience?: string;
@@ -45,7 +51,6 @@ export declare class JwksCache {
45
51
  private pending;
46
52
  constructor(source: {
47
53
  url: string;
48
- tenant: string;
49
54
  key?: string;
50
55
  fetch?: typeof fetch;
51
56
  } | {
@@ -4,8 +4,10 @@
4
4
  * only, so the SDK takes no JWT dependency: a JWS ES256 signature is the raw `r || s` pair
5
5
  * WebCrypto's ECDSA verifies.
6
6
  */
7
- import { PROTOCOL_HEADER, PROTOCOL_VERSION, TENANT_HEADER, } from "@nylorun/core/compatibility";
7
+ import { PROTOCOL_HEADER, PROTOCOL_VERSION, isTenantId } from "@nylorun/core/compatibility";
8
8
  import { DELIVERY_TOKEN_TYPE, subjectTokenIssuer } from "@nylorun/core/contracts";
9
+ /** The issuer prefix of a Tenant's tokens: `urn:nylorun:tenant:<tenantId>`. */
10
+ const ISSUER_PREFIX = subjectTokenIssuer("");
9
11
  /** Why a delivery was refused; `code` is sent back to the Runtime. */
10
12
  export class DeliveryVerificationError extends Error {
11
13
  code;
@@ -20,6 +22,11 @@ const SKEW_SECONDS = 30;
20
22
  const FORBIDDEN_HEADERS = ["jku", "jwk", "x5u", "x5c", "x5t", "x5t#S256", "crit"];
21
23
  /** The largest token accepted: well above a real one, well below a request limit. */
22
24
  const MAX_TOKEN_LENGTH = 4096;
25
+ /**
26
+ * Verifies `token` against `body`. The keys are the installation's, so a valid signature already
27
+ * binds the issuer to its one Tenant: any `urn:nylorun:tenant:<id>` issuer is accepted (and
28
+ * returned in `iss`) unless `tenantId` names the one expected.
29
+ */
23
30
  export async function verifyDeliveryToken(token, options) {
24
31
  if (!token)
25
32
  throw new DeliveryVerificationError("signature_missing", "No delivery token");
@@ -50,7 +57,10 @@ export async function verifyDeliveryToken(token, options) {
50
57
  typeof claims.exp !== "number" ||
51
58
  typeof claims.jti !== "string")
52
59
  throw invalid("Missing claims");
53
- if (claims.iss !== subjectTokenIssuer(options.tenantId))
60
+ if (!claims.iss.startsWith(ISSUER_PREFIX) ||
61
+ !isTenantId(claims.iss.slice(ISSUER_PREFIX.length)))
62
+ throw invalid("The token was not issued by a Tenant");
63
+ if (options.tenantId !== undefined && claims.iss !== subjectTokenIssuer(options.tenantId))
54
64
  throw invalid("The token is for another Tenant");
55
65
  if (options.audience !== undefined && claims.aud !== options.audience)
56
66
  throw invalid(`The token is for ${claims.aud}, not ${options.audience}`);
@@ -111,7 +121,6 @@ export class JwksCache {
111
121
  }
112
122
  async fetchKeys(source) {
113
123
  const headers = {
114
- [TENANT_HEADER]: source.tenant,
115
124
  [PROTOCOL_HEADER]: String(PROTOCOL_VERSION),
116
125
  };
117
126
  if (source.key)
package/dist/http.d.ts CHANGED
@@ -8,14 +8,17 @@ export interface TokenSource {
8
8
  get(signal?: AbortSignal): Promise<string>;
9
9
  invalidate(): void;
10
10
  }
11
+ /**
12
+ * Where a client sends its requests. Nothing names a Tenant: the Runtime at `url` serves one
13
+ * (protocol 5).
14
+ */
11
15
  export interface Destination {
12
16
  url?: string;
13
17
  key?: string;
14
- tenant?: string;
15
18
  fetch?: typeof fetch;
16
19
  /** Instead of `key`: subject tokens. The client then skips the `/health` check. */
17
20
  token?: TokenSource;
18
- /** A publishable key (Host feature `browser-access`); it also names the Tenant. */
21
+ /** A publishable key (Host feature `browser-access`): names the app and its origins. */
19
22
  publishableKey?: string;
20
23
  }
21
24
  export declare function env(name: string): string | undefined;
@@ -38,8 +41,6 @@ export declare class Transport {
38
41
  readonly url: string;
39
42
  /** The Tenant key; empty for a client that uses subject tokens. */
40
43
  readonly key: string;
41
- /** The Tenant id; empty when a publishable key names it. */
42
- readonly tenant: string;
43
44
  readonly token: TokenSource | undefined;
44
45
  readonly publishableKey: string | undefined;
45
46
  readonly fetcher: typeof fetch;
package/dist/http.js CHANGED
@@ -1,4 +1,4 @@
1
- import { PROTOCOL_FEATURES, PROTOCOL_HEADER, PROTOCOL_VERSION, PUBLISHABLE_KEY_HEADER, TENANT_HEADER, checkCompatibility, } from "@nylorun/core/compatibility";
1
+ import { PROTOCOL_FEATURES, PROTOCOL_HEADER, PROTOCOL_VERSION, PUBLISHABLE_KEY_HEADER, checkCompatibility, } from "@nylorun/core/compatibility";
2
2
  export function env(name) {
3
3
  return typeof process !== "undefined" ? process.env[name] : undefined;
4
4
  }
@@ -56,8 +56,6 @@ export class Transport {
56
56
  url;
57
57
  /** The Tenant key; empty for a client that uses subject tokens. */
58
58
  key;
59
- /** The Tenant id; empty when a publishable key names it. */
60
- tenant;
61
59
  token;
62
60
  publishableKey;
63
61
  fetcher;
@@ -68,19 +66,15 @@ export class Transport {
68
66
  const url = options.url ?? env("NYLORUN_RUNTIME_URL");
69
67
  const token = options.token;
70
68
  const key = token ? "" : options.key ?? env("NYLORUN_SERVER_KEY");
71
- const tenant = options.tenant ?? (token ? undefined : env("NYLORUN_TENANT"));
72
69
  if (!url || (!token && !key))
73
70
  throw new Error("Set Runtime url and server key explicitly or via NYLORUN_RUNTIME_URL / NYLORUN_SERVER_KEY");
74
71
  if (token && options.key)
75
72
  throw new Error("Use a Tenant key or subject tokens, not both");
76
- if (!tenant && !options.publishableKey)
77
- throw new Error("Set tenant explicitly or via NYLORUN_TENANT");
78
73
  const parsed = new URL(url);
79
74
  if (!["http:", "https:"].includes(parsed.protocol))
80
75
  throw new Error("Runtime requires an HTTP(S) URL");
81
76
  this.url = url.replace(/\/$/, "");
82
77
  this.key = key;
83
- this.tenant = tenant ?? "";
84
78
  this.token = token;
85
79
  this.publishableKey = options.publishableKey;
86
80
  // Browsers may not call `/health` (it refuses `Origin`); token clients rely on `426`.
@@ -171,8 +165,6 @@ export class Transport {
171
165
  ? await this.token.get(init.signal ?? undefined)
172
166
  : this.key;
173
167
  headers.set("Authorization", `Bearer ${bearer}`);
174
- if (this.tenant)
175
- headers.set(TENANT_HEADER, this.tenant);
176
168
  if (this.publishableKey)
177
169
  headers.set(PUBLISHABLE_KEY_HEADER, this.publishableKey);
178
170
  headers.set(PROTOCOL_HEADER, String(PROTOCOL_VERSION));
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { hashManifest, PROTOCOL_VERSION, PROTOCOL_FEATURES, HOST_PROTOCOL, DEFINITION_SCHEMA_VERSION, TENANT_HEADER, PROTOCOL_HEADER, SUBJECT_HEADER, SCOPES_HEADER, TENANT_ID_PATTERN, isTenantId, newTenantId, newPrincipalId, checkCompatibility, compareVersions, ERROR_CODES, } from "@nylorun/core/compatibility";
1
+ export { hashManifest, PROTOCOL_VERSION, PROTOCOL_FEATURES, HOST_PROTOCOL, DEFINITION_SCHEMA_VERSION, PROTOCOL_HEADER, SUBJECT_HEADER, SCOPES_HEADER, TENANT_ID_PATTERN, isTenantId, newTenantId, newPrincipalId, checkCompatibility, compareVersions, ERROR_CODES, } from "@nylorun/core/compatibility";
2
2
  export type { ProtocolFeature, ProtocolRange, Compatibility, ErrorCode, } from "@nylorun/core/compatibility";
3
3
  export { Agent, AgentBuilder } from "./builder.js";
4
4
  export { AgentBuildError, AgentLifecycleError, Loop, Chain, Switch, Parallel, Map, VerdictSchema, isVerdict, withInstructions, withoutTools, WorkflowBuildError, isBuiltWorkflow, capability, CapabilityBuilder, flow, tool, defineSchema, ToolError, } from "@nylorun/core/define";
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // Definition authoring and wire contracts are the only harness runtime imports.
2
- export { hashManifest, PROTOCOL_VERSION, PROTOCOL_FEATURES, HOST_PROTOCOL, DEFINITION_SCHEMA_VERSION, TENANT_HEADER, PROTOCOL_HEADER, SUBJECT_HEADER, SCOPES_HEADER, TENANT_ID_PATTERN, isTenantId, newTenantId, newPrincipalId, checkCompatibility, compareVersions, ERROR_CODES, } from "@nylorun/core/compatibility";
2
+ export { hashManifest, PROTOCOL_VERSION, PROTOCOL_FEATURES, HOST_PROTOCOL, DEFINITION_SCHEMA_VERSION, PROTOCOL_HEADER, SUBJECT_HEADER, SCOPES_HEADER, TENANT_ID_PATTERN, isTenantId, newTenantId, newPrincipalId, checkCompatibility, compareVersions, ERROR_CODES, } from "@nylorun/core/compatibility";
3
3
  export { Agent, AgentBuilder } from "./builder.js";
4
4
  export { AgentBuildError, AgentLifecycleError, Loop, Chain, Switch, Parallel, Map, VerdictSchema, isVerdict, withInstructions, withoutTools, WorkflowBuildError, isBuiltWorkflow, capability, CapabilityBuilder, flow, tool, defineSchema, ToolError, } from "@nylorun/core/define";
5
5
  export { SUBJECT_SCOPES } from "@nylorun/core/contracts";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nylorun/agents",
3
- "version": "0.10.0-beta",
3
+ "version": "0.11.0-beta",
4
4
  "description": "Nylorun agent definitions, session client and Action endpoint",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -47,7 +47,7 @@
47
47
  "test": "vitest run --passWithNoTests"
48
48
  },
49
49
  "dependencies": {
50
- "@nylorun/core": "0.9.0-beta",
50
+ "@nylorun/core": "0.10.0-beta",
51
51
  "gray-matter": "^4.0.3"
52
52
  },
53
53
  "peerDependencies": {