@nylorun/runtime 0.31.0-beta → 0.33.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.
Files changed (37) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/dist/api/components.d.ts +100 -0
  3. package/dist/api/components.js +4 -1
  4. package/dist/api/http/routes/agent-manifests.d.ts +2 -1
  5. package/dist/api/http/routes/agent-manifests.js +29 -2
  6. package/dist/api/http/routes/sessions.js +63 -2
  7. package/dist/api/openapi.js +4 -0
  8. package/dist/gates/handler.js +2 -2
  9. package/dist/gates/in-process.d.ts +4 -3
  10. package/dist/gates/in-process.js +11 -5
  11. package/dist/gates/tenant-vaults.d.ts +4 -3
  12. package/dist/gates/tenant-vaults.js +2 -2
  13. package/dist/harness-api/start.js +1 -1
  14. package/dist/host/main.js +2 -0
  15. package/dist/host/stack-config.d.ts +5 -0
  16. package/dist/host/stack-config.js +15 -0
  17. package/dist/management-openapi.json +90 -8
  18. package/dist/model/descriptor.d.ts +5 -4
  19. package/dist/model/descriptor.js +4 -3
  20. package/dist/model/pi-model.js +17 -1
  21. package/dist/openapi.json +880 -2
  22. package/dist/tenant/agent-manifests.d.ts +6 -1
  23. package/dist/tenant/agent-manifests.js +50 -8
  24. package/dist/tenant/agents.d.ts +4 -3
  25. package/dist/tenant/agents.js +26 -5
  26. package/dist/tenant/context.d.ts +6 -1
  27. package/dist/tenant/session-model.d.ts +16 -0
  28. package/dist/tenant/session-model.js +46 -0
  29. package/dist/vault/host-identity.d.ts +24 -0
  30. package/dist/vault/host-identity.js +35 -0
  31. package/dist/vault/host-model.d.ts +9 -5
  32. package/dist/vault/host-model.js +30 -19
  33. package/dist/vault/service.d.ts +18 -2
  34. package/dist/vault/service.js +52 -9
  35. package/dist/version.d.ts +1 -1
  36. package/dist/version.js +1 -1
  37. package/package.json +4 -4
package/CHANGELOG.md CHANGED
@@ -1,5 +1,68 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.33.0-beta
4
+
5
+ ### Minor Changes
6
+
7
+ - 95f98f1: A model can use the Host's own identity (ADR-71, Host feature `model-host-identity`). `PUT /v1/tenant/model` takes `auth: { type: "host_identity", project, location }` for `google-vertex`. It keeps no secret, and the model gate calls Agent Platform with the Host's Google Application Default Credentials: on Google Cloud, the attached service account from the metadata server. It's off unless the operator lists the provider in `NYLORUN_MODEL_HOST_IDENTITY` on the process that runs the gates, which `nylorun start` passes from its shell to the `gateway` container. Every Tenant on that Host may use it. `nylorun tenant models set --provider google-vertex --model <id> --host-identity --project <id> --location <location>` sets it with no key.
8
+ - d99b3ac: A session can have its own model (optional Host feature `session-model`). `PUT /v1/sessions/{id}/model` chooses one of a connected provider's models for that session's turns, `DELETE` clears it, and `GET` answers the model the next turn uses (`source: "session"` or `"tenant"`). The Tenant's model and every other session's stay as they are. The Model Gate calls the session's model on its provider's credential (a refreshed OAuth credential is written back to that provider), `turn.start.model` describes it, and the usage ledger records it. A choice names a connected provider and a model its catalog lists, is set by the application only (a trusted issuer's token can read it, not change it), and is refused while a turn runs or waits (`409 session_busy`); while its provider is disconnected the session uses the Tenant's model. The SDK adds `session.model()`, `setModel({ provider, model })` and `clearModel()`.
9
+
10
+ Studio's model picker under the message box now sets the model of that session only, instead of the Tenant's: "Tenant model" clears the session's choice, the picker is locked while a turn runs, it groups models by provider, and its search keeps what is typed. A Runtime without `session-model` shows no picker.
11
+
12
+ ### Patch Changes
13
+
14
+ - 683de1a: The model catalog comes from pi-ai 1.3.0, which adds Gemini 4 Argon: `google-vertex/gemini-4-argon` on Google's Agent Platform (Vertex AI) and `google/gemini-4-argon` on the Gemini API, with a 1,048,576-token context and thinking. `nylorun tenant models set --provider google-vertex --model gemini-4-argon` takes a Google Cloud API key for the Agent Platform API; the Runtime still reads no ambient Google credentials.
15
+ - Pin core to the tested release.
16
+ - Pin harness to the tested release.
17
+ - Updated dependencies [95f98f1]
18
+ - Updated dependencies [d99b3ac]
19
+ - Updated dependencies
20
+ - @nylorun/core@0.29.0-beta
21
+ - @nylorun/harness@0.29.5-beta
22
+
23
+ ## 0.32.0-beta
24
+
25
+ ### Minor Changes
26
+
27
+ - b3dad06: Both ends of the agent folder loop (track T7).
28
+
29
+ - `nylorun init <path>` names the agent after the path's last segment (`init agents/hello`). `--example` adds `tools/get_order.json`, `mcp.json` (a `Bearer ${ORDERS_MCP_TOKEN}` header) and `triggers/new-order.md`, explained in the folder's README.md, and the result passes `nylorun check`. The first run in a project (the nearest directory with `.git`) writes `.gitattributes` (`* text=auto eol=lf`), ignores `.nylorun/credentials.json` in `.gitignore`, and maps the folder files to their JSON Schemas in `.vscode/settings.json`, merged into an existing file. Its hint names `apply --agent` and `chat`.
30
+ - `nylorun apply --agent [<id>]` makes the Agent from the manifest after a save (or on an unchanged one), or moves it, as `nylorun agents put` does, and prints what it needs a person for.
31
+ - `nylorun apply --watch [--save]` checks and plans again on each change to the folders (and the files their `runtime.json` extends); it saves only with `--save`.
32
+ - A plan's change to the instructions prints their text diff, `-` and `+` lines.
33
+ - `nylorun chat <agent> [--session <id>] [--message <text>]` opens or reuses a session, sends `--message` or each line of standard input, and prints the reply as it comes.
34
+ - `nylorun check --json` prints `{ folders: [{ path, ok, manifest?, diagnostics }] }` for CI, with the same exit codes. `check` says "1 file", and `nylorun --version` prints the version.
35
+ - `packFolder` (`@nylorun/agents`) leaves out what the folder's `.nyloignore` names (gitignore rules), and resolves `extends` in `runtime.json`: the packed file is the merge, the folder's own keys winning, with `.nylorun/origin.json` naming the files it came from; a missing file or a cycle is a `FolderError`. `ignoreMatcher` is exported.
36
+ - `session.approve(id, approved)` and `session.respond(id, value)` take no options: each makes its own idempotency key.
37
+ - The Runtime's Agent view names each required secret of the version it runs that has no value, as an attention item (`secret_unset: required secret ORDERS_TOKEN unset`, state `input_required`), until it is set.
38
+ - The compiler's refusal of an unresolved `extends` says to resolve it before uploading.
39
+
40
+ - b3dad06: Compare two versions' agent folders, and set what a folder may hold per Tenant (track T7).
41
+
42
+ - **`GET /v1/agent-manifests/{name}/source/diff?from=<v>&to=<v>`** answers the files added, removed and changed (by SHA-256) from one version's source to another's, each list sorted by path, and how many stayed the same (`AgentManifestSourceDiff` in `@nylorun/core/contracts`). A version saved as JSON has no source (`404`); a `from` or `to` that is not a version number is `400`. `client.agentManifests.sourceDiff(name, from, to)` in `@nylorun/agents` calls it.
43
+ - **Folder limits are Tenant settings**: the Runtime limits `folderFiles` (2,000), `folderBytes` (50 MiB) and `folderTextFileBytes` (1 MiB), T6's fixed values, set with `PUT /v1/tenant/limits` like the others. A folder save with more files or bytes is `413 limit_exceeded` (`details.limit` names the limit), and the compiler holds each Markdown or JSON file to `folderTextFileBytes` (`422 folder_invalid`).
44
+ - `compileAgentFolder({ entries, files, name, limits })` takes the limits as an optional input (`AgentFolderLimits`); each left out is `AGENT_FOLDER_LIMITS`'.
45
+ - `@nylorun/agents` also exports the types `AgentManifestSource`, `AgentManifestSourceDiff`, `AgentManifestSourceSummary` and `AgentFolderDiagnostic`, and `AGENT_FOLDER_MEDIA_TYPE`.
46
+
47
+ ### Patch Changes
48
+
49
+ - 498d972: `nylorun check --fix` makes the edits the compiler's diagnostics carry (track T7). A diagnostic one edit fixes has a `fix` (`AgentFolderFix`, in `AgentFolderDiagnostic` and the Runtime API's): `rename_key` for an unknown key with exactly one did-you-mean (in `AGENT.md`, a trigger, a `SKILL.md`, a tool file, `runtime.json`, `mcp.json`, `flow.json` and a JSON Schema's keywords), at its line and column, and none when two keys are as close or the key it would be is already there; `crlf` for a file with CRLF line ends, which is now a `crlf` note (it is read as LF, as before); and `move_skill_secrets` for `secrets` in a root skill's `SKILL.md`, when `AGENT.md` lists none for the skill, or lists them all. Messages, manifests and hashes are unchanged. `applyFolderFixes` in `@nylorun/core/folder` makes them on text: the key token renamed with every other byte kept, LF line ends, and the secrets moved to `AGENT.md`'s `skills` block as `migrate folder` writes it, skipping (with why) a fix whose text is no longer where it was.
50
+
51
+ `nylorun check` marks each fixable problem `(fixable with --fix)` and writes nothing. `check --fix` makes the edits in the folder's files, prints each change by file, and checks again until nothing is left to fix; it leaves a file whose bytes changed since it was checked (writing nothing when one changes while it fixes), and a `runtime.json` that `extends` another. `check --json` gives each diagnostic's `fix`, and with `--fix` each folder's `fixed` and `unfixed`.
52
+
53
+ - Pin core to the tested release.
54
+ - Pin harness to the tested release.
55
+ - Updated dependencies [498d972]
56
+ - Updated dependencies [d23304e]
57
+ - Updated dependencies [b3dad06]
58
+ - Updated dependencies [b3dad06]
59
+ - Updated dependencies [b3dad06]
60
+ - Updated dependencies [498d972]
61
+ - Updated dependencies [b3dad06]
62
+ - Updated dependencies
63
+ - @nylorun/core@0.28.0-beta
64
+ - @nylorun/harness@0.29.4-beta
65
+
3
66
  ## 0.31.0-beta
4
67
 
5
68
  ### Minor Changes
@@ -541,6 +541,33 @@ export declare const AgentManifestSource: z.ZodObject<{
541
541
  sha256: z.ZodString;
542
542
  }, z.core.$strict>>;
543
543
  }, z.core.$strict>;
544
+ export declare const AgentManifestSourceDiff: z.ZodObject<{
545
+ name: z.ZodString;
546
+ from: z.ZodNumber;
547
+ to: z.ZodNumber;
548
+ added: z.ZodArray<z.ZodObject<{
549
+ path: z.ZodString;
550
+ size: z.ZodNumber;
551
+ sha256: z.ZodString;
552
+ }, z.core.$strict>>;
553
+ removed: z.ZodArray<z.ZodObject<{
554
+ path: z.ZodString;
555
+ size: z.ZodNumber;
556
+ sha256: z.ZodString;
557
+ }, z.core.$strict>>;
558
+ changed: z.ZodArray<z.ZodObject<{
559
+ path: z.ZodString;
560
+ from: z.ZodObject<{
561
+ size: z.ZodNumber;
562
+ sha256: z.ZodString;
563
+ }, z.core.$strict>;
564
+ to: z.ZodObject<{
565
+ size: z.ZodNumber;
566
+ sha256: z.ZodString;
567
+ }, z.core.$strict>;
568
+ }, z.core.$strict>>;
569
+ unchanged: z.ZodNumber;
570
+ }, z.core.$strict>;
544
571
  export declare const AgentManifestView: z.ZodObject<{
545
572
  name: z.ZodString;
546
573
  version: z.ZodNumber;
@@ -792,6 +819,22 @@ export declare const AgentManifestPlan: z.ZodObject<{
792
819
  line: z.ZodOptional<z.ZodNumber>;
793
820
  column: z.ZodOptional<z.ZodNumber>;
794
821
  pointer: z.ZodOptional<z.ZodString>;
822
+ fix: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
823
+ kind: z.ZodLiteral<"rename_key">;
824
+ file: z.ZodString;
825
+ line: z.ZodNumber;
826
+ column: z.ZodNumber;
827
+ from: z.ZodString;
828
+ to: z.ZodString;
829
+ }, z.core.$strict>, z.ZodObject<{
830
+ kind: z.ZodLiteral<"crlf">;
831
+ file: z.ZodString;
832
+ }, z.core.$strict>, z.ZodObject<{
833
+ kind: z.ZodLiteral<"move_skill_secrets">;
834
+ file: z.ZodString;
835
+ skill: z.ZodString;
836
+ secrets: z.ZodArray<z.ZodString>;
837
+ }, z.core.$strict>], "kind">>;
795
838
  }, z.core.$strict>>>;
796
839
  compiler: z.ZodOptional<z.ZodString>;
797
840
  }, z.core.$strict>;
@@ -925,6 +968,22 @@ export declare const SaveAgentManifestResponse: z.ZodObject<{
925
968
  line: z.ZodOptional<z.ZodNumber>;
926
969
  column: z.ZodOptional<z.ZodNumber>;
927
970
  pointer: z.ZodOptional<z.ZodString>;
971
+ fix: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
972
+ kind: z.ZodLiteral<"rename_key">;
973
+ file: z.ZodString;
974
+ line: z.ZodNumber;
975
+ column: z.ZodNumber;
976
+ from: z.ZodString;
977
+ to: z.ZodString;
978
+ }, z.core.$strict>, z.ZodObject<{
979
+ kind: z.ZodLiteral<"crlf">;
980
+ file: z.ZodString;
981
+ }, z.core.$strict>, z.ZodObject<{
982
+ kind: z.ZodLiteral<"move_skill_secrets">;
983
+ file: z.ZodString;
984
+ skill: z.ZodString;
985
+ secrets: z.ZodArray<z.ZodString>;
986
+ }, z.core.$strict>], "kind">>;
928
987
  }, z.core.$strict>>>;
929
988
  }, z.core.$strict>;
930
989
  export declare const ListAgentManifestsResponse: z.ZodObject<{
@@ -1917,6 +1976,9 @@ export declare const PutTenantLimitsRequest: z.ZodObject<{
1917
1976
  flowConcurrency: z.ZodOptional<z.ZodNumber>;
1918
1977
  flowMapItems: z.ZodOptional<z.ZodNumber>;
1919
1978
  flowLoopIterations: z.ZodOptional<z.ZodNumber>;
1979
+ folderFiles: z.ZodOptional<z.ZodNumber>;
1980
+ folderBytes: z.ZodOptional<z.ZodNumber>;
1981
+ folderTextFileBytes: z.ZodOptional<z.ZodNumber>;
1920
1982
  }, z.core.$strict>>;
1921
1983
  requestId: z.ZodOptional<z.ZodString>;
1922
1984
  }, z.core.$strict>;
@@ -1992,6 +2054,9 @@ export declare const TenantLimitsView: z.ZodObject<{
1992
2054
  flowConcurrency: z.ZodNumber;
1993
2055
  flowMapItems: z.ZodNumber;
1994
2056
  flowLoopIterations: z.ZodNumber;
2057
+ folderFiles: z.ZodNumber;
2058
+ folderBytes: z.ZodNumber;
2059
+ folderTextFileBytes: z.ZodNumber;
1995
2060
  }, z.core.$strict>;
1996
2061
  }, z.core.$strict>;
1997
2062
  export declare const ArtifactTree: z.ZodObject<{
@@ -2356,6 +2421,30 @@ export declare const SessionView: z.ZodObject<{
2356
2421
  error: z.ZodUnknown;
2357
2422
  }, z.core.$strict>>;
2358
2423
  }, z.core.$strict>;
2424
+ export declare const PutSessionModelRequest: z.ZodObject<{
2425
+ provider: z.ZodString;
2426
+ model: z.ZodString;
2427
+ }, z.core.$strict>;
2428
+ export declare const SessionModelView: z.ZodUnion<readonly [z.ZodObject<{
2429
+ configured: z.ZodLiteral<false>;
2430
+ choice: z.ZodOptional<z.ZodObject<{
2431
+ provider: z.ZodString;
2432
+ model: z.ZodString;
2433
+ }, z.core.$strict>>;
2434
+ }, z.core.$strict>, z.ZodObject<{
2435
+ configured: z.ZodLiteral<true>;
2436
+ source: z.ZodEnum<{
2437
+ session: "session";
2438
+ tenant: "tenant";
2439
+ }>;
2440
+ provider: z.ZodString;
2441
+ model: z.ZodString;
2442
+ baseUrl: z.ZodOptional<z.ZodString>;
2443
+ choice: z.ZodOptional<z.ZodObject<{
2444
+ provider: z.ZodString;
2445
+ model: z.ZodString;
2446
+ }, z.core.$strict>>;
2447
+ }, z.core.$strict>]>;
2359
2448
  export declare const LiveEvent: z.ZodObject<{
2360
2449
  schema: z.ZodLiteral<"nylorun.event/2">;
2361
2450
  eventId: z.ZodString;
@@ -13392,6 +13481,7 @@ export declare const TenantStatus: z.ZodObject<{
13392
13481
  model: z.ZodString;
13393
13482
  authType: z.ZodEnum<{
13394
13483
  api_key: "api_key";
13484
+ host_identity: "host_identity";
13395
13485
  oauth: "oauth";
13396
13486
  workload: "workload";
13397
13487
  }>;
@@ -13557,6 +13647,10 @@ export declare const SeedTenantConfigRequest: z.ZodObject<{
13557
13647
  expires: z.ZodNumber;
13558
13648
  }, z.core.$loose>, z.ZodObject<{
13559
13649
  type: z.ZodLiteral<"workload">;
13650
+ }, z.core.$strict>, z.ZodObject<{
13651
+ type: z.ZodLiteral<"host_identity">;
13652
+ project: z.ZodString;
13653
+ location: z.ZodString;
13560
13654
  }, z.core.$strict>], "type">;
13561
13655
  }, z.core.$strict>>;
13562
13656
  fixtureModel: z.ZodOptional<z.ZodLiteral<true>>;
@@ -13815,6 +13909,7 @@ export declare const ListProvidersResponse: z.ZodObject<{
13815
13909
  model: z.ZodString;
13816
13910
  authType: z.ZodEnum<{
13817
13911
  api_key: "api_key";
13912
+ host_identity: "host_identity";
13818
13913
  oauth: "oauth";
13819
13914
  workload: "workload";
13820
13915
  }>;
@@ -13862,6 +13957,7 @@ export declare const HostModelView: z.ZodUnion<readonly [z.ZodObject<{
13862
13957
  model: z.ZodString;
13863
13958
  authType: z.ZodEnum<{
13864
13959
  api_key: "api_key";
13960
+ host_identity: "host_identity";
13865
13961
  oauth: "oauth";
13866
13962
  workload: "workload";
13867
13963
  }>;
@@ -13930,6 +14026,10 @@ export declare const PutHostModelRequest: z.ZodObject<{
13930
14026
  expires: z.ZodNumber;
13931
14027
  }, z.core.$loose>, z.ZodObject<{
13932
14028
  type: z.ZodLiteral<"workload">;
14029
+ }, z.core.$strict>, z.ZodObject<{
14030
+ type: z.ZodLiteral<"host_identity">;
14031
+ project: z.ZodString;
14032
+ location: z.ZodString;
13933
14033
  }, z.core.$strict>], "type">;
13934
14034
  }, z.core.$strict>;
13935
14035
  export declare const SelectHostModelRequest: z.ZodObject<{
@@ -5,7 +5,7 @@
5
5
  * global registry.
6
6
  */
7
7
  import { z } from "zod";
8
- import { QueuedResponseSchema, TriggerListSchema, MemoryListSchema, MemorySchema, PutMemoryRequestSchema, MemoryVersionPageSchema, MemoryVersionDetailSchema, TriggerRunPageSchema, RunTriggerResponseSchema, WebhookSecretResponseSchema, WebhookAcceptedResponseSchema, AgentManifestV8Schema, EnvironmentSecretSlotsSchema, DeleteAgentManifestResponseSchema, AgentManifestPlanSchema, AgentManifestVersionsSchema, AgentManifestViewSchema, ListAgentManifestsResponseSchema, PutAgentManifestRequestSchema, PutAgentFolderRequestSchema, AgentManifestSourceSchema, PutAgentSecretValueRequestSchema, SaveAgentManifestResponseSchema, SessionListItemSchema, ModelCallSchema, SessionPageSchema, SessionManifestViewSchema, HistoryPageSchema, SessionUsageTotalsSchema, ModelCallsPageSchema, ModelCallExportPageSchema, AgentPageSchema, ToolCallSchema, ToolCallsPageSchema, TurnTraceSchema, JwksSchema, SigningKeyListSchema, SigningKeyViewSchema, RotateSigningKeysRequestSchema, RevokeSigningKeyRequestSchema, MeResponseSchema, TenantStatusSchema, ResetTenantRequestSchema, ResetTenantResponseSchema, SeedTenantConfigRequestSchema, SeedTenantConfigResponseSchema, HostModelCatalogSchema, TenantSandboxViewSchema, ModelUsageTotalsSchema, ModelBudgetsSchema, PutModelBudgetsRequestSchema, ToolPolicySchema, PutToolPolicyRequestSchema, PutModelInputsRequestSchema, ModelInputsViewSchema, PutTenantEnvironmentsRequestSchema, ListProvidersResponseSchema, HostModelViewSchema, PutHostModelRequestSchema, SelectHostModelRequestSchema, PutAgentSecretRequestSchema, AgentSecretViewSchema, ListAgentSecretsResponseSchema, McpPreviewRequestSchema, McpPreviewSchema, CredentialCoverageRequestSchema, CredentialCoverageSchema, DeletedResponseSchema, ListSessionsResponseSchema, LiveEventSchema, PutAgentRequestSchema, PutSessionRequestSchema, SessionItemsResponseSchema, SessionViewSchema, StreamClosedFrameSchema, AcceptedResponseSchema, SandboxToolOutcomeSchema, AgentViewSchema, AgentSnapshotSchema, RestoreAgentRequestSchema, ForkAgentRequestSchema, AgentForkPlanSchema, ForkAgentResponseSchema, PutAgentDryRunResponseSchema, RestartAgentRequestSchema, AgentRestoreSchema, AgentDiskVersionsSchema, ListAgentsResponseSchema, DeleteAgentResponseSchema, ListAgentEventsResponseSchema, ArtifactViewSchema, ArtifactListItemSchema, ArtifactPageSchema, ArtifactVersionViewSchema, ListArtifactsResponseSchema, UploadArtifactResponseSchema, DefinitionFileViewSchema, DeleteArtifactResponseSchema, CreateArtifactLinkRequestSchema, ArtifactLinkSchema, PutTenantArtifactsRequestSchema, PutTenantSkillsRequestSchema, PutTenantLimitsRequestSchema, TenantLimitsViewSchema, TenantArtifactsViewSchema, TenantSkillsViewSchema, ArtifactTreeSchema, ArtifactDiffSchema, SessionCommandSchema, ListOperatorKeysResponseSchema, PutOperatorKeyResponseSchema, DeleteOperatorKeyResponseSchema, OperatorKeySchema, ProtocolRejectedResponseSchema, RejectedResponseSchema, EVENT_SCHEMAS, EVENT_TYPES, } from "@nylorun/core/contracts";
8
+ import { PutSessionModelRequestSchema, SessionModelViewSchema, QueuedResponseSchema, TriggerListSchema, MemoryListSchema, MemorySchema, PutMemoryRequestSchema, MemoryVersionPageSchema, MemoryVersionDetailSchema, TriggerRunPageSchema, RunTriggerResponseSchema, WebhookSecretResponseSchema, WebhookAcceptedResponseSchema, AgentManifestV8Schema, EnvironmentSecretSlotsSchema, DeleteAgentManifestResponseSchema, AgentManifestPlanSchema, AgentManifestVersionsSchema, AgentManifestViewSchema, ListAgentManifestsResponseSchema, PutAgentManifestRequestSchema, PutAgentFolderRequestSchema, AgentManifestSourceSchema, AgentManifestSourceDiffSchema, PutAgentSecretValueRequestSchema, SaveAgentManifestResponseSchema, SessionListItemSchema, ModelCallSchema, SessionPageSchema, SessionManifestViewSchema, HistoryPageSchema, SessionUsageTotalsSchema, ModelCallsPageSchema, ModelCallExportPageSchema, AgentPageSchema, ToolCallSchema, ToolCallsPageSchema, TurnTraceSchema, JwksSchema, SigningKeyListSchema, SigningKeyViewSchema, RotateSigningKeysRequestSchema, RevokeSigningKeyRequestSchema, MeResponseSchema, TenantStatusSchema, ResetTenantRequestSchema, ResetTenantResponseSchema, SeedTenantConfigRequestSchema, SeedTenantConfigResponseSchema, HostModelCatalogSchema, TenantSandboxViewSchema, ModelUsageTotalsSchema, ModelBudgetsSchema, PutModelBudgetsRequestSchema, ToolPolicySchema, PutToolPolicyRequestSchema, PutModelInputsRequestSchema, ModelInputsViewSchema, PutTenantEnvironmentsRequestSchema, ListProvidersResponseSchema, HostModelViewSchema, PutHostModelRequestSchema, SelectHostModelRequestSchema, PutAgentSecretRequestSchema, AgentSecretViewSchema, ListAgentSecretsResponseSchema, McpPreviewRequestSchema, McpPreviewSchema, CredentialCoverageRequestSchema, CredentialCoverageSchema, DeletedResponseSchema, ListSessionsResponseSchema, LiveEventSchema, PutAgentRequestSchema, PutSessionRequestSchema, SessionItemsResponseSchema, SessionViewSchema, StreamClosedFrameSchema, AcceptedResponseSchema, SandboxToolOutcomeSchema, AgentViewSchema, AgentSnapshotSchema, RestoreAgentRequestSchema, ForkAgentRequestSchema, AgentForkPlanSchema, ForkAgentResponseSchema, PutAgentDryRunResponseSchema, RestartAgentRequestSchema, AgentRestoreSchema, AgentDiskVersionsSchema, ListAgentsResponseSchema, DeleteAgentResponseSchema, ListAgentEventsResponseSchema, ArtifactViewSchema, ArtifactListItemSchema, ArtifactPageSchema, ArtifactVersionViewSchema, ListArtifactsResponseSchema, UploadArtifactResponseSchema, DefinitionFileViewSchema, DeleteArtifactResponseSchema, CreateArtifactLinkRequestSchema, ArtifactLinkSchema, PutTenantArtifactsRequestSchema, PutTenantSkillsRequestSchema, PutTenantLimitsRequestSchema, TenantLimitsViewSchema, TenantArtifactsViewSchema, TenantSkillsViewSchema, ArtifactTreeSchema, ArtifactDiffSchema, SessionCommandSchema, ListOperatorKeysResponseSchema, PutOperatorKeyResponseSchema, DeleteOperatorKeyResponseSchema, OperatorKeySchema, ProtocolRejectedResponseSchema, RejectedResponseSchema, EVENT_SCHEMAS, EVENT_TYPES, } from "@nylorun/core/contracts";
9
9
  function named(id, schema) {
10
10
  return schema.meta({ id });
11
11
  }
@@ -23,6 +23,7 @@ export const AgentManifest = named("AgentManifest", AgentManifestV8Schema);
23
23
  export const PutAgentManifestRequest = named("PutAgentManifestRequest", PutAgentManifestRequestSchema);
24
24
  export const PutAgentFolderRequest = named("PutAgentFolderRequest", PutAgentFolderRequestSchema);
25
25
  export const AgentManifestSource = named("AgentManifestSource", AgentManifestSourceSchema);
26
+ export const AgentManifestSourceDiff = named("AgentManifestSourceDiff", AgentManifestSourceDiffSchema);
26
27
  export const AgentManifestView = named("AgentManifestView", AgentManifestViewSchema);
27
28
  export const AgentManifestPlan = named("AgentManifestPlan", AgentManifestPlanSchema);
28
29
  export const SaveAgentManifestResponse = named("SaveAgentManifestResponse", SaveAgentManifestResponseSchema);
@@ -76,6 +77,8 @@ export const WebhookAcceptedResponse = named("WebhookAcceptedResponse", WebhookA
76
77
  export const ListSessionsResponse = named("ListSessionsResponse", ListSessionsResponseSchema);
77
78
  export const PutSessionRequest = named("PutSessionRequest", PutSessionRequestSchema);
78
79
  export const SessionView = named("SessionView", SessionViewSchema);
80
+ export const PutSessionModelRequest = named("PutSessionModelRequest", PutSessionModelRequestSchema);
81
+ export const SessionModelView = named("SessionModelView", SessionModelViewSchema);
79
82
  export const LiveEvent = named("LiveEvent", LiveEventSchema);
80
83
  /** `environment.state` → `EnvironmentStateEvent`. */
81
84
  export function eventComponentName(type) {
@@ -4,7 +4,8 @@
4
4
  * moves every Agent that tracks the manifest to the new version; a pinned one stays. Reading needs `agents:read` (or a write scope), saving and deleting
5
5
  * `agent-manifests:write`, from any client (Q12). A token caller reaches none: a manifest holds
6
6
  * an agent's instructions, tools and servers. A save may send an agent folder by hash instead of
7
- * the manifest (track T6): the Runtime compiles it and keeps it as the version's source.
7
+ * the manifest (track T6): the Runtime compiles it and keeps it as the version's source, and
8
+ * `source/diff` compares two versions' sources (track T7).
8
9
  */
9
10
  import type { OpenAPIHono } from "@hono/zod-openapi";
10
11
  import type { TenantEnv } from "../app.js";
@@ -1,9 +1,9 @@
1
1
  import { z } from "zod";
2
2
  import { AGENT_FOLDER_MEDIA_TYPE, contentTypeFor, isAgentManifestName, PutAgentFolderRequestSchema, PutAgentManifestRequestSchema, } from "@nylorun/core/contracts";
3
- import { AgentManifestPlan, AgentManifestSource, AgentManifestVersions, AgentManifestView, DeleteAgentManifestResponse, ListAgentManifestsResponse, PutAgentFolderRequest, PutAgentManifestRequest, SaveAgentManifestResponse, } from "../../components.js";
3
+ import { AgentManifestPlan, AgentManifestSource, AgentManifestSourceDiff, AgentManifestVersions, AgentManifestView, DeleteAgentManifestResponse, ListAgentManifestsResponse, PutAgentFolderRequest, PutAgentManifestRequest, SaveAgentManifestResponse, } from "../../components.js";
4
4
  import { requirePrincipal } from "../../../tenant/auth.js";
5
5
  import { fail } from "../../../tenant/http.js";
6
- import { agentManifestVersions, deleteAgentManifest, getAgentManifest, getAgentManifestSource, listAgentManifests, readAgentManifestSourceFile, saveAgentFolder, saveAgentManifest, } from "../../../tenant/agent-manifests.js";
6
+ import { agentManifestVersions, deleteAgentManifest, diffAgentManifestSources, getAgentManifest, getAgentManifestSource, listAgentManifests, readAgentManifestSourceFile, saveAgentFolder, saveAgentManifest, } from "../../../tenant/agent-manifests.js";
7
7
  import { readJson } from "../body.js";
8
8
  import { tenantRoute } from "../define.js";
9
9
  import { jsonResponse } from "../respond.js";
@@ -172,6 +172,33 @@ export function agentManifestRoutes(api) {
172
172
  headers: { "content-type": contentTypeFor(path), "content-length": String(file.size) },
173
173
  });
174
174
  });
175
+ tenantRoute(api, READ, {
176
+ method: "get",
177
+ path: "/v1/agent-manifests/{name}/source/diff",
178
+ tags: ["Agent manifests"],
179
+ summary: "Compare two versions' folders",
180
+ description: "The files added, removed and changed from version `from`'s source to version `to`'s, by path, a file changed when its SHA-256 differs (feature `agent-folders`). Each list is sorted by path. A version saved as JSON has no source (`404`).",
181
+ request: {
182
+ params: namePath,
183
+ query: z.object({
184
+ from: z.string().meta({ description: "The version to compare from" }),
185
+ to: z.string().meta({ description: "The version to compare to" }),
186
+ }),
187
+ },
188
+ responses: {
189
+ 200: json(AgentManifestSourceDiff, "What changed"),
190
+ 400: { description: "`from` or `to` is not a version number" },
191
+ 404: { description: "No such version, or one was saved as JSON and has no source" },
192
+ },
193
+ }, async (c) => {
194
+ requirePrincipal(c.get("scope"));
195
+ const name = nameOf(c.req.param("name"));
196
+ const from = Number(c.req.query("from"));
197
+ const to = Number(c.req.query("to"));
198
+ if (!Number.isInteger(from) || from < 1 || !Number.isInteger(to) || to < 1)
199
+ fail(400, "from and to are the versions to compare: positive integers");
200
+ return jsonResponse(200, await diffAgentManifestSources(c.env.tenant, name, from, to));
201
+ });
175
202
  tenantRoute(api, WRITE, {
176
203
  method: "delete",
177
204
  path: "/v1/agent-manifests/{name}",
@@ -1,10 +1,10 @@
1
1
  import { RESPONSE_ALREADY_SENT } from "@hono/node-server/utils/response";
2
2
  import { z } from "zod";
3
3
  import { PROTOCOL_HEADER } from "@nylorun/core/compatibility";
4
- import { PutSessionRequestSchema, SessionCommandSchema, } from "@nylorun/core/contracts";
4
+ import { PutSessionModelRequestSchema, PutSessionRequestSchema, SessionCommandSchema, } from "@nylorun/core/contracts";
5
5
  import { handleSessionSandboxTool, SandboxRouteError } from "../../../core/sandbox-routes.js";
6
6
  import { traceContextOf } from "../../../traces/ids.js";
7
- import { AcceptedResponse, QueuedResponse, ListSessionsResponse, SessionPage, HistoryPage, SessionEvent, PutSessionRequest, SandboxToolOutcome, SessionCommand, SessionItemsResponse, SessionView, StreamClosedFrame, } from "../../components.js";
7
+ import { AcceptedResponse, QueuedResponse, ListSessionsResponse, SessionPage, HistoryPage, SessionEvent, PutSessionModelRequest, PutSessionRequest, SandboxToolOutcome, SessionCommand, SessionItemsResponse, SessionModelView, SessionView, StreamClosedFrame, } from "../../components.js";
8
8
  import { accessOf, requireApplication } from "../../../tenant/auth.js";
9
9
  import { command } from "../../../tenant/commands.js";
10
10
  import { loadSession, sessionOf } from "../../../tenant/context.js";
@@ -12,6 +12,7 @@ import { fail } from "../../../tenant/http.js";
12
12
  import { agentGrantsOf, sandboxRouteDeps } from "../../../tenant/agents.js";
13
13
  import { readHistory, requestCursor, streamHolderOf, streamSessionEvents } from "../../../tenant/session-streams.js";
14
14
  import { listSessions, putSession, sessionView } from "../../../tenant/sessions.js";
15
+ import { chooseSessionModel, readSessionModel } from "../../../tenant/session-model.js";
15
16
  import { readJson } from "../body.js";
16
17
  import { TraceHeaders, tenantRoute } from "../define.js";
17
18
  import { pageQuery, pageSize, sessionPage, sessionPageQuery, parseQuery } from "./reads.js";
@@ -20,6 +21,18 @@ const OWN_SESSIONS = {
20
21
  credentials: ["application", "subject", "token"],
21
22
  scopes: ["sessions:own"],
22
23
  };
24
+ /**
25
+ * A session's own model costs what its model costs: the application chooses it (for a subject
26
+ * too), never a token a person's browser holds.
27
+ */
28
+ const APP_SESSIONS = {
29
+ credentials: ["application", "subject"],
30
+ scopes: ["sessions:own"],
31
+ };
32
+ function refuseToken(scope) {
33
+ if (scope.kind === "token")
34
+ fail(403, "A token caller cannot choose a session's model", { code: "scope_required" });
35
+ }
23
36
  const json = (schema, description) => ({
24
37
  description,
25
38
  content: { "application/json": { schema } },
@@ -122,6 +135,54 @@ export function sessionRoutes(api) {
122
135
  const id = c.req.param("sessionId");
123
136
  return jsonResponse(200, await ctx.store.tx(async (t) => sessionView(t, await sessionOf(t, id, accessOf(scope)))));
124
137
  });
138
+ tenantRoute(api, OWN_SESSIONS, {
139
+ method: "get",
140
+ path: "/v1/sessions/{sessionId}/model",
141
+ tags: ["Sessions"],
142
+ summary: "Get a session's model",
143
+ description: "The model the session's next turn uses (feature `session-model`): its own (`source: \"session\"`) while its provider is connected, else the Tenant's (`source: \"tenant\"`). `choice` is the session's own model, kept while its provider is disconnected.",
144
+ request: { params: sessionId },
145
+ responses: { 200: json(SessionModelView, "The session's model") },
146
+ }, async (c) => {
147
+ const scope = c.get("scope");
148
+ return jsonResponse(200, await readSessionModel(c.env.tenant, c.req.param("sessionId"), accessOf(scope)));
149
+ });
150
+ tenantRoute(api, APP_SESSIONS, {
151
+ method: "put",
152
+ path: "/v1/sessions/{sessionId}/model",
153
+ tags: ["Sessions"],
154
+ summary: "Choose a session's model",
155
+ description: "Chooses one of a connected provider's models for the session's turns (feature `session-model`), in place of the Tenant's model. The Tenant's model and every other session's stay as they are. Only the application sets it, for itself or for a subject: a trusted issuer's token is refused. A turn calls one model, so the choice changes only while no turn runs or waits (`409 session_busy`). A provider that is not connected, or a model its catalog does not list, is `422 request_rejected`.",
156
+ request: { params: sessionId, body: body(PutSessionModelRequest) },
157
+ responses: {
158
+ 200: json(SessionModelView, "The session's model"),
159
+ 403: { description: "A trusted issuer's token (`scope_required`)" },
160
+ 409: { description: "A turn is running or waits (`session_busy`)" },
161
+ 422: { description: "The provider is not connected, or the model is not one of its models" },
162
+ },
163
+ }, async (c) => {
164
+ const scope = c.get("scope");
165
+ refuseToken(scope);
166
+ const choice = PutSessionModelRequestSchema.parse(await readJson(c));
167
+ return jsonResponse(200, await chooseSessionModel(c.env.tenant, c.req.param("sessionId"), choice, accessOf(scope)));
168
+ });
169
+ tenantRoute(api, APP_SESSIONS, {
170
+ method: "delete",
171
+ path: "/v1/sessions/{sessionId}/model",
172
+ tags: ["Sessions"],
173
+ summary: "Clear a session's model",
174
+ description: "Clears the session's own model (feature `session-model`): its next turns use the Tenant's model. Refused while a turn runs or waits (`409 session_busy`).",
175
+ request: { params: sessionId },
176
+ responses: {
177
+ 200: json(SessionModelView, "The session's model"),
178
+ 403: { description: "A trusted issuer's token (`scope_required`)" },
179
+ 409: { description: "A turn is running or waits (`session_busy`)" },
180
+ },
181
+ }, async (c) => {
182
+ const scope = c.get("scope");
183
+ refuseToken(scope);
184
+ return jsonResponse(200, await chooseSessionModel(c.env.tenant, c.req.param("sessionId"), undefined, accessOf(scope)));
185
+ });
125
186
  tenantRoute(api, OWN_SESSIONS, {
126
187
  method: "get",
127
188
  path: "/v1/sessions/{sessionId}/items",
@@ -121,6 +121,7 @@ const RUNTIME_TAGS = [
121
121
  "GET /v1/agent-manifests/{name}/versions/{version}",
122
122
  "GET /v1/agent-manifests/{name}/versions/{version}/source",
123
123
  "GET /v1/agent-manifests/{name}/versions/{version}/source/files/{path}",
124
+ "GET /v1/agent-manifests/{name}/source/diff",
124
125
  "DELETE /v1/agent-manifests/{name}",
125
126
  ],
126
127
  },
@@ -193,6 +194,9 @@ const RUNTIME_TAGS = [
193
194
  "GET /v1/sessions/{sessionId}/events",
194
195
  "GET /v1/sessions/{sessionId}/items",
195
196
  "GET /v1/sessions/{sessionId}/manifest",
197
+ "GET /v1/sessions/{sessionId}/model",
198
+ "PUT /v1/sessions/{sessionId}/model",
199
+ "DELETE /v1/sessions/{sessionId}/model",
196
200
  "GET /v1/sessions/{sessionId}/usage",
197
201
  "GET /v1/sessions/{sessionId}/calls/model",
198
202
  "GET /v1/sessions/{sessionId}/calls/tool",
@@ -23,8 +23,8 @@ export function createModelCallHandler(options) {
23
23
  request = call;
24
24
  const gate = inProcessModelGate({
25
25
  root: vault.root,
26
- readHostModel: () => vault.readHostModel(),
27
- writeHostCredential: (credential) => vault.writeHostCredential(credential),
26
+ readHostModel: (request) => vault.readHostModel(request.sessionId),
27
+ writeHostCredential: (credential, provider) => vault.writeHostCredential(credential, provider),
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) } : {}),
@@ -15,9 +15,10 @@ import type { ModelCaller } from "../model/call-headers.js";
15
15
  export interface InProcessModelGateOptions {
16
16
  /** The Tenant's home: `piModel` redacts secrets it finds there from failure messages. */
17
17
  readonly root?: string;
18
- readonly readHostModel: () => Promise<HostModelSecret | undefined>;
19
- /** Writes back a credential pi-ai refreshed (OAuth). */
20
- readonly writeHostCredential: (credential: Credential) => Promise<void>;
18
+ /** The host model a call uses: its session's own model (`session-model`), else the Tenant's. */
19
+ readonly readHostModel: (request: ModelGateRequest) => Promise<HostModelSecret | undefined>;
20
+ /** Writes back a credential pi-ai refreshed (OAuth) to `provider`'s, the one the call read. */
21
+ readonly writeHostCredential: (credential: Credential, provider: string) => Promise<void>;
21
22
  readonly settings?: ModelCallSettings;
22
23
  /** Reads the files a call's prompt names (protocol 6): artifacts, for model-gate. */
23
24
  readonly files?: (request: ModelGateRequest) => FileResolver;
@@ -45,8 +45,8 @@ export function tenantModelGate(options) {
45
45
  // The Tenant's Runtime limits `model*`, read for this call.
46
46
  const gate = inProcessModelGate({
47
47
  root: options.root,
48
- readHostModel: () => vault.readHostModel(),
49
- writeHostCredential: (credential) => vault.updateHostCredential(credential),
48
+ readHostModel: (request) => vault.readHostModel(request.sessionId),
49
+ writeHostCredential: (credential, provider) => vault.updateHostCredential(credential, provider),
50
50
  settings: await modelCallSettings(options.store),
51
51
  ...(options.blobs ? { files: callFiles(options.store, options.blobs) } : {}),
52
52
  caller: callCaller(options.store),
@@ -71,11 +71,17 @@ export async function serve(store, gate, request, signal) {
71
71
  export function inProcessModelGate(options) {
72
72
  return {
73
73
  async call(request, signal) {
74
- // One adapter per call, as before the gate: it reads the host model when invoked.
74
+ // One adapter per call, as before the gate: it reads the host model when invoked, and a
75
+ // refreshed credential goes back to the provider it read.
76
+ let provider;
75
77
  const adapter = piModel({
76
78
  ...(options.root !== undefined ? { root: options.root } : {}),
77
- readHostModel: options.readHostModel,
78
- writeHostCredential: options.writeHostCredential,
79
+ readHostModel: async () => {
80
+ const stored = await options.readHostModel(request);
81
+ provider = stored?.provider;
82
+ return stored;
83
+ },
84
+ writeHostCredential: (credential) => options.writeHostCredential(credential, provider),
79
85
  ...(options.settings ? { settings: options.settings } : {}),
80
86
  ...(options.files ? { files: options.files(request) } : {}),
81
87
  ...(options.caller ? { caller: () => options.caller(request) } : {}),
@@ -22,9 +22,10 @@ export interface TenantVault {
22
22
  readonly store: SessionStore;
23
23
  /** The Tenant's home: secrets found there are redacted from failure messages. */
24
24
  readonly root: string;
25
- readHostModel(): Promise<HostModelSecret | undefined>;
26
- /** Writes back a credential pi-ai refreshed (OAuth). */
27
- writeHostCredential(credential: Credential): Promise<void>;
25
+ /** The host model a call uses: its session's own model (`session-model`), else the Tenant's. */
26
+ readHostModel(sessionId?: string): Promise<HostModelSecret | undefined>;
27
+ /** Writes back a credential pi-ai refreshed (OAuth) to `provider`'s, else the active one's. */
28
+ writeHostCredential(credential: Credential, provider?: string): Promise<void>;
28
29
  /** A session, for the remote MCP servers and HTTP tools its pinned manifest declares. */
29
30
  session(sessionId: string): Promise<Session | undefined>;
30
31
  /**
@@ -47,8 +47,8 @@ export function createTenantVaults(options) {
47
47
  tenantId,
48
48
  store,
49
49
  root: paths.home,
50
- readHostModel: () => vault.readHostModel(),
51
- writeHostCredential: (credential) => vault.updateHostCredential(credential),
50
+ readHostModel: (sessionId) => vault.readHostModel(sessionId),
51
+ writeHostCredential: (credential, provider) => vault.updateHostCredential(credential, provider),
52
52
  session,
53
53
  authorizeMcp: (sessionId, request) => authorizeSessionMcp(credentials, session, sessionId, request),
54
54
  keys: () => keys,
@@ -82,7 +82,7 @@ options = {}) {
82
82
  });
83
83
  // The model an agent's harness decides files by, read outside the session's transaction.
84
84
  const model = options.harness && !isWorkflowManifest(opened.current.manifest)
85
- ? await currentModelDescriptor(ctx)
85
+ ? await currentModelDescriptor(ctx, opened.current.model)
86
86
  : undefined;
87
87
  const started = model ? { ...opened, model } : opened;
88
88
  // One history (P0.3): the engine resumes from the transcript folded from the record.
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 { useHostIdentity } from "../vault/host-identity.js";
68
69
  import { useWorkloadToken } from "../vault/workload-token.js";
69
70
  const nodeRequire = createRequire(import.meta.url);
70
71
  function coreVersion() {
@@ -220,6 +221,7 @@ export async function main() {
220
221
  if (stack.tuning)
221
222
  useHostTuning(stack.tuning.values, stack.tuning.overrides);
222
223
  useWorkloadToken(stack.workloadToken ?? {});
224
+ useHostIdentity(stack.modelHostIdentity ?? []);
223
225
  if (stack.services.has("gates") || stack.services.has("keys") || stack.services.has("egress"))
224
226
  return runGates(stack);
225
227
  if (stack.services.has("harness"))
@@ -188,6 +188,11 @@ export interface StackConfig {
188
188
  file?: string;
189
189
  origins?: string[];
190
190
  };
191
+ /**
192
+ * `NYLORUN_MODEL_HOST_IDENTITY`: the comma-separated providers whose models may use the Host's
193
+ * own identity (ADR-71, `vault/host-identity.ts`). Absent: none.
194
+ */
195
+ modelHostIdentity?: string[];
191
196
  /** Present when the process runs gates. */
192
197
  gates?: GatesConfig;
193
198
  /** Present when the process runs egress. */
@@ -50,6 +50,7 @@ import { isTenantId } from "@nylorun/core/compatibility";
50
50
  import { LOOPBACK_HOSTS } from "@nylorun/core/contracts";
51
51
  import { blockedAddresses } from "../sandbox/pods/network-gate.js";
52
52
  import { HostTuningError, parseHostTuning } from "../tuning.js";
53
+ import { HOST_IDENTITY_PROVIDERS } from "../vault/host-identity.js";
53
54
  export const RUNTIME_SERVICES = ["core", "loop", "gates", "keys", "egress", "harness"];
54
55
  /**
55
56
  * Services that may share a process (D12): they hold the same secrets and parse the same
@@ -327,6 +328,7 @@ export function parseStackConfig(env, argv) {
327
328
  const delivery = parseDelivery(env);
328
329
  const tenant = servesApi ? parseTenant(env) : undefined;
329
330
  const workloadToken = parseWorkloadToken(env);
331
+ const modelHostIdentity = parseModelHostIdentity(env);
330
332
  const objectStore = parseObjectStore(env);
331
333
  const traces = services.has("core") ? parseTraces(env) : undefined;
332
334
  const removedResolverVariables = Object.keys(env)
@@ -350,6 +352,7 @@ export function parseStackConfig(env, argv) {
350
352
  ...(delivery ? { delivery } : {}),
351
353
  ...(tenant ? { tenant } : {}),
352
354
  ...(workloadToken ? { workloadToken } : {}),
355
+ ...(modelHostIdentity ? { modelHostIdentity } : {}),
353
356
  ...(objectStore ? { objectStore } : {}),
354
357
  ...(traces ? { traces } : {}),
355
358
  ...(tuning ? { tuning } : {}),
@@ -468,6 +471,18 @@ function parseWorkloadToken(env) {
468
471
  return undefined;
469
472
  return { ...(file ? { file } : {}), ...(origins ? { origins } : {}) };
470
473
  }
474
+ /** `StackConfig.modelHostIdentity` (ADR-71): each a provider that can use the Host's identity. */
475
+ function parseModelHostIdentity(env) {
476
+ const raw = read(env, "NYLORUN_MODEL_HOST_IDENTITY");
477
+ if (raw === undefined)
478
+ return undefined;
479
+ const providers = [...new Set(raw.split(",").map((item) => item.trim()).filter((item) => item !== ""))];
480
+ for (const provider of providers) {
481
+ if (!HOST_IDENTITY_PROVIDERS.includes(provider))
482
+ throw new StackConfigError(`NYLORUN_MODEL_HOST_IDENTITY: ${provider} can't use the Host's identity (only ${HOST_IDENTITY_PROVIDERS.join(", ")})`);
483
+ }
484
+ return providers.length ? providers : undefined;
485
+ }
471
486
  /** `NYLORUN_WORKLOAD_TOKEN_ORIGINS`: each an `https` origin (`http` only to a loopback host). */
472
487
  function parseOrigins(raw) {
473
488
  if (raw === undefined)