@nylorun/runtime 0.29.0-beta → 0.30.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,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.30.0-beta
4
+
5
+ ### Minor Changes
6
+
7
+ - fce0c9b: Headers for a custom model endpoint (#493). A custom endpoint's `settings` (`PUT /v1/tenant/model`) take `headers`, sent on every call, and `callHeaders`, whose values the Runtime resolves per call from the calling Agent: one of its labels (`{ from: "agent.label", key: "org" }`), its id (`agent.id`) or the session's owner when it is a person (`session.owner`). A source with no value sends no header. A Runtime that serves several orgs' Agents can then send each call's org (`x-nylorun-org` from the `org` label), so a model gateway attributes and budgets it while the call is made, in process and through the gates service alike. They are settings, read back by `GET /v1/tenant/model`: neither map may set `authorization`, a hop-by-hop or transport header, or `Nylorun-*`, and a name appears once across both. Studio keeps them when it saves the model.
8
+ - ab7c08a: A gateway's tool prefix (#494, Host feature `gateway-credentials`). A `bearer` or `headers` credential with `via`, written to an Agent's vault, may add `toolPrefix`: what the gateway at `via` puts before each tool name it lists, such as LiteLLM's `github-`. The Runtime lists the server's tools without it, so tool names (`github__create_issue`), a manifest's `tools` settings and tool policy rules name the server's own tools whether or not a gateway is in between, and calls each tool by the name the gateway listed. A listed tool that lacks the prefix keeps its name, and the server's `mcp.discovered` message names it. `toolPrefix` needs `via`, and secret views carry it. It is the installation's routing, never a manifest's: a manifest's secret slot cannot name it, and a slot's value write (`{ headers, toolPrefix }`) adds it. A fork keeps it while the new slot keeps `via`. No protocol change.
9
+ - 4a563e7: The workload token (#496). A Runtime given `NYLORUN_WORKLOAD_TOKEN_FILE`, a token a platform rotates through a file, reads it at each use and keeps nothing, so a rotation applies to the next request with no restart; a missing, unreadable or empty file fails the request, naming the variable, and nothing is sent without it. A custom model with `auth: { type: "workload" }` sends it as the endpoint's bearer. A new credential type, `workload` (`{ url, via, identity?, toolPrefix? }`, no value), sends `Authorization: Bearer <token>` to its `via` only, and only when `via`'s origin is in `NYLORUN_WORKLOAD_TOKEN_ORIGINS`, since an Agent's writer chooses `via`. A gateway's `401` is `credential_rejected` with `gateway`, its origin. The token stays in the process that makes the call: never in a sandbox, the model, an event or a log. Studio shows the credential and the model's auth.
10
+
11
+ ### Patch Changes
12
+
13
+ - 8c82ed9: An MCP gateway's report of an upstream `401` is `credential_rejected` (#495). Through a credential's `via`, a gateway that answers HTTP 200 but reports that the server behind it answered `401` is read like a `401`. LiteLLM does this for a token the provider revoked: a `tools/list` with no tools and `"auth_required"` for the server in `_meta["litellm.ai/server_outcomes"]` makes the server's `mcp.discovered` outcome `failed` ("MCP server returned 401"), and a tool error carrying that outcome, or LiteLLM's `HTTPStatusError: Client error '401 Unauthorized'` text, completes as `credential_rejected` with `retryable: false`, naming the server. A server reached without `via` is read as before.
14
+ - Pin core to the tested release.
15
+ - Pin harness to the tested release.
16
+ - Updated dependencies [fce0c9b]
17
+ - Updated dependencies [ab7c08a]
18
+ - Updated dependencies [4a563e7]
19
+ - Updated dependencies
20
+ - @nylorun/core@0.26.0-beta
21
+ - @nylorun/harness@0.29.2-beta
22
+
3
23
  ## 0.29.0-beta
4
24
 
5
25
  ### Minor Changes
@@ -897,6 +897,7 @@ export declare const PutAgentSecretValueRequest: z.ZodObject<{
897
897
  requestId: z.ZodOptional<z.ZodString>;
898
898
  value: z.ZodOptional<z.ZodString>;
899
899
  headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
900
+ toolPrefix: z.ZodOptional<z.ZodString>;
900
901
  }, z.core.$strict>;
901
902
  export declare const AgentSnapshot: z.ZodObject<{
902
903
  agentId: z.ZodString;
@@ -4447,6 +4448,7 @@ export declare const SessionEvent: z.ZodDiscriminatedUnion<[z.ZodObject<{
4447
4448
  code: z.ZodString;
4448
4449
  message: z.ZodString;
4449
4450
  server: z.ZodOptional<z.ZodString>;
4451
+ gateway: z.ZodOptional<z.ZodString>;
4450
4452
  retryable: z.ZodOptional<z.ZodBoolean>;
4451
4453
  }, z.core.$loose>>;
4452
4454
  redacted: z.ZodOptional<z.ZodNumber>;
@@ -7179,6 +7181,7 @@ export declare const SessionEvent: z.ZodDiscriminatedUnion<[z.ZodObject<{
7179
7181
  code: z.ZodString;
7180
7182
  message: z.ZodString;
7181
7183
  server: z.ZodOptional<z.ZodString>;
7184
+ gateway: z.ZodOptional<z.ZodString>;
7182
7185
  retryable: z.ZodOptional<z.ZodBoolean>;
7183
7186
  }, z.core.$loose>>;
7184
7187
  redacted: z.ZodOptional<z.ZodNumber>;
@@ -9914,6 +9917,7 @@ export declare const SessionItemsResponse: z.ZodObject<{
9914
9917
  code: z.ZodString;
9915
9918
  message: z.ZodString;
9916
9919
  server: z.ZodOptional<z.ZodString>;
9920
+ gateway: z.ZodOptional<z.ZodString>;
9917
9921
  retryable: z.ZodOptional<z.ZodBoolean>;
9918
9922
  }, z.core.$loose>>;
9919
9923
  redacted: z.ZodOptional<z.ZodNumber>;
@@ -12646,6 +12650,7 @@ export declare const SessionItemsResponse: z.ZodObject<{
12646
12650
  code: z.ZodString;
12647
12651
  message: z.ZodString;
12648
12652
  server: z.ZodOptional<z.ZodString>;
12653
+ gateway: z.ZodOptional<z.ZodString>;
12649
12654
  retryable: z.ZodOptional<z.ZodBoolean>;
12650
12655
  }, z.core.$loose>>;
12651
12656
  redacted: z.ZodOptional<z.ZodNumber>;
@@ -13224,6 +13229,7 @@ export declare const TenantStatus: z.ZodObject<{
13224
13229
  authType: z.ZodEnum<{
13225
13230
  api_key: "api_key";
13226
13231
  oauth: "oauth";
13232
+ workload: "workload";
13227
13233
  }>;
13228
13234
  baseUrl: z.ZodOptional<z.ZodString>;
13229
13235
  settings: z.ZodOptional<z.ZodObject<{
@@ -13239,6 +13245,15 @@ export declare const TenantStatus: z.ZodObject<{
13239
13245
  video: "video";
13240
13246
  }>>>;
13241
13247
  compat: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
13248
+ headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
13249
+ callHeaders: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodDiscriminatedUnion<[z.ZodObject<{
13250
+ from: z.ZodLiteral<"agent.label">;
13251
+ key: z.ZodString;
13252
+ }, z.core.$strict>, z.ZodObject<{
13253
+ from: z.ZodLiteral<"agent.id">;
13254
+ }, z.core.$strict>, z.ZodObject<{
13255
+ from: z.ZodLiteral<"session.owner">;
13256
+ }, z.core.$strict>], "from">>>;
13242
13257
  }, z.core.$strict>>;
13243
13258
  }, z.core.$strict>]>;
13244
13259
  agents: z.ZodArray<z.ZodObject<{
@@ -13357,6 +13372,15 @@ export declare const SeedTenantConfigRequest: z.ZodObject<{
13357
13372
  video: "video";
13358
13373
  }>>>;
13359
13374
  compat: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
13375
+ headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
13376
+ callHeaders: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodDiscriminatedUnion<[z.ZodObject<{
13377
+ from: z.ZodLiteral<"agent.label">;
13378
+ key: z.ZodString;
13379
+ }, z.core.$strict>, z.ZodObject<{
13380
+ from: z.ZodLiteral<"agent.id">;
13381
+ }, z.core.$strict>, z.ZodObject<{
13382
+ from: z.ZodLiteral<"session.owner">;
13383
+ }, z.core.$strict>], "from">>>;
13360
13384
  }, z.core.$strict>>;
13361
13385
  auth: z.ZodDiscriminatedUnion<[z.ZodObject<{
13362
13386
  type: z.ZodLiteral<"api_key">;
@@ -13367,7 +13391,9 @@ export declare const SeedTenantConfigRequest: z.ZodObject<{
13367
13391
  refresh: z.ZodString;
13368
13392
  access: z.ZodString;
13369
13393
  expires: z.ZodNumber;
13370
- }, z.core.$loose>], "type">;
13394
+ }, z.core.$loose>, z.ZodObject<{
13395
+ type: z.ZodLiteral<"workload">;
13396
+ }, z.core.$strict>], "type">;
13371
13397
  }, z.core.$strict>>;
13372
13398
  fixtureModel: z.ZodOptional<z.ZodLiteral<true>>;
13373
13399
  }, z.core.$strict>;
@@ -13626,6 +13652,7 @@ export declare const ListProvidersResponse: z.ZodObject<{
13626
13652
  authType: z.ZodEnum<{
13627
13653
  api_key: "api_key";
13628
13654
  oauth: "oauth";
13655
+ workload: "workload";
13629
13656
  }>;
13630
13657
  baseUrl: z.ZodOptional<z.ZodString>;
13631
13658
  settings: z.ZodOptional<z.ZodObject<{
@@ -13641,6 +13668,15 @@ export declare const ListProvidersResponse: z.ZodObject<{
13641
13668
  video: "video";
13642
13669
  }>>>;
13643
13670
  compat: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
13671
+ headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
13672
+ callHeaders: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodDiscriminatedUnion<[z.ZodObject<{
13673
+ from: z.ZodLiteral<"agent.label">;
13674
+ key: z.ZodString;
13675
+ }, z.core.$strict>, z.ZodObject<{
13676
+ from: z.ZodLiteral<"agent.id">;
13677
+ }, z.core.$strict>, z.ZodObject<{
13678
+ from: z.ZodLiteral<"session.owner">;
13679
+ }, z.core.$strict>], "from">>>;
13644
13680
  }, z.core.$strict>>;
13645
13681
  inputs: z.ZodOptional<z.ZodArray<z.ZodEnum<{
13646
13682
  audio: "audio";
@@ -13663,6 +13699,7 @@ export declare const HostModelView: z.ZodUnion<readonly [z.ZodObject<{
13663
13699
  authType: z.ZodEnum<{
13664
13700
  api_key: "api_key";
13665
13701
  oauth: "oauth";
13702
+ workload: "workload";
13666
13703
  }>;
13667
13704
  baseUrl: z.ZodOptional<z.ZodString>;
13668
13705
  settings: z.ZodOptional<z.ZodObject<{
@@ -13678,6 +13715,15 @@ export declare const HostModelView: z.ZodUnion<readonly [z.ZodObject<{
13678
13715
  video: "video";
13679
13716
  }>>>;
13680
13717
  compat: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
13718
+ headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
13719
+ callHeaders: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodDiscriminatedUnion<[z.ZodObject<{
13720
+ from: z.ZodLiteral<"agent.label">;
13721
+ key: z.ZodString;
13722
+ }, z.core.$strict>, z.ZodObject<{
13723
+ from: z.ZodLiteral<"agent.id">;
13724
+ }, z.core.$strict>, z.ZodObject<{
13725
+ from: z.ZodLiteral<"session.owner">;
13726
+ }, z.core.$strict>], "from">>>;
13681
13727
  }, z.core.$strict>>;
13682
13728
  }, z.core.$strict>]>;
13683
13729
  export declare const PutHostModelRequest: z.ZodObject<{
@@ -13699,6 +13745,15 @@ export declare const PutHostModelRequest: z.ZodObject<{
13699
13745
  video: "video";
13700
13746
  }>>>;
13701
13747
  compat: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
13748
+ headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
13749
+ callHeaders: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodDiscriminatedUnion<[z.ZodObject<{
13750
+ from: z.ZodLiteral<"agent.label">;
13751
+ key: z.ZodString;
13752
+ }, z.core.$strict>, z.ZodObject<{
13753
+ from: z.ZodLiteral<"agent.id">;
13754
+ }, z.core.$strict>, z.ZodObject<{
13755
+ from: z.ZodLiteral<"session.owner">;
13756
+ }, z.core.$strict>], "from">>>;
13702
13757
  }, z.core.$strict>>;
13703
13758
  auth: z.ZodDiscriminatedUnion<[z.ZodObject<{
13704
13759
  type: z.ZodLiteral<"api_key">;
@@ -13709,7 +13764,9 @@ export declare const PutHostModelRequest: z.ZodObject<{
13709
13764
  refresh: z.ZodString;
13710
13765
  access: z.ZodString;
13711
13766
  expires: z.ZodNumber;
13712
- }, z.core.$loose>], "type">;
13767
+ }, z.core.$loose>, z.ZodObject<{
13768
+ type: z.ZodLiteral<"workload">;
13769
+ }, z.core.$strict>], "type">;
13713
13770
  }, z.core.$strict>;
13714
13771
  export declare const SelectHostModelRequest: z.ZodObject<{
13715
13772
  requestId: z.ZodString;
@@ -13740,6 +13797,7 @@ export declare const PutAgentSecretRequest: z.ZodDiscriminatedUnion<[z.ZodObject
13740
13797
  type: z.ZodLiteral<"bearer">;
13741
13798
  url: z.ZodString;
13742
13799
  value: z.ZodString;
13800
+ toolPrefix: z.ZodOptional<z.ZodString>;
13743
13801
  }, z.core.$strict>, z.ZodObject<{
13744
13802
  via: z.ZodOptional<z.ZodString>;
13745
13803
  identity: z.ZodOptional<z.ZodObject<{
@@ -13749,6 +13807,16 @@ export declare const PutAgentSecretRequest: z.ZodDiscriminatedUnion<[z.ZodObject
13749
13807
  type: z.ZodLiteral<"headers">;
13750
13808
  url: z.ZodString;
13751
13809
  headers: z.ZodRecord<z.ZodString, z.ZodString>;
13810
+ toolPrefix: z.ZodOptional<z.ZodString>;
13811
+ }, z.core.$strict>, z.ZodObject<{
13812
+ requestId: z.ZodOptional<z.ZodString>;
13813
+ type: z.ZodLiteral<"workload">;
13814
+ url: z.ZodString;
13815
+ via: z.ZodString;
13816
+ identity: z.ZodOptional<z.ZodObject<{
13817
+ header: z.ZodString;
13818
+ }, z.core.$strict>>;
13819
+ toolPrefix: z.ZodOptional<z.ZodString>;
13752
13820
  }, z.core.$strict>], "type">;
13753
13821
  export declare const AgentSecretView: z.ZodObject<{
13754
13822
  name: z.ZodString;
@@ -13757,6 +13825,7 @@ export declare const AgentSecretView: z.ZodObject<{
13757
13825
  environment_secret: "environment_secret";
13758
13826
  environment_variable: "environment_variable";
13759
13827
  headers: "headers";
13828
+ workload: "workload";
13760
13829
  }>;
13761
13830
  binding: z.ZodObject<{
13762
13831
  url: z.ZodOptional<z.ZodString>;
@@ -13768,6 +13837,7 @@ export declare const AgentSecretView: z.ZodObject<{
13768
13837
  }, z.core.$strict>;
13769
13838
  headerNames: z.ZodOptional<z.ZodArray<z.ZodString>>;
13770
13839
  via: z.ZodOptional<z.ZodString>;
13840
+ toolPrefix: z.ZodOptional<z.ZodString>;
13771
13841
  identity: z.ZodOptional<z.ZodObject<{
13772
13842
  header: z.ZodString;
13773
13843
  }, z.core.$strict>>;
@@ -13782,6 +13852,7 @@ export declare const ListAgentSecretsResponse: z.ZodObject<{
13782
13852
  environment_secret: "environment_secret";
13783
13853
  environment_variable: "environment_variable";
13784
13854
  headers: "headers";
13855
+ workload: "workload";
13785
13856
  }>;
13786
13857
  binding: z.ZodObject<{
13787
13858
  url: z.ZodOptional<z.ZodString>;
@@ -13793,6 +13864,7 @@ export declare const ListAgentSecretsResponse: z.ZodObject<{
13793
13864
  }, z.core.$strict>;
13794
13865
  headerNames: z.ZodOptional<z.ZodArray<z.ZodString>>;
13795
13866
  via: z.ZodOptional<z.ZodString>;
13867
+ toolPrefix: z.ZodOptional<z.ZodString>;
13796
13868
  identity: z.ZodOptional<z.ZodObject<{
13797
13869
  header: z.ZodString;
13798
13870
  }, z.core.$strict>>;
@@ -16210,6 +16282,7 @@ export declare const HistoryPage: z.ZodObject<{
16210
16282
  code: z.ZodString;
16211
16283
  message: z.ZodString;
16212
16284
  server: z.ZodOptional<z.ZodString>;
16285
+ gateway: z.ZodOptional<z.ZodString>;
16213
16286
  retryable: z.ZodOptional<z.ZodBoolean>;
16214
16287
  }, z.core.$loose>>;
16215
16288
  redacted: z.ZodOptional<z.ZodNumber>;
@@ -18942,6 +19015,7 @@ export declare const HistoryPage: z.ZodObject<{
18942
19015
  code: z.ZodString;
18943
19016
  message: z.ZodString;
18944
19017
  server: z.ZodOptional<z.ZodString>;
19018
+ gateway: z.ZodOptional<z.ZodString>;
18945
19019
  retryable: z.ZodOptional<z.ZodBoolean>;
18946
19020
  }, z.core.$loose>>;
18947
19021
  redacted: z.ZodOptional<z.ZodNumber>;
@@ -19,26 +19,15 @@ import { createServer } from "node:http";
19
19
  import { Agent, request } from "node:https";
20
20
  import { createSecureContext, TLSSocket } from "node:tls";
21
21
  import { hostTuning } from "../tuning.js";
22
+ import { HOP_BY_HOP_HEADERS } from "../vault/headers.js";
22
23
  /** The answer to a CONNECT the gate serves; a tunnel's byte counts leave it out. */
23
24
  export const CONNECT_ESTABLISHED = "HTTP/1.1 200 Connection Established\r\n\r\n";
24
- /** Headers that describe one hop and are never forwarded. */
25
- const HOP_BY_HOP = new Set([
26
- "connection",
27
- "keep-alive",
28
- "proxy-authenticate",
29
- "proxy-authorization",
30
- "proxy-connection",
31
- "te",
32
- "trailer",
33
- "transfer-encoding",
34
- "upgrade",
35
- ]);
36
25
  /** `rawHeaders` without hop-by-hop headers and `drop`, as lowercase names to values. */
37
26
  function forwardable(raw, drop) {
38
27
  const headers = {};
39
28
  for (let i = 0; i + 1 < raw.length; i += 2) {
40
29
  const name = raw[i].toLowerCase();
41
- if (HOP_BY_HOP.has(name) || name === drop)
30
+ if (HOP_BY_HOP_HEADERS.has(name) || name === drop)
42
31
  continue;
43
32
  (headers[name] ??= []).push(raw[i + 1]);
44
33
  }
@@ -6,7 +6,7 @@
6
6
  * the Tenant's ledger. Logs one line per call, never the prompt, the output or a credential.
7
7
  */
8
8
  import { classifyThrown } from "../model/classify.js";
9
- import { callFiles, inProcessModelGate, modelCallSettings, serve } from "./in-process.js";
9
+ import { callCaller, callFiles, inProcessModelGate, modelCallSettings, serve } from "./in-process.js";
10
10
  import { createMeter } from "./meter.js";
11
11
  import { GateRefusal } from "./tenant-vaults.js";
12
12
  export function createModelCallHandler(options) {
@@ -28,6 +28,7 @@ export function createModelCallHandler(options) {
28
28
  // The Tenant's Runtime limits `model*`, read for this call.
29
29
  settings: await modelCallSettings(vault.store),
30
30
  ...(blobs ? { files: callFiles(vault.store, blobs) } : {}),
31
+ caller: callCaller(vault.store),
31
32
  });
32
33
  outcome = await meter.call(vault.store, call, () => serve(vault.store, gate, call, signal));
33
34
  }
@@ -11,6 +11,7 @@ import type { HostModelSecret } from "../vault/service.js";
11
11
  import type { ModelGate, ModelGateRequest } from "./model-gate.js";
12
12
  import { type FileResolver } from "../artifacts/files.js";
13
13
  import type { BlobStore } from "../blob/index.js";
14
+ import type { ModelCaller } from "../model/call-headers.js";
14
15
  export interface InProcessModelGateOptions {
15
16
  /** The Tenant's home: `piModel` redacts secrets it finds there from failure messages. */
16
17
  readonly root?: string;
@@ -20,6 +21,8 @@ export interface InProcessModelGateOptions {
20
21
  readonly settings?: ModelCallSettings;
21
22
  /** Reads the files a call's prompt names (protocol 6): artifacts, for model-gate. */
22
23
  readonly files?: (request: ModelGateRequest) => FileResolver;
24
+ /** Reads who makes a call, for a custom endpoint's per-call headers (#493). */
25
+ readonly caller?: (request: ModelGateRequest) => Promise<ModelCaller | undefined>;
23
26
  }
24
27
  /**
25
28
  * The files a call names, from the Tenant's artifacts, limited to the call's session: in
@@ -27,6 +30,12 @@ export interface InProcessModelGateOptions {
27
30
  * gate's route puts on the request (F5). A request without a session reads no file at all.
28
31
  */
29
32
  export declare function callFiles(store: SessionStore, blobs: BlobStore): (request: Pick<ModelGateRequest, "sessionId">) => FileResolver;
33
+ /**
34
+ * Who makes a call, from the Tenant's store: the Agent its session runs on, with its labels, and
35
+ * the session's owner when it is a person. The session is the loop's own in process, and the run
36
+ * token's over HTTP; the request's `agentId` names the manifest's agent, not the Agent.
37
+ */
38
+ export declare function callCaller(store: SessionStore): (request: Pick<ModelGateRequest, "sessionId">) => Promise<ModelCaller | undefined>;
30
39
  /**
31
40
  * The in-process gate of an open Tenant: reads its host model from its own vault and records
32
41
  * each call in its usage ledger. Only a Runtime without the gates service (embedding, the
@@ -5,6 +5,7 @@ import { createMeter } from "./meter.js";
5
5
  import { artifactFiles } from "../artifacts/files.js";
6
6
  import { usesFixtureModel } from "../tenant/model-setting.js";
7
7
  import { fixtureAnswer } from "./fixture-model.js";
8
+ import { isSubject } from "@nylorun/core/contracts";
8
9
  /**
9
10
  * The files a call names, from the Tenant's artifacts, limited to the call's session: in
10
11
  * process, the loop's own request; over HTTP, the session of the run token's claims, which the
@@ -13,6 +14,24 @@ import { fixtureAnswer } from "./fixture-model.js";
13
14
  export function callFiles(store, blobs) {
14
15
  return (request) => artifactFiles({ store, blobs, sessionId: request.sessionId });
15
16
  }
17
+ /**
18
+ * Who makes a call, from the Tenant's store: the Agent its session runs on, with its labels, and
19
+ * the session's owner when it is a person. The session is the loop's own in process, and the run
20
+ * token's over HTTP; the request's `agentId` names the manifest's agent, not the Agent.
21
+ */
22
+ export function callCaller(store) {
23
+ return (request) => store.tx(async (t) => {
24
+ const session = await t.get("sessions", request.sessionId);
25
+ const agent = session && (await t.environment(session.agentId));
26
+ if (!session || !agent)
27
+ return undefined;
28
+ return {
29
+ agentId: agent.id,
30
+ labels: agent.labels,
31
+ ...(isSubject(session.ownerUserId) ? { owner: session.ownerUserId } : {}),
32
+ };
33
+ });
34
+ }
16
35
  /**
17
36
  * The in-process gate of an open Tenant: reads its host model from its own vault and records
18
37
  * each call in its usage ledger. Only a Runtime without the gates service (embedding, the
@@ -30,6 +49,7 @@ export function tenantModelGate(options) {
30
49
  writeHostCredential: (credential) => vault.updateHostCredential(credential),
31
50
  settings: await modelCallSettings(options.store),
32
51
  ...(options.blobs ? { files: callFiles(options.store, options.blobs) } : {}),
52
+ caller: callCaller(options.store),
33
53
  });
34
54
  return serve(options.store, gate, request, signal);
35
55
  }),
@@ -58,6 +78,7 @@ export function inProcessModelGate(options) {
58
78
  writeHostCredential: options.writeHostCredential,
59
79
  ...(options.settings ? { settings: options.settings } : {}),
60
80
  ...(options.files ? { files: options.files(request) } : {}),
81
+ ...(options.caller ? { caller: () => options.caller(request) } : {}),
61
82
  });
62
83
  return adapter(request.call, {
63
84
  // The Runtime never journals the provider request (harness `durable.ts`), and
package/dist/host/main.js CHANGED
@@ -65,6 +65,7 @@ import { startHarnessListener } from "../harness-api/ws-server.js";
65
65
  import { httpSandboxesClient } from "../sandbox/pods/client.js";
66
66
  import { retry } from "../retry.js";
67
67
  import { hostTuning, useHostTuning } from "../tuning.js";
68
+ import { useWorkloadToken } from "../vault/workload-token.js";
68
69
  const nodeRequire = createRequire(import.meta.url);
69
70
  function coreVersion() {
70
71
  try {
@@ -218,6 +219,7 @@ export async function main() {
218
219
  // Read once, before any service starts: each reads the parts it uses where it uses them.
219
220
  if (stack.tuning)
220
221
  useHostTuning(stack.tuning.values, stack.tuning.overrides);
222
+ useWorkloadToken(stack.workloadToken ?? {});
221
223
  if (stack.services.has("gates") || stack.services.has("keys") || stack.services.has("egress"))
222
224
  return runGates(stack);
223
225
  if (stack.services.has("harness"))
@@ -178,6 +178,16 @@ export interface HarnessServiceConfig {
178
178
  export interface StackConfig {
179
179
  /** The Runtime services this process runs. */
180
180
  services: RuntimeServices;
181
+ /**
182
+ * `NYLORUN_WORKLOAD_TOKEN_FILE`, the file of the Runtime's workload token, which a platform
183
+ * rotates and the Runtime reads at each use, and `NYLORUN_WORKLOAD_TOKEN_ORIGINS`, the
184
+ * comma-separated origins a `workload` credential's `via` may send it to (#496,
185
+ * `vault/workload-token.ts`). Absent: no workload token.
186
+ */
187
+ workloadToken?: {
188
+ file?: string;
189
+ origins?: string[];
190
+ };
181
191
  /** Present when the process runs gates. */
182
192
  gates?: GatesConfig;
183
193
  /** Present when the process runs egress. */
@@ -47,6 +47,7 @@
47
47
  import { DEFAULT_SERVICE_NAME } from "../traces/otlp.js";
48
48
  import { TRACE_CONTENT_KINDS } from "../traces/map.js";
49
49
  import { isTenantId } from "@nylorun/core/compatibility";
50
+ import { LOOPBACK_HOSTS } from "@nylorun/core/contracts";
50
51
  import { blockedAddresses } from "../sandbox/pods/network-gate.js";
51
52
  import { HostTuningError, parseHostTuning } from "../tuning.js";
52
53
  export const RUNTIME_SERVICES = ["core", "loop", "gates", "keys", "egress", "harness"];
@@ -325,6 +326,7 @@ export function parseStackConfig(env, argv) {
325
326
  throw new StackConfigError("NYLORUN_HARNESS_LISTEN_PORT must differ from NYLORUN_LISTEN_PORT");
326
327
  const delivery = parseDelivery(env);
327
328
  const tenant = servesApi ? parseTenant(env) : undefined;
329
+ const workloadToken = parseWorkloadToken(env);
328
330
  const objectStore = parseObjectStore(env);
329
331
  const traces = services.has("core") ? parseTraces(env) : undefined;
330
332
  const removedResolverVariables = Object.keys(env)
@@ -347,6 +349,7 @@ export function parseStackConfig(env, argv) {
347
349
  ...(sandboxes ? { sandboxes } : {}),
348
350
  ...(delivery ? { delivery } : {}),
349
351
  ...(tenant ? { tenant } : {}),
352
+ ...(workloadToken ? { workloadToken } : {}),
350
353
  ...(objectStore ? { objectStore } : {}),
351
354
  ...(traces ? { traces } : {}),
352
355
  ...(tuning ? { tuning } : {}),
@@ -454,6 +457,39 @@ function parseTenant(env) {
454
457
  ...(managementKeyFile ? { managementKeyFile } : {}),
455
458
  };
456
459
  }
460
+ /**
461
+ * `StackConfig.workloadToken` (#496): the Runtime's workload token, which the gates read for a
462
+ * custom model and for `workload` credentials, and the in-process Tenant does without them.
463
+ */
464
+ function parseWorkloadToken(env) {
465
+ const file = read(env, "NYLORUN_WORKLOAD_TOKEN_FILE");
466
+ const origins = parseOrigins(read(env, "NYLORUN_WORKLOAD_TOKEN_ORIGINS"));
467
+ if (file === undefined && origins === undefined)
468
+ return undefined;
469
+ return { ...(file ? { file } : {}), ...(origins ? { origins } : {}) };
470
+ }
471
+ /** `NYLORUN_WORKLOAD_TOKEN_ORIGINS`: each an `https` origin (`http` only to a loopback host). */
472
+ function parseOrigins(raw) {
473
+ if (raw === undefined)
474
+ return undefined;
475
+ return raw
476
+ .split(",")
477
+ .map((item) => item.trim())
478
+ .filter((item) => item !== "")
479
+ .map((item) => {
480
+ let url;
481
+ try {
482
+ url = new URL(item);
483
+ }
484
+ catch {
485
+ throw new StackConfigError(`NYLORUN_WORKLOAD_TOKEN_ORIGINS: ${item} is not a URL`);
486
+ }
487
+ const loopback = url.protocol === "http:" && LOOPBACK_HOSTS.test(url.hostname);
488
+ if ((url.protocol !== "https:" && !loopback) || url.origin !== item.replace(/\/$/, ""))
489
+ throw new StackConfigError(`NYLORUN_WORKLOAD_TOKEN_ORIGINS: ${item} must be an https origin, such as https://gateway.example.com`);
490
+ return url.origin;
491
+ });
492
+ }
457
493
  const GATES_TOKEN = /^[0-9a-f]{64,}$/i;
458
494
  /** `NYLORUN_HARNESS`: `in-process` (the default when unset) or `remote`. */
459
495
  function parseHarnessMode(env) {