@nylorun/runtime 0.32.0-beta → 0.34.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 (45) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/api/ag-ui/routes.js +4 -2
  3. package/dist/api/ag-ui/translate.js +3 -1
  4. package/dist/api/components.d.ts +35 -0
  5. package/dist/api/components.js +3 -1
  6. package/dist/api/http/routes/sessions.js +63 -2
  7. package/dist/api/openapi.js +3 -0
  8. package/dist/gates/credential-provider.d.ts +42 -0
  9. package/dist/gates/credential-provider.js +140 -0
  10. package/dist/gates/egress-credentials.d.ts +12 -0
  11. package/dist/gates/egress-credentials.js +60 -6
  12. package/dist/gates/egress.d.ts +14 -1
  13. package/dist/gates/egress.js +67 -19
  14. package/dist/gates/handler.js +2 -2
  15. package/dist/gates/in-process.d.ts +4 -3
  16. package/dist/gates/in-process.js +11 -5
  17. package/dist/gates/tenant-vaults.d.ts +4 -3
  18. package/dist/gates/tenant-vaults.js +2 -2
  19. package/dist/harness-api/start.js +30 -3
  20. package/dist/host/gates.d.ts +8 -1
  21. package/dist/host/gates.js +6 -1
  22. package/dist/host/main.js +17 -1
  23. package/dist/host/stack-config.d.ts +17 -1
  24. package/dist/host/stack-config.js +47 -1
  25. package/dist/management-openapi.json +61 -5
  26. package/dist/model/descriptor.d.ts +5 -4
  27. package/dist/model/descriptor.js +4 -3
  28. package/dist/model/pi-model.js +17 -1
  29. package/dist/openapi.json +516 -2
  30. package/dist/tenant/context.d.ts +12 -1
  31. package/dist/tenant/runtime.d.ts +6 -0
  32. package/dist/tenant/runtime.js +1 -0
  33. package/dist/tenant/session-model.d.ts +16 -0
  34. package/dist/tenant/session-model.js +46 -0
  35. package/dist/tuning.d.ts +6 -0
  36. package/dist/tuning.js +14 -0
  37. package/dist/vault/host-identity.d.ts +24 -0
  38. package/dist/vault/host-identity.js +35 -0
  39. package/dist/vault/host-model.d.ts +9 -5
  40. package/dist/vault/host-model.js +30 -19
  41. package/dist/vault/service.d.ts +18 -2
  42. package/dist/vault/service.js +52 -9
  43. package/dist/version.d.ts +1 -1
  44. package/dist/version.js +1 -1
  45. package/package.json +6 -6
package/dist/openapi.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "openapi": "3.2.0",
3
3
  "info": {
4
4
  "title": "Nylorun Runtime API",
5
- "version": "0.32.0-beta",
5
+ "version": "0.34.0-beta",
6
6
  "x-nylorun-protocol": {
7
7
  "min": 16,
8
8
  "max": 18,
@@ -34,7 +34,10 @@
34
34
  "traces",
35
35
  "artifact-reads",
36
36
  "gateway-credentials",
37
- "agent-folders"
37
+ "agent-folders",
38
+ "session-model",
39
+ "model-host-identity",
40
+ "credential-provider"
38
41
  ]
39
42
  },
40
43
  "description": "For developers: agent manifests, Agents, sessions (with AG-UI and A2A) and artifacts, through `@nylorun/agents`. Every Tenant serves it on its one URL, beside the Management API (`/openapi/management.json`). Every request sends `Nylorun-Protocol`. Who may call each operation is its `security` and its `x-nylorun-credentials` and `x-nylorun-scopes` (the subject scopes that reach it) fields: an application key (servers) or a trusted issuer's token (browsers and apps)."
@@ -8867,6 +8870,113 @@
8867
8870
  ],
8868
8871
  "additionalProperties": false
8869
8872
  },
8873
+ "SessionModelView": {
8874
+ "anyOf": [
8875
+ {
8876
+ "type": "object",
8877
+ "properties": {
8878
+ "configured": {
8879
+ "type": "boolean",
8880
+ "enum": [
8881
+ false
8882
+ ]
8883
+ },
8884
+ "choice": {
8885
+ "type": "object",
8886
+ "properties": {
8887
+ "provider": {
8888
+ "type": "string",
8889
+ "minLength": 1
8890
+ },
8891
+ "model": {
8892
+ "type": "string",
8893
+ "minLength": 1
8894
+ }
8895
+ },
8896
+ "required": [
8897
+ "provider",
8898
+ "model"
8899
+ ],
8900
+ "additionalProperties": false
8901
+ }
8902
+ },
8903
+ "required": [
8904
+ "configured"
8905
+ ],
8906
+ "additionalProperties": false
8907
+ },
8908
+ {
8909
+ "type": "object",
8910
+ "properties": {
8911
+ "configured": {
8912
+ "type": "boolean",
8913
+ "enum": [
8914
+ true
8915
+ ]
8916
+ },
8917
+ "source": {
8918
+ "type": "string",
8919
+ "enum": [
8920
+ "session",
8921
+ "tenant"
8922
+ ]
8923
+ },
8924
+ "provider": {
8925
+ "type": "string"
8926
+ },
8927
+ "model": {
8928
+ "type": "string"
8929
+ },
8930
+ "baseUrl": {
8931
+ "type": "string"
8932
+ },
8933
+ "choice": {
8934
+ "type": "object",
8935
+ "properties": {
8936
+ "provider": {
8937
+ "type": "string",
8938
+ "minLength": 1
8939
+ },
8940
+ "model": {
8941
+ "type": "string",
8942
+ "minLength": 1
8943
+ }
8944
+ },
8945
+ "required": [
8946
+ "provider",
8947
+ "model"
8948
+ ],
8949
+ "additionalProperties": false
8950
+ }
8951
+ },
8952
+ "required": [
8953
+ "configured",
8954
+ "source",
8955
+ "provider",
8956
+ "model"
8957
+ ],
8958
+ "additionalProperties": false
8959
+ }
8960
+ ]
8961
+ },
8962
+ "PutSessionModelRequest": {
8963
+ "type": "object",
8964
+ "properties": {
8965
+ "provider": {
8966
+ "type": "string",
8967
+ "minLength": 1
8968
+ },
8969
+ "model": {
8970
+ "type": "string",
8971
+ "minLength": 1
8972
+ }
8973
+ },
8974
+ "required": [
8975
+ "provider",
8976
+ "model"
8977
+ ],
8978
+ "additionalProperties": false
8979
+ },
8870
8980
  "SessionItemsResponse": {
8871
8981
  "type": "object",
8872
8982
  "properties": {
@@ -32816,6 +32926,410 @@
32816
32926
  }
32817
32927
  }
32818
32928
  },
32929
+ "/v1/sessions/{sessionId}/model": {
32930
+ "get": {
32931
+ "tags": [
32932
+ "Sessions API"
32933
+ ],
32934
+ "summary": "Get a session's model",
32935
+ "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.",
32936
+ "security": [
32937
+ {
32938
+ "applicationKey": []
32939
+ },
32940
+ {
32941
+ "issuerToken": []
32942
+ }
32943
+ ],
32944
+ "x-nylorun-credentials": [
32945
+ "application",
32946
+ "subject",
32947
+ "token"
32948
+ ],
32949
+ "x-nylorun-scopes": [
32950
+ "sessions:own"
32951
+ ],
32952
+ "parameters": [
32953
+ {
32954
+ "schema": {
32955
+ "type": "string"
32956
+ },
32957
+ "required": true,
32958
+ "name": "sessionId",
32959
+ "in": "path"
32960
+ },
32961
+ {
32962
+ "schema": {
32963
+ "type": "string",
32964
+ "description": "The protocol version, `18`"
32965
+ },
32966
+ "required": true,
32967
+ "description": "The protocol version, `18`",
32968
+ "name": "Nylorun-Protocol",
32969
+ "in": "header"
32970
+ },
32971
+ {
32972
+ "schema": {
32973
+ "type": "string",
32974
+ "description": "The person an application key acts for"
32975
+ },
32976
+ "required": false,
32977
+ "description": "The person an application key acts for",
32978
+ "name": "Nylorun-Subject",
32979
+ "in": "header"
32980
+ },
32981
+ {
32982
+ "schema": {
32983
+ "type": "string",
32984
+ "description": "The subject's space-separated scopes; required with a subject"
32985
+ },
32986
+ "required": false,
32987
+ "description": "The subject's space-separated scopes; required with a subject",
32988
+ "name": "Nylorun-Scopes",
32989
+ "in": "header"
32990
+ }
32991
+ ],
32992
+ "responses": {
32993
+ "200": {
32994
+ "description": "The session's model",
32995
+ "content": {
32996
+ "application/json": {
32997
+ "schema": {
32998
+ "$ref": "#/components/schemas/SessionModelView"
32999
+ }
33000
+ }
33001
+ }
33002
+ },
33003
+ "400": {
33004
+ "description": "Invalid headers, path, body or cursor",
33005
+ "content": {
33006
+ "application/json": {
33007
+ "schema": {
33008
+ "$ref": "#/components/schemas/Rejected"
33009
+ }
33010
+ }
33011
+ }
33012
+ },
33013
+ "401": {
33014
+ "description": "No credential (`credential_required`), one the Tenant does not accept (`credential_invalid`), a token that expired (`token_expired`), or a token whose issuer's keys cannot be fetched now (`issuer_unavailable`). `WWW-Authenticate: Bearer` names the protected resource metadata (`resource_metadata`)",
33015
+ "content": {
33016
+ "application/json": {
33017
+ "schema": {
33018
+ "$ref": "#/components/schemas/Rejected"
33019
+ }
33020
+ }
33021
+ }
33022
+ },
33023
+ "403": {
33024
+ "description": "Not allowed for this credential or subject scope (`scope_required`; a token also gets `WWW-Authenticate: Bearer error=\"insufficient_scope\"` and the scope), a key of the other API (`key_role_mismatch`), or an application key from a browser (`origin_rejected`)",
33025
+ "content": {
33026
+ "application/json": {
33027
+ "schema": {
33028
+ "$ref": "#/components/schemas/Rejected"
33029
+ }
33030
+ }
33031
+ }
33032
+ },
33033
+ "404": {
33034
+ "description": "Not found (`not_found`)",
33035
+ "content": {
33036
+ "application/json": {
33037
+ "schema": {
33038
+ "$ref": "#/components/schemas/Rejected"
33039
+ }
33040
+ }
33041
+ }
33042
+ },
33043
+ "426": {
33044
+ "description": "`Nylorun-Protocol` missing or unsupported",
33045
+ "content": {
33046
+ "application/json": {
33047
+ "schema": {
33048
+ "$ref": "#/components/schemas/ProtocolRejected"
33049
+ }
33050
+ }
33051
+ }
33052
+ },
33053
+ "503": {
33054
+ "description": "The Tenant's storage or streams are unavailable",
33055
+ "content": {
33056
+ "application/json": {
33057
+ "schema": {
33058
+ "$ref": "#/components/schemas/Rejected"
33059
+ }
33060
+ }
33061
+ }
33062
+ }
33063
+ }
33064
+ },
33065
+ "put": {
33066
+ "tags": [
33067
+ "Sessions API"
33068
+ ],
33069
+ "summary": "Choose a session's model",
33070
+ "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`.",
33071
+ "security": [
33072
+ {
33073
+ "applicationKey": []
33074
+ }
33075
+ ],
33076
+ "x-nylorun-credentials": [
33077
+ "application",
33078
+ "subject"
33079
+ ],
33080
+ "x-nylorun-scopes": [
33081
+ "sessions:own"
33082
+ ],
33083
+ "parameters": [
33084
+ {
33085
+ "schema": {
33086
+ "type": "string"
33087
+ },
33088
+ "required": true,
33089
+ "name": "sessionId",
33090
+ "in": "path"
33091
+ },
33092
+ {
33093
+ "schema": {
33094
+ "type": "string",
33095
+ "description": "The protocol version, `18`"
33096
+ },
33097
+ "required": true,
33098
+ "description": "The protocol version, `18`",
33099
+ "name": "Nylorun-Protocol",
33100
+ "in": "header"
33101
+ },
33102
+ {
33103
+ "schema": {
33104
+ "type": "string",
33105
+ "description": "The person an application key acts for"
33106
+ },
33107
+ "required": false,
33108
+ "description": "The person an application key acts for",
33109
+ "name": "Nylorun-Subject",
33110
+ "in": "header"
33111
+ },
33112
+ {
33113
+ "schema": {
33114
+ "type": "string",
33115
+ "description": "The subject's space-separated scopes; required with a subject"
33116
+ },
33117
+ "required": false,
33118
+ "description": "The subject's space-separated scopes; required with a subject",
33119
+ "name": "Nylorun-Scopes",
33120
+ "in": "header"
33121
+ }
33122
+ ],
33123
+ "requestBody": {
33124
+ "required": true,
33125
+ "content": {
33126
+ "application/json": {
33127
+ "schema": {
33128
+ "$ref": "#/components/schemas/PutSessionModelRequest"
33129
+ }
33130
+ }
33131
+ }
33132
+ },
33133
+ "responses": {
33134
+ "200": {
33135
+ "description": "The session's model",
33136
+ "content": {
33137
+ "application/json": {
33138
+ "schema": {
33139
+ "$ref": "#/components/schemas/SessionModelView"
33140
+ }
33141
+ }
33142
+ }
33143
+ },
33144
+ "400": {
33145
+ "description": "Invalid headers, path, body or cursor",
33146
+ "content": {
33147
+ "application/json": {
33148
+ "schema": {
33149
+ "$ref": "#/components/schemas/Rejected"
33150
+ }
33151
+ }
33152
+ }
33153
+ },
33154
+ "401": {
33155
+ "description": "No credential (`credential_required`), or one the Tenant does not accept (`credential_invalid`), with a `WWW-Authenticate: Bearer` challenge",
33156
+ "content": {
33157
+ "application/json": {
33158
+ "schema": {
33159
+ "$ref": "#/components/schemas/Rejected"
33160
+ }
33161
+ }
33162
+ }
33163
+ },
33164
+ "403": {
33165
+ "description": "A trusted issuer's token (`scope_required`)"
33166
+ },
33167
+ "404": {
33168
+ "description": "Not found (`not_found`)",
33169
+ "content": {
33170
+ "application/json": {
33171
+ "schema": {
33172
+ "$ref": "#/components/schemas/Rejected"
33173
+ }
33174
+ }
33175
+ }
33176
+ },
33177
+ "409": {
33178
+ "description": "A turn is running or waits (`session_busy`)"
33179
+ },
33180
+ "422": {
33181
+ "description": "The provider is not connected, or the model is not one of its models"
33182
+ },
33183
+ "426": {
33184
+ "description": "`Nylorun-Protocol` missing or unsupported",
33185
+ "content": {
33186
+ "application/json": {
33187
+ "schema": {
33188
+ "$ref": "#/components/schemas/ProtocolRejected"
33189
+ }
33190
+ }
33191
+ }
33192
+ },
33193
+ "503": {
33194
+ "description": "The Tenant's storage or streams are unavailable",
33195
+ "content": {
33196
+ "application/json": {
33197
+ "schema": {
33198
+ "$ref": "#/components/schemas/Rejected"
33199
+ }
33200
+ }
33201
+ }
33202
+ }
33203
+ }
33204
+ },
33205
+ "delete": {
33206
+ "tags": [
33207
+ "Sessions API"
33208
+ ],
33209
+ "summary": "Clear a session's model",
33210
+ "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`).",
33211
+ "security": [
33212
+ {
33213
+ "applicationKey": []
33214
+ }
33215
+ ],
33216
+ "x-nylorun-credentials": [
33217
+ "application",
33218
+ "subject"
33219
+ ],
33220
+ "x-nylorun-scopes": [
33221
+ "sessions:own"
33222
+ ],
33223
+ "parameters": [
33224
+ {
33225
+ "schema": {
33226
+ "type": "string"
33227
+ },
33228
+ "required": true,
33229
+ "name": "sessionId",
33230
+ "in": "path"
33231
+ },
33232
+ {
33233
+ "schema": {
33234
+ "type": "string",
33235
+ "description": "The protocol version, `18`"
33236
+ },
33237
+ "required": true,
33238
+ "description": "The protocol version, `18`",
33239
+ "name": "Nylorun-Protocol",
33240
+ "in": "header"
33241
+ },
33242
+ {
33243
+ "schema": {
33244
+ "type": "string",
33245
+ "description": "The person an application key acts for"
33246
+ },
33247
+ "required": false,
33248
+ "description": "The person an application key acts for",
33249
+ "name": "Nylorun-Subject",
33250
+ "in": "header"
33251
+ },
33252
+ {
33253
+ "schema": {
33254
+ "type": "string",
33255
+ "description": "The subject's space-separated scopes; required with a subject"
33256
+ },
33257
+ "required": false,
33258
+ "description": "The subject's space-separated scopes; required with a subject",
33259
+ "name": "Nylorun-Scopes",
33260
+ "in": "header"
33261
+ }
33262
+ ],
33263
+ "responses": {
33264
+ "200": {
33265
+ "description": "The session's model",
33266
+ "content": {
33267
+ "application/json": {
33268
+ "schema": {
33269
+ "$ref": "#/components/schemas/SessionModelView"
33270
+ }
33271
+ }
33272
+ }
33273
+ },
33274
+ "400": {
33275
+ "description": "Invalid headers, path, body or cursor",
33276
+ "content": {
33277
+ "application/json": {
33278
+ "schema": {
33279
+ "$ref": "#/components/schemas/Rejected"
33280
+ }
33281
+ }
33282
+ }
33283
+ },
33284
+ "401": {
33285
+ "description": "No credential (`credential_required`), or one the Tenant does not accept (`credential_invalid`), with a `WWW-Authenticate: Bearer` challenge",
33286
+ "content": {
33287
+ "application/json": {
33288
+ "schema": {
33289
+ "$ref": "#/components/schemas/Rejected"
33290
+ }
33291
+ }
33292
+ }
33293
+ },
33294
+ "403": {
33295
+ "description": "A trusted issuer's token (`scope_required`)"
33296
+ },
33297
+ "404": {
33298
+ "description": "Not found (`not_found`)",
33299
+ "content": {
33300
+ "application/json": {
33301
+ "schema": {
33302
+ "$ref": "#/components/schemas/Rejected"
33303
+ }
33304
+ }
33305
+ }
33306
+ },
33307
+ "409": {
33308
+ "description": "A turn is running or waits (`session_busy`)"
33309
+ },
33310
+ "426": {
33311
+ "description": "`Nylorun-Protocol` missing or unsupported",
33312
+ "content": {
33313
+ "application/json": {
33314
+ "schema": {
33315
+ "$ref": "#/components/schemas/ProtocolRejected"
33316
+ }
33317
+ }
33318
+ }
33319
+ },
33320
+ "503": {
33321
+ "description": "The Tenant's storage or streams are unavailable",
33322
+ "content": {
33323
+ "application/json": {
33324
+ "schema": {
33325
+ "$ref": "#/components/schemas/Rejected"
33326
+ }
33327
+ }
33328
+ }
33329
+ }
33330
+ }
33331
+ }
33332
+ },
32819
33333
  "/v1/sessions/{sessionId}/usage": {
32820
33334
  "get": {
32821
33335
  "tags": [
@@ -15,7 +15,7 @@
15
15
  * under ownership (§10.6); `abortLocal` aborts an advance running on this process.
16
16
  */
17
17
  import type { SessionHistory } from "./history.js";
18
- import type { IssuerScope, SubjectScope, TenantEnvelope } from "@nylorun/core/contracts";
18
+ import type { IssuerScope, SessionModelChoice, SubjectScope, TenantEnvelope } from "@nylorun/core/contracts";
19
19
  import type { DurableCheckpoint, FlowCheckpoint } from "@nylorun/harness/run";
20
20
  import type { AgentManifest, JsonValue, SandboxManifest } from "@nylorun/core/define";
21
21
  import type { OutboxRequest, SessionStore, Tx } from "../store/types.js";
@@ -28,6 +28,7 @@ import type { McpDiagnostic, McpSnapshot } from "../mcp/snapshot.js";
28
28
  import type { HarnessApiServer } from "../harness-api/server.js";
29
29
  import type { WorkspacePort } from "../harness-api/workspace.js";
30
30
  import type { SandboxesClient } from "../sandbox/pods/client.js";
31
+ import type { CredentialProvider } from "../gates/credential-provider.js";
31
32
  import type { SandboxSignal } from "../execution/types.js";
32
33
  import type { TenantConfig } from "./types.js";
33
34
  import type { SessionStreams } from "./session-streams.js";
@@ -141,6 +142,11 @@ export interface Session {
141
142
  * Tenant-wide ones beside the session's own. Absent until a message names a file.
142
143
  */
143
144
  fileArtifacts?: string[];
145
+ /**
146
+ * The session's own model (Host feature `session-model`): its turns' model calls use it in
147
+ * place of the Tenant's while its provider is connected. Absent: the Tenant's model.
148
+ */
149
+ model?: SessionModelChoice;
144
150
  }
145
151
  export type AuthScope = {
146
152
  kind: "application";
@@ -285,6 +291,11 @@ export interface TenantContext {
285
291
  * Absent without a cluster (`nylorun local sandbox enable`): kind `pod` is `agent_unavailable`.
286
292
  */
287
293
  readonly pods?: TenantPods;
294
+ /**
295
+ * The outside credential provider (`gates/credential-provider.ts`): the variables a pod Agent's
296
+ * segments set to the stand-in for its owner. Absent: none.
297
+ */
298
+ readonly credentialProvider?: CredentialProvider;
288
299
  /**
289
300
  * Seam: reconcile a pod sandbox, or arm one of its timers (`DurableExecution.sandbox`),
290
301
  * outside any tx. What a transaction causes goes through `t.signalSandbox` instead, which
@@ -15,6 +15,7 @@ import { type HarnessPeer } from "../harness-api/server.js";
15
15
  import { type HostAuthority } from "../sandbox/join.js";
16
16
  import type { TenantPods } from "./context.js";
17
17
  import type { TraceOptions } from "../traces/options.js";
18
+ import type { CredentialProvider } from "../gates/credential-provider.js";
18
19
  /** TENANTS-CCR: test/injection hooks until TenantConfig gains them. */
19
20
  export type TenantOpenHooks = {
20
21
  modelProvider?: ModelProvider;
@@ -84,6 +85,11 @@ export type TenantOpenHooks = {
84
85
  * pods copy the engine from. Without it, kind `pod` is `agent_unavailable`.
85
86
  */
86
87
  pods?: TenantPods;
88
+ /**
89
+ * The outside credential provider (`NYLORUN_CREDENTIAL_PROVIDER_*`): a pod Agent's segments set
90
+ * the variables it names for the Agent's owner to the stand-in, under the vault's own.
91
+ */
92
+ credentialProvider?: CredentialProvider;
87
93
  /** How the trace route makes spans (R7.5): the Host's `OTEL_*` resource and content opt-ins. */
88
94
  traces?: TraceOptions;
89
95
  } & OpenedTenant;
@@ -261,6 +261,7 @@ export class TenantRuntime {
261
261
  },
262
262
  abortLocal: (sessionId, turnId) => abortLocal(ctx, sessionId, "cancel", turnId),
263
263
  ...(hooks.pods ? { pods: hooks.pods } : {}),
264
+ ...(hooks.credentialProvider ? { credentialProvider: hooks.credentialProvider } : {}),
264
265
  sandboxSignal: async (sandboxId, signal) => {
265
266
  if (ctx.closing || ctx.closed)
266
267
  return false;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * A session's own model (Host feature `session-model`): one of a connected provider's models,
3
+ * which the session's turns use in place of the Tenant's. It is kept on the session
4
+ * (`Session.model`): the Model Gate reads it at each call (`HostModelVault.readHostModel`) and a
5
+ * turn's start describes it (`turn.start.model`). The Tenant's model, and every other session's,
6
+ * stay as they are. While its provider is disconnected the session uses the Tenant's model again.
7
+ */
8
+ import type { SessionModelChoice, SessionModelView } from "@nylorun/core/contracts";
9
+ import { type SessionAccess, type TenantContext } from "./context.js";
10
+ /** The model the session's next turn uses, and where it comes from. */
11
+ export declare function readSessionModel(ctx: TenantContext, id: string, access: SessionAccess | undefined): Promise<SessionModelView>;
12
+ /**
13
+ * Chooses the session's own model, or clears it (`choice` undefined) so its turns use the
14
+ * Tenant's. A turn calls one model: the choice changes only while no turn runs or waits.
15
+ */
16
+ export declare function chooseSessionModel(ctx: TenantContext, id: string, choice: SessionModelChoice | undefined, access: SessionAccess | undefined): Promise<SessionModelView>;
@@ -0,0 +1,46 @@
1
+ import { catalogModel, hostModelCatalog } from "../model/catalog.js";
2
+ import { hostCredentialRowFor, modelBinding } from "../vault/service.js";
3
+ import { takesMessages } from "./commands.js";
4
+ import { lockedSession, sessionOf } from "./context.js";
5
+ import { fail } from "./http.js";
6
+ /** The model the session's next turn uses, and where it comes from. */
7
+ export async function readSessionModel(ctx, id, access) {
8
+ const session = await ctx.store.tx((t) => sessionOf(t, id, access));
9
+ return ctx.vault.getSessionModel(session.model);
10
+ }
11
+ /**
12
+ * Chooses the session's own model, or clears it (`choice` undefined) so its turns use the
13
+ * Tenant's. A turn calls one model: the choice changes only while no turn runs or waits.
14
+ */
15
+ export async function chooseSessionModel(ctx, id, choice, access) {
16
+ const session = await ctx.store.tx(async (t) => {
17
+ const s = await lockedSession(t, id, access);
18
+ if (!takesMessages(s))
19
+ fail(409, "The session's turn is running or waits: choose its model when the turn ends", {
20
+ code: "session_busy",
21
+ });
22
+ if (choice)
23
+ await checkChoice(t, choice);
24
+ const { model: _model, ...rest } = s;
25
+ const next = choice ? { ...rest, model: { provider: choice.provider, model: choice.model } } : rest;
26
+ await t.put("sessions", id, next);
27
+ return next;
28
+ });
29
+ return ctx.vault.getSessionModel(session.model);
30
+ }
31
+ /** A choice names a connected provider and, when its catalog lists models, one of them. */
32
+ async function checkChoice(t, choice) {
33
+ if (!/^[a-zA-Z0-9._-]+$/.test(choice.provider))
34
+ fail(400, "Invalid provider id", { code: "invalid_request" });
35
+ const row = await hostCredentialRowFor(t, choice.provider);
36
+ if (!row)
37
+ fail(422, `Model provider ${choice.provider} is not connected: connect it in Model Settings first`, {
38
+ code: "request_rejected",
39
+ });
40
+ // A custom endpoint serves the models it names; the provider's own model is always one.
41
+ if (choice.provider === "custom" || modelBinding(row).model === choice.model)
42
+ return;
43
+ const listed = hostModelCatalog().providers.find((provider) => provider.id === choice.provider);
44
+ if (listed && listed.models.length > 0 && !catalogModel(choice.provider, choice.model))
45
+ fail(422, `Unknown model ${choice.model} for provider ${choice.provider}`, { code: "request_rejected" });
46
+ }