@noodleseed/one 0.136.0 → 0.138.0

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 (124) hide show
  1. package/dist/commands/assistant-ops.d.ts.map +1 -1
  2. package/dist/commands/assistant-ops.js +30 -1
  3. package/dist/commands/assistant-ops.js.map +1 -1
  4. package/dist/commands/deploy-first-flow.d.ts +2 -0
  5. package/dist/commands/deploy-first-flow.d.ts.map +1 -1
  6. package/dist/commands/deploy-first-flow.js +46 -14
  7. package/dist/commands/deploy-first-flow.js.map +1 -1
  8. package/dist/commands/deploy-ops.d.ts.map +1 -1
  9. package/dist/commands/deploy-ops.js +27 -1
  10. package/dist/commands/deploy-ops.js.map +1 -1
  11. package/dist/commands/deploy-preflight.d.ts +1 -0
  12. package/dist/commands/deploy-preflight.d.ts.map +1 -1
  13. package/dist/commands/deploy-preflight.js +1 -0
  14. package/dist/commands/deploy-preflight.js.map +1 -1
  15. package/dist/commands/deploy-status-ops.d.ts +1 -0
  16. package/dist/commands/deploy-status-ops.d.ts.map +1 -1
  17. package/dist/commands/deploy-status-ops.js +59 -4
  18. package/dist/commands/deploy-status-ops.js.map +1 -1
  19. package/dist/commands/deployments-ops.d.ts.map +1 -1
  20. package/dist/commands/deployments-ops.js +2 -0
  21. package/dist/commands/deployments-ops.js.map +1 -1
  22. package/dist/commands/diagnostics-ops.d.ts +1 -0
  23. package/dist/commands/diagnostics-ops.d.ts.map +1 -1
  24. package/dist/commands/diagnostics-ops.js +3 -0
  25. package/dist/commands/diagnostics-ops.js.map +1 -1
  26. package/dist/deploy.d.ts +3 -0
  27. package/dist/deploy.d.ts.map +1 -1
  28. package/dist/deploy.js +4 -0
  29. package/dist/deploy.js.map +1 -1
  30. package/dist/index.d.ts +1 -1
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +1 -1
  33. package/dist/index.js.map +1 -1
  34. package/node_modules/@noodle-borg/admission-limits/dist/envelope.js +1 -1
  35. package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +4 -4
  36. package/node_modules/@noodle-borg/agent-kit/dist/generated/surface.js +1 -0
  37. package/node_modules/@noodle-borg/agent-kit/dist/skill-embedded-assistant-ref.js +22 -13
  38. package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
  39. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-store.d.ts +6 -2
  40. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-store.js +5 -2
  41. package/node_modules/@noodle-borg/assistant-gateway/dist/model-request.js +37 -0
  42. package/node_modules/@noodle-borg/assistant-gateway/dist/model-runtime.js +4 -0
  43. package/node_modules/@noodle-borg/{service/dist/assistant-model-stream.js → assistant-gateway/dist/model-stream.js} +34 -2
  44. package/node_modules/@noodle-borg/assistant-gateway/dist/public-session.d.ts +3 -0
  45. package/node_modules/@noodle-borg/assistant-gateway/dist/public-session.js +2 -1
  46. package/node_modules/@noodle-borg/assistant-gateway/dist/public-turn.d.ts +4 -1
  47. package/node_modules/@noodle-borg/assistant-gateway/dist/public-turn.js +2 -1
  48. package/node_modules/@noodle-borg/assistant-gateway/dist/surface-budget.d.ts +6 -1
  49. package/node_modules/@noodle-borg/assistant-gateway/dist/surface-budget.js +11 -5
  50. package/node_modules/@noodle-borg/assistant-gateway/package.json +5 -1
  51. package/node_modules/@noodle-borg/authoring/dist/assistant.d.ts +10 -2
  52. package/node_modules/@noodle-borg/authoring/dist/assistant.js +4 -0
  53. package/node_modules/@noodle-borg/authoring/dist/index.d.ts +1 -1
  54. package/node_modules/@noodle-borg/authoring/dist/index.js +1 -1
  55. package/node_modules/@noodle-borg/authoring/dist/load-authored.js +1 -1
  56. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-assistant.js +17 -0
  57. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-hosted-deployment.js +14 -0
  58. package/node_modules/@noodle-borg/compiler/dist/artifact/types.d.ts +1 -7
  59. package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +20 -8
  60. package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +12 -8
  61. package/node_modules/@noodle-borg/control-plane/dist/portable.d.ts +2 -0
  62. package/node_modules/@noodle-borg/control-plane/dist/portable.js +1 -0
  63. package/node_modules/@noodle-borg/control-plane/dist/rollback-deployment.d.ts +61 -0
  64. package/node_modules/@noodle-borg/{service/dist/operations → control-plane/dist}/rollback-deployment.js +3 -2
  65. package/node_modules/@noodle-borg/control-plane/dist/rollback-result.d.ts +19 -0
  66. package/node_modules/@noodle-borg/control-plane/package.json +3 -1
  67. package/node_modules/@noodle-borg/developer-mcp/dist/contracts.js +4 -0
  68. package/node_modules/@noodle-borg/developer-mcp/dist/widgets/app-overview.js +1 -1
  69. package/node_modules/@noodle-borg/developer-mcp/dist/widgets/deployment-detail.js +1 -1
  70. package/node_modules/@noodle-borg/module/dist/contract.d.ts +1 -0
  71. package/node_modules/@noodle-borg/module/dist/deployment-automation.d.ts +1 -1
  72. package/node_modules/@noodle-borg/module/dist/endpoint-url.d.ts +11 -0
  73. package/node_modules/@noodle-borg/module/dist/endpoint-url.js +20 -0
  74. package/node_modules/@noodle-borg/module/dist/index.d.ts +1 -0
  75. package/node_modules/@noodle-borg/module/dist/index.js +1 -0
  76. package/node_modules/@noodle-borg/observability/dist/assistant-usage.js +217 -0
  77. package/node_modules/@noodle-borg/observability/dist/index.js +2 -0
  78. package/node_modules/@noodle-borg/{service → observability}/dist/request-event-metrics.js +5 -2
  79. package/node_modules/@noodle-borg/observability/dist/request-events-postgres.js +6 -0
  80. package/node_modules/@noodle-borg/observability/dist/request-events.js +2 -0
  81. package/node_modules/@noodle-borg/service/dist/assistant-bindings.js +3 -1
  82. package/node_modules/@noodle-borg/service/dist/developer-mcp/control-plane-adapter.js +3 -3
  83. package/node_modules/@noodle-borg/service/dist/developer-mcp/inspection.js +2 -1
  84. package/node_modules/@noodle-borg/service/dist/registry-access.js +73 -24
  85. package/node_modules/@noodle-borg/service/dist/registry-deploy-transaction.js +5 -2
  86. package/node_modules/@noodle-borg/service/dist/registry-helpers.d.ts +5 -9
  87. package/node_modules/@noodle-borg/service/dist/registry-helpers.js +4 -18
  88. package/node_modules/@noodle-borg/service/dist/registry-rollback.js +3 -1
  89. package/node_modules/@noodle-borg/service/dist/registry-status.js +4 -1
  90. package/node_modules/@noodle-borg/service/dist/registry-targets.js +4 -4
  91. package/node_modules/@noodle-borg/service/dist/registry-types.d.ts +11 -16
  92. package/node_modules/@noodle-borg/service/dist/registry.d.ts +8 -5
  93. package/node_modules/@noodle-borg/service/dist/registry.js +8 -5
  94. package/node_modules/@noodle-borg/service/dist/routes/access.js +19 -1
  95. package/node_modules/@noodle-borg/service/dist/routes/analytics-dispatch.js +17 -1
  96. package/node_modules/@noodle-borg/service/dist/routes/analytics.js +1 -1
  97. package/node_modules/@noodle-borg/service/dist/routes/assistant-agent.js +73 -59
  98. package/node_modules/@noodle-borg/service/dist/routes/assistant-doctor.js +1 -1
  99. package/node_modules/@noodle-borg/service/dist/routes/assistant-model-binding.js +28 -0
  100. package/node_modules/@noodle-borg/service/dist/routes/assistant-public-session.js +18 -0
  101. package/node_modules/@noodle-borg/service/dist/routes/assistant-public-turn.js +6 -0
  102. package/node_modules/@noodle-borg/service/dist/routes/assistant-usage.js +42 -0
  103. package/node_modules/@noodle-borg/service/dist/routes/assistant.js +20 -3
  104. package/node_modules/@noodle-borg/service/dist/routes/auth-doctor-live.js +1 -2
  105. package/node_modules/@noodle-borg/service/dist/routes/control-plane.js +49 -4
  106. package/node_modules/@noodle-borg/service/dist/routes/deploy-preflight.js +11 -1
  107. package/node_modules/@noodle-borg/service/dist/routes/endpoint-enrichment.js +2 -1
  108. package/node_modules/@noodle-borg/service/dist/routes/paths.js +4 -0
  109. package/node_modules/@noodle-borg/service/dist/routes/rollback.js +1 -1
  110. package/node_modules/@noodle-borg/service/dist/service.js +6 -0
  111. package/node_modules/@noodle-borg/service/dist/store/in-memory.d.ts +2 -5
  112. package/node_modules/@noodle-borg/service/dist/store/in-memory.js +9 -21
  113. package/node_modules/@noodle-borg/service/dist/store/json-file.d.ts +2 -5
  114. package/node_modules/@noodle-borg/service/dist/store/json-file.js +8 -20
  115. package/node_modules/@noodle-borg/service/dist/store/records.js +39 -0
  116. package/node_modules/@noodle-borg/service/dist/store.d.ts +21 -10
  117. package/node_modules/@noodle-borg/service/package.json +5 -1
  118. package/node_modules/@noodle-borg/wire-contracts/dist/control-plane.d.ts +120 -0
  119. package/node_modules/@noodle-borg/wire-contracts/dist/control-plane.js +123 -1
  120. package/node_modules/@noodle-borg/wire-contracts/dist/index.d.ts +4 -0
  121. package/node_modules/@noodle-borg/wire-contracts/dist/index.js +15 -1
  122. package/node_modules/@noodleseed/assistant/README.md +43 -8
  123. package/node_modules/@noodleseed/assistant/package.json +16 -2
  124. package/package.json +2 -2
@@ -88,6 +88,7 @@ export const SDK_EXPORTS = [
88
88
  'knowledge',
89
89
  'managedSecret',
90
90
  'meilisearch',
91
+ 'noodleManaged',
91
92
  'openAICompatible',
92
93
  'prompt',
93
94
  'publicWebsite',
@@ -32,7 +32,8 @@ export function renderEmbeddedAssistantReference() {
32
32
  '| Owner | Values | Destination |',
33
33
  '| --- | --- | --- |',
34
34
  '| Noodle operator | Login, selected org/app/env | Plugin-managed CLI profile and explicit target; never the SaaS runtime |',
35
- '| Noodle deployment | `ASSISTANT_MODEL_BASE_URL`, `ASSISTANT_MODEL`, `ASSISTANT_MODEL_API_KEY` | `noodle variables set` / `noodle secrets set`; never the SaaS environment |',
35
+ '| Noodle-managed model | No authored provider, model id, URL, or key | Hosted operator state; available only when Noodle has enrolled the exact org/app/env |',
36
+ '| Operator-provided model | `ASSISTANT_MODEL_BASE_URL`, `ASSISTANT_MODEL`, `ASSISTANT_MODEL_API_KEY` | `noodle variables set` / `noodle secrets set`; never the SaaS environment |',
36
37
  '| Connector/delegated exchange | Connector credentials and any customer-owned token-exchange client | Noodle managed configuration plus the matching customer backend secret manager |',
37
38
  '| SaaS backend | `NOODLE_SERVICE_URL`, `NOODLE_ASSISTANT_CLIENT_ID`, `NOODLE_ASSISTANT_CLIENT_SECRET`, `PUBLIC_APP_ORIGIN` | Backend-only environment or secret manager; never browser code or public-prefixed variables |',
38
39
  '| Browser | Short-lived assistant session only | In memory; never a client secret, model key, connector credential, or raw application session |',
@@ -51,11 +52,7 @@ export function renderEmbeddedAssistantReference() {
51
52
  'branding: { name: "Acme", accent: "#3157D5" },',
52
53
  'context: { defaults: { locale: "en-GB", timeZone: "Europe/London" } },',
53
54
  'assistant: embeddedAssistant({',
54
- ' model: openAICompatible({',
55
- ' baseUrl: variable("ASSISTANT_MODEL_BASE_URL"),',
56
- ' model: variable("ASSISTANT_MODEL"),',
57
- ' apiKey: secret("ASSISTANT_MODEL_API_KEY"),',
58
- ' }),',
55
+ ' model: noodleManaged(),',
59
56
  ' access: authenticatedWebsite({',
60
57
  ' origins: ["http://localhost:3000", "https://app.example.com"],',
61
58
  ' }),',
@@ -63,6 +60,8 @@ export function renderEmbeddedAssistantReference() {
63
60
  '}),',
64
61
  '```',
65
62
  '',
63
+ '`noodleManaged()` is the zero-configuration Cloud path: the public artifact contains only `{ kind: "noodle-managed" }`. It never exposes a provider or model identifier, and it fails closed unless Noodle has enrolled that exact deployment target. For a customer- or self-hosted model, replace it with `openAICompatible({ baseUrl: variable("ASSISTANT_MODEL_BASE_URL"), model: variable("ASSISTANT_MODEL"), apiKey: secret("ASSISTANT_MODEL_API_KEY") })`; that remains the portable BYO path.',
64
+ '',
66
65
  'Origins are exact: scheme, host, and optional port, with no path, trailing slash, or wildcard. Production origins must be HTTPS; plain HTTP is accepted only for loopback development origins (`http://localhost:<port>`, `http://127.0.0.1:<port>`). `noodle dev` serves the MCP project, not the embedding SaaS. For a public surface it also prints a process-local Embed ID and script; mount that script on the separately running loopback website to test anonymous mint, chat, widgets, and confirmation. The local ID is ephemeral, while a hosted deploy provisions the stable ID behind durable admission counters.',
67
66
  '',
68
67
  '## Product workflow guidance',
@@ -169,7 +168,7 @@ export function renderEmbeddedAssistantReference() {
169
168
  'noodle deploy --org <org> --app <app> --env <env>',
170
169
  '```',
171
170
  '',
172
- 'Deploy preflights the complete target before upload. In an interactive terminal it collects all missing model variables and secrets, then continues. In a non-interactive run it reports every missing name and safe `noodle variables set ... --from-env` / `noodle secrets set ... --from-env` action; perform every action and repeat the same deploy command. Values never appear in the preflight report or resume state. Do not put these model values in the embedding SaaS environment. A production deployment may omit a local origin; include a loopback origin only when local browser integration is required.',
171
+ 'Deploy preflights the complete target before upload. `noodleManaged()` has no customer model variables or secrets; enrollment is hosted operator state and an unenrolled target fails closed at serving time. `openAICompatible()` preflight collects or reports every missing model variable and secret with safe `noodle variables set ... --from-env` / `noodle secrets set ... --from-env` actions. Values never appear in the preflight report or resume state. Do not put BYO model values in the embedding SaaS environment. A production deployment may omit a local origin; include a loopback origin only when local browser integration is required.',
173
172
  '',
174
173
  '## Access modes and customer auth',
175
174
  '',
@@ -516,14 +515,19 @@ export function renderEmbeddedAssistantReference() {
516
515
  '',
517
516
  '`settle` must await or catch the command promise; the same structured failure also appears in the hook `error` state.',
518
517
  '',
519
- 'The sample fails closed on input requests until you replace that branch with a form generated from `requestedSchema`. A custom renderer must show the complete confirmation review and both decisions, handle every part it supports, and surface an explicit unsupported state for the rest. For `data-view`, use `NoodleAppView` to render the linked App or deliberately map `resourceUri`/tool plus the bounded redacted `result` to an application-trusted native component. JSON result data is not the linked App UI. Never inject `part.data.html`, assign it to `srcdoc`, fetch a `ui://` URI, or reproduce the bridge with a direct Ext Apps dependency. Do not wrap this client in another chat transport or invent user messages for interaction continuations.',
518
+ 'The sample fails closed on input requests until you replace that branch with a form generated from `requestedSchema`. A custom renderer must show the complete confirmation review and both decisions, handle every part it supports, and surface an explicit unsupported state for the rest. For `data-view`, use the canonical `<noodle-app-view>` host (or its React `NoodleAppView` adapter) to render the linked App, or deliberately map `resourceUri`/tool plus the bounded redacted `result` to an application-trusted native component. JSON result data is not the linked App UI. Never inject `part.data.html`, assign it to `srcdoc`, fetch a `ui://` URI, or reproduce the bridge with a direct Ext Apps dependency. Do not wrap this client in another chat transport or invent user messages for interaction continuations.',
519
+ '',
520
+ '`<noodle-app-view>` owns one bridge for the semantic view identity: client + `view.id` + `view.resourceUri`; `NoodleAppView` delegates to it. The host retains the iframe across fresh payload/callback/theme rerenders, publishes later resolved-theme changes through MCP Apps host context, and sends standard App teardown when that semantic identity changes, the element disconnects, or the App requests teardown. App views remain inline by default; the host advertises only inline presentation and rejects widget fullscreen requests. Opt in with `allowFullscreen` on `NoodleAppView` or `allow-fullscreen` on `<noodle-app-view>` only when fullscreen is an intentional part of the customer-owned experience. When fullscreen is accepted, the shared host adds an accessible top-right exit control that returns the same mounted App to inline mode without losing its state. Pass the same resolved application theme used by the conversation shell. Do not key an ancestor by a view object or callback. If the embedding page sets Content-Security-Policy, include the Noodle service origin in both `connect-src` and `frame-src`.',
520
521
  '',
521
- '`NoodleAppView` owns one bridge for the semantic view identity: client + `view.id` + `view.resourceUri`. It retains the iframe across fresh payload/callback/theme rerenders, reads current payloads through refs, publishes later resolved-theme changes through MCP Apps host context, and sends standard App teardown only when that semantic identity changes or the component unmounts. Pass the same resolved application theme used by the conversation shell. Do not key an ancestor by a view object or callback. If the embedding page sets Content-Security-Policy, include the Noodle service origin in both `connect-src` and `frame-src`.',
522
+ 'Outside React, use the same DOM-free client directly and import the isolated App-view entry only when rendering linked Apps. The client keeps the session token in memory, the transcript stays React-free, and the element owns only App presentation:',
522
523
  '',
523
- 'Outside React, use the same DOM-free client directly. It keeps the session token in memory, exposes a React-free `UIMessage` transcript with typed parts, and never registers a custom element:',
524
+ '```html',
525
+ '<noodle-app-view id="assistant-app-view"></noodle-app-view>',
526
+ '```',
524
527
  '',
525
528
  '```ts',
526
529
  'import { createAssistantClient } from "@noodleseed/assistant/client";',
530
+ 'import "@noodleseed/assistant/app-view";',
527
531
  '',
528
532
  'const assistant = createAssistantClient({',
529
533
  ' sessionEndpoint: "/api/assistant/session",',
@@ -533,6 +537,11 @@ export function renderEmbeddedAssistantReference() {
533
537
  ' }),',
534
538
  '});',
535
539
  '',
540
+ 'const appView = document.querySelector("#assistant-app-view");',
541
+ 'if (!appView) throw new Error("Missing App view host");',
542
+ 'appView.client = assistant;',
543
+ "appView.theme = document.documentElement.classList.contains('dark') ? 'dark' : 'light';",
544
+ '',
536
545
  'assistant.updateModelContext({',
537
546
  " content: [{ type: 'text', text: 'The time-off form is mounted.' }],",
538
547
  " structuredContent: { widget: { name: 'time-off', lifecycle: 'mounted' } },",
@@ -551,7 +560,7 @@ export function renderEmbeddedAssistantReference() {
551
560
  ' pending = { id: part.data.id, requestedSchema: part.data.requestedSchema };',
552
561
  ' }',
553
562
  " if (part.type === 'data-view') {",
554
- ' renderRegisteredView(part.data.resourceUri, part.data.result);',
563
+ ' appView.view = part.data;',
555
564
  ' }',
556
565
  ' }',
557
566
  ' }',
@@ -590,7 +599,7 @@ export function renderEmbeddedAssistantReference() {
590
599
  '});',
591
600
  '```',
592
601
  '',
593
- '`data-view` means a completed tool has a linked MCP App view. In a customer-owned React renderer, pass that typed part and the existing client to `NoodleAppView`; it retains one bridge for client + `view.id` + `view.resourceUri` and requests standard App teardown on semantic replacement or unmount. That pair is transport identity: different call ids are distinct invocations and must not be deduplicated generically. If the product intentionally owns one current panel for a known resource, declare an application-owned slot map and replace only that slot. Deliberately map the bounded result to an application-trusted native component only when replacing the linked App UI.',
602
+ '`data-view` means a completed tool has a linked MCP App view. Pass that typed part and the existing client to `<noodle-app-view>` in Vue, Angular, or plain DOM, or to its `NoodleAppView` React adapter. It retains one bridge for client + `view.id` + `view.resourceUri` and requests standard App teardown on semantic replacement, disconnect, or App request. That pair is transport identity: different call ids are distinct invocations and must not be deduplicated generically. If the product intentionally owns one current panel for a known resource, declare an application-owned slot map and replace only that slot. Deliberately map the bounded result to an application-trusted native component only when replacing the linked App UI.',
594
603
  '',
595
604
  '`clientContext` and typed `pageContext` are recomputed for each turn. `updateContext(...)` remains the legacy session-exchange context; `updatePageContext(...)` replaces the fresh per-turn application hint. `updateModelContext({ content, structuredContent })` publishes one cohesive renderer snapshot for later message turns without starting a turn; every call replaces the prior snapshot rather than merging fields. These are untrusted data, not conversation history or authorization input, and the boundaries reject credential-shaped or unbounded updates. A message may re-exchange once after a pre-execution `401`; the client never auto-retries interaction decisions. `tool_proposed.arguments` is a complete schema-aware review projection and, for connector-backed tools, names the sole exact connector version/operation/resolved arguments. Sensitive/write-only fields are redacted; truncating or omitting any non-sensitive action field fails closed. Accept is bound to the server-held action and claims at most one execution attempt—clients cannot replace it. Normal terminal outcomes scrub private arguments and continuations immediately; only an accepted action still executing retains them for the one-hour unknown-outcome recovery window, after which it records `interaction_outcome_unknown` and scrubs. Without downstream idempotency this is not an exactly-once business-effect guarantee. To reconcile a lost response, explicitly repeat the same id and decision: the service returns its durable stored outcome without re-execution.',
596
605
  '',
@@ -615,7 +624,7 @@ export function renderEmbeddedAssistantReference() {
615
624
  '',
616
625
  '- Node.js 20+ for `@noodleseed/assistant/server`.',
617
626
  '- The package ships ESM and CommonJS with full export conditions; no bundler aliases, `transpilePackages`, or ambient type shims are needed. If resolution fails, the installed package version is outdated: update `@noodleseed/assistant` instead of adding workarounds.',
618
- '- TypeScript `moduleResolution` `bundler` or `node16` recommended; classic `node` also resolves the `/client`, `/react`, `/react/client`, and `/server` subpaths.',
627
+ '- TypeScript `moduleResolution` `bundler` or `node16` recommended; classic `node` also resolves the `/app-view`, `/client`, `/react`, `/react/client`, and `/server` subpaths.',
619
628
  '',
620
629
  '## Verify the boundary',
621
630
  '',
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noodle-borg/agent-kit",
3
- "version": "0.81.0",
3
+ "version": "0.82.0",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "engines": {
@@ -31,6 +31,8 @@ export interface AssistantSessionRecord {
31
31
  readonly clientId: string;
32
32
  readonly tenant: TenantRef;
33
33
  readonly deploymentId: string;
34
+ /** Model funding boundary pinned with the deployment that minted this session. */
35
+ readonly modelSource?: 'operator' | 'noodle-managed';
34
36
  readonly origin: string;
35
37
  readonly caller: CallerIdentity;
36
38
  /** Canonical customer endpoint routes; private session authority, never caller or response data. */
@@ -59,8 +61,8 @@ export interface AssistantSessionRecord {
59
61
  readonly absoluteExpiresAt: string;
60
62
  readonly history: AssistantHistoryMessage[];
61
63
  /**
62
- * Model turns already spent. Durable and separate from `history`, which truncates to the last twelve
63
- * messages: history is a prompt-window trim, never a bound.
64
+ * Model turns already spent. Durable and separate from `history`, which keeps a bounded recent
65
+ * prompt window and is never the admission bound.
64
66
  */
65
67
  readonly turnCount: number;
66
68
  }
@@ -78,6 +80,8 @@ export interface AssistantHistoryMessage {
78
80
  readonly role: 'user' | 'assistant';
79
81
  readonly content: string;
80
82
  }
83
+ /** Latest prompt messages retained for model continuity; turn admission is tracked separately. */
84
+ export declare const ASSISTANT_HISTORY_MAX_MESSAGES = 40;
81
85
  export type AssistantSessionElevation = {
82
86
  readonly ok: true;
83
87
  readonly session: AssistantSessionRecord;
@@ -22,6 +22,8 @@ export const ASSISTANT_BROWSER_UI_FIELDS = [
22
22
  'locale',
23
23
  'direction',
24
24
  ];
25
+ /** Latest prompt messages retained for model continuity; turn admission is tracked separately. */
26
+ export const ASSISTANT_HISTORY_MAX_MESSAGES = 40;
25
27
  export class InMemoryAssistantStore {
26
28
  #clients = new Map();
27
29
  #sessions = new Map();
@@ -163,8 +165,9 @@ export class InMemoryAssistantStore {
163
165
  if (!session)
164
166
  return;
165
167
  session.history.push(...messages);
166
- if (session.history.length > 12)
167
- session.history.splice(0, session.history.length - 12);
168
+ if (session.history.length > ASSISTANT_HISTORY_MAX_MESSAGES) {
169
+ session.history.splice(0, session.history.length - ASSISTANT_HISTORY_MAX_MESSAGES);
170
+ }
168
171
  }
169
172
  async createInteraction(input) {
170
173
  const proposalTime = new Date(input.createdAt ?? new Date());
@@ -0,0 +1,37 @@
1
+ import { readModelCompletion } from './model-stream.js';
2
+ export async function requestModelCompletion(input) {
3
+ const { binding } = input;
4
+ const url = new URL(`${binding.baseUrl.replace(/\/$/, '')}/chat/completions`);
5
+ if (url.protocol !== 'https:' || url.username || url.password) {
6
+ throw new Error('unsafe model endpoint');
7
+ }
8
+ const completionLimit = input.maxCompletionTokens ?? binding.requestPolicy?.maxCompletionTokens;
9
+ const body = JSON.stringify({
10
+ ...binding.requestPolicy?.extraBody,
11
+ model: binding.model,
12
+ stream: true,
13
+ messages: input.messages,
14
+ tools: input.tools,
15
+ ...(completionLimit === undefined ? {} : { max_completion_tokens: completionLimit }),
16
+ });
17
+ const requestBytes = new TextEncoder().encode(body).byteLength;
18
+ const maxRequestBytes = binding.requestPolicy?.maxRequestBytes;
19
+ if (maxRequestBytes !== undefined && requestBytes > maxRequestBytes) {
20
+ throw new Error('model request too large');
21
+ }
22
+ const bearerToken = binding.apiKey ?? (await binding.bearerToken());
23
+ if (!/^[^\s]{1,8192}$/.test(bearerToken))
24
+ throw new Error('invalid model bearer token');
25
+ const timeoutSignal = AbortSignal.timeout(binding.requestPolicy?.timeoutMs ?? 30_000);
26
+ const response = await input.fetcher(url.href, {
27
+ method: 'POST',
28
+ headers: { Authorization: `Bearer ${bearerToken}`, 'content-type': 'application/json' },
29
+ body,
30
+ redirect: 'manual',
31
+ signal: input.signal === undefined ? timeoutSignal : AbortSignal.any([input.signal, timeoutSignal]),
32
+ });
33
+ if (!response.ok)
34
+ throw new Error(`model request failed (${response.status})`);
35
+ return readModelCompletion(response, input.onContent ?? (() => undefined), input.maxResponseBytes);
36
+ }
37
+ //# sourceMappingURL=model-request.js.map
@@ -0,0 +1,4 @@
1
+ /** Provider-neutral embedded-assistant model transport. */
2
+ export * from './model-request.js';
3
+ export * from './model-stream.js';
4
+ //# sourceMappingURL=model-runtime.js.map
@@ -2,7 +2,11 @@ export async function readModelCompletion(response, onContent, maxBytes = 1 << 2
2
2
  const contentType = response.headers.get('content-type') ?? '';
3
3
  if (!contentType.includes('text/event-stream')) {
4
4
  const text = await readBoundedText(response, maxBytes);
5
- const completion = JSON.parse(text);
5
+ const parsed = JSON.parse(text);
6
+ const completion = {
7
+ choices: parsed.choices,
8
+ ...normalizeUsage(parsed.usage),
9
+ };
6
10
  const message = completion.choices[0]?.message;
7
11
  if (!message?.tool_calls?.length && message?.content)
8
12
  onContent(message.content);
@@ -15,6 +19,7 @@ export async function readModelCompletion(response, onContent, maxBytes = 1 << 2
15
19
  const toolCalls = new Map();
16
20
  let pending = '';
17
21
  let content = '';
22
+ let usage;
18
23
  let bytes = 0;
19
24
  while (true) {
20
25
  const { done, value } = await reader.read();
@@ -36,6 +41,7 @@ export async function readModelCompletion(response, onContent, maxBytes = 1 << 2
36
41
  const parsed = JSON.parse(data);
37
42
  if (parsed.error !== undefined)
38
43
  throw new Error('model stream returned an error');
44
+ usage = normalizeUsageValue(parsed.usage) ?? usage;
39
45
  const delta = parsed.choices?.[0]?.delta;
40
46
  if (delta?.content) {
41
47
  content += delta.content;
@@ -80,8 +86,34 @@ export async function readModelCompletion(response, onContent, maxBytes = 1 << 2
80
86
  },
81
87
  },
82
88
  ],
89
+ ...(usage === undefined ? {} : { usage }),
83
90
  };
84
91
  }
92
+ function normalizeUsage(usage) {
93
+ const normalized = normalizeUsageValue(usage);
94
+ return normalized === undefined ? {} : { usage: normalized };
95
+ }
96
+ function normalizeUsageValue(usage) {
97
+ if (usage === undefined)
98
+ return undefined;
99
+ const promptTokens = nonNegativeInteger(usage.prompt_tokens);
100
+ const completionTokens = nonNegativeInteger(usage.completion_tokens);
101
+ const reportedTotalTokens = nonNegativeInteger(usage.total_tokens);
102
+ if (promptTokens === undefined || completionTokens === undefined) {
103
+ return undefined;
104
+ }
105
+ const totalTokens = reportedTotalTokens ?? promptTokens + completionTokens;
106
+ const reasoningTokens = nonNegativeInteger(usage.completion_tokens_details?.reasoning_tokens);
107
+ return {
108
+ promptTokens,
109
+ completionTokens,
110
+ totalTokens,
111
+ ...(reasoningTokens === undefined ? {} : { reasoningTokens }),
112
+ };
113
+ }
114
+ function nonNegativeInteger(value) {
115
+ return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 ? value : undefined;
116
+ }
85
117
  async function readBoundedText(response, maxBytes) {
86
118
  const text = await response.text();
87
119
  if (new TextEncoder().encode(text).byteLength > maxBytes) {
@@ -89,4 +121,4 @@ async function readBoundedText(response, maxBytes) {
89
121
  }
90
122
  return text;
91
123
  }
92
- //# sourceMappingURL=assistant-model-stream.js.map
124
+ //# sourceMappingURL=model-stream.js.map
@@ -1,6 +1,7 @@
1
1
  import type { AdmissionEnvelope, DailyCounterStore } from '@noodle-borg/admission-limits/portable';
2
2
  import { type PublicEmbedRecord, type PublicEmbedStore } from './embed-store.js';
3
3
  import type { PublicSurface } from './public-surface.js';
4
+ import { type SurfaceBudgetBounds } from './surface-budget.js';
4
5
  /**
5
6
  * Minting an anonymous session for a public page.
6
7
  *
@@ -17,6 +18,8 @@ export interface PublicSessionPorts {
17
18
  readonly counters: DailyCounterStore;
18
19
  /** The live surface for this tenant, read from the *active* deployment at mint time. */
19
20
  resolveActiveSurface(embed: PublicEmbedRecord): Promise<PublicSurface | undefined>;
21
+ /** Optional hosted entitlement; absent preserves the ordinary customer-funded envelope. */
22
+ readonly resolveBudgetBounds?: (embed: PublicEmbedRecord) => Promise<SurfaceBudgetBounds | undefined>;
20
23
  /** Persist the anonymous session and return its bearer token and expiry. */
21
24
  createSession(input: {
22
25
  readonly embed: PublicEmbedRecord;
@@ -30,7 +30,8 @@ export async function mintPublicSession(request, envelope, ports) {
30
30
  }
31
31
  // The surface's own budget, which an operator may have lowered — or set to zero, which is the kill
32
32
  // switch. Resolved after the embed is known and before anything is spent.
33
- const surfaceLimits = surfaceEnvelope(envelope, embed);
33
+ const budgetBounds = await ports.resolveBudgetBounds?.(embed);
34
+ const surfaceLimits = surfaceEnvelope(envelope, embed, budgetBounds);
34
35
  const now = ports.now();
35
36
  // Fairness before solvency: an address at its hourly ceiling is refused without spending any of the
36
37
  // surface's day, so one visitor cannot burn the customer's budget on refusals.
@@ -1,6 +1,7 @@
1
1
  import type { AdmissionEnvelope, DailyCounterStore } from '@noodle-borg/admission-limits/portable';
2
2
  import type { AssistantTurnConsumption } from './assistant-store.js';
3
- import type { PublicEmbedStore } from './embed-store.js';
3
+ import type { PublicEmbedRecord, PublicEmbedStore } from './embed-store.js';
4
+ import { type SurfaceBudgetBounds } from './surface-budget.js';
4
5
  /**
5
6
  * Whether an anonymous visitor's next turn runs.
6
7
  *
@@ -16,6 +17,8 @@ export interface PublicTurnPorts {
16
17
  readonly counters: DailyCounterStore;
17
18
  /** Re-read per turn: it carries the operator's live budget, and whether the surface still exists. */
18
19
  readonly embeds: Pick<PublicEmbedStore, 'lookup'>;
20
+ /** Optional hosted entitlement; resolved live so cohort removal lowers the envelope immediately. */
21
+ readonly resolveBudgetBounds?: (embed: PublicEmbedRecord) => Promise<SurfaceBudgetBounds | undefined>;
19
22
  /** Spend one of this session's turns, atomically (admission tier 1). */
20
23
  consumeTurn(id: string, limit: number): Promise<AssistantTurnConsumption>;
21
24
  now(): Date;
@@ -22,7 +22,8 @@ export async function admitPublicTurn(request, defaults, ports) {
22
22
  message: 'assistant is unavailable right now',
23
23
  };
24
24
  }
25
- const envelope = surfaceEnvelope(defaults, embed);
25
+ const budgetBounds = await ports.resolveBudgetBounds?.(embed);
26
+ const envelope = surfaceEnvelope(defaults, embed, budgetBounds);
26
27
  // Fairness before either of the budgets below: an address at its hourly ceiling is turned away
27
28
  // without spending the session's allowance or the customer's day, so one visitor cannot burn either
28
29
  // on turns that were never served. Mirrors the ordering the mint uses.
@@ -1,5 +1,10 @@
1
1
  import { type AdmissionEnvelope } from '@noodle-borg/admission-limits/portable';
2
2
  import type { PublicEmbedRecord } from './embed-store.js';
3
+ /** Hosted/operator entitlement layered between structural maxima and per-surface overrides. */
4
+ export interface SurfaceBudgetBounds {
5
+ readonly defaults: Pick<AdmissionEnvelope, 'turnsPerSession' | 'turnsPerDay' | 'mintsPerDay'>;
6
+ readonly ceiling: Pick<AdmissionEnvelope, 'turnsPerDay' | 'mintsPerDay'>;
7
+ }
3
8
  /**
4
9
  * One surface's daily budget, resolved from the operator's override and the deployment's defaults.
5
10
  *
@@ -12,5 +17,5 @@ import type { PublicEmbedRecord } from './embed-store.js';
12
17
  * operator any more than by app code, so an over-eager `--turns-per-day 999999999` degrades to the
13
18
  * ceiling instead of becoming the ceiling. Zero survives clamping and is the kill switch.
14
19
  */
15
- export declare function surfaceEnvelope(base: AdmissionEnvelope, embed: Pick<PublicEmbedRecord, 'turnsPerDay' | 'mintsPerDay'>): AdmissionEnvelope;
20
+ export declare function surfaceEnvelope(base: AdmissionEnvelope, embed: Pick<PublicEmbedRecord, 'turnsPerDay' | 'mintsPerDay'>, bounds?: SurfaceBudgetBounds): AdmissionEnvelope;
16
21
  //# sourceMappingURL=surface-budget.d.ts.map
@@ -11,13 +11,19 @@ import { clamp } from '@noodle-borg/admission-limits/portable';
11
11
  * operator any more than by app code, so an over-eager `--turns-per-day 999999999` degrades to the
12
12
  * ceiling instead of becoming the ceiling. Zero survives clamping and is the kill switch.
13
13
  */
14
- export function surfaceEnvelope(base, embed) {
15
- if (embed.turnsPerDay === undefined && embed.mintsPerDay === undefined)
16
- return base;
17
- return clamp({
18
- ...base,
14
+ export function surfaceEnvelope(base, embed, bounds) {
15
+ const effectiveBase = bounds === undefined ? base : clamp({ ...base, ...bounds.defaults });
16
+ const requested = clamp({
17
+ ...effectiveBase,
19
18
  ...(embed.turnsPerDay !== undefined ? { turnsPerDay: embed.turnsPerDay } : {}),
20
19
  ...(embed.mintsPerDay !== undefined ? { mintsPerDay: embed.mintsPerDay } : {}),
21
20
  });
21
+ if (bounds === undefined)
22
+ return requested;
23
+ return {
24
+ ...requested,
25
+ turnsPerDay: Math.min(requested.turnsPerDay, bounds.ceiling.turnsPerDay),
26
+ mintsPerDay: Math.min(requested.mintsPerDay, bounds.ceiling.mintsPerDay),
27
+ };
22
28
  }
23
29
  //# sourceMappingURL=surface-budget.js.map
@@ -17,6 +17,10 @@
17
17
  "types": "./dist/portable.d.ts",
18
18
  "import": "./dist/portable.js"
19
19
  },
20
+ "./model-runtime": {
21
+ "types": "./dist/model-runtime.d.ts",
22
+ "import": "./dist/model-runtime.js"
23
+ },
20
24
  "./postgres": {
21
25
  "types": "./dist/postgres.d.ts",
22
26
  "import": "./dist/postgres.js"
@@ -36,7 +40,7 @@
36
40
  "@noodle-borg/module": "0.0.0",
37
41
  "@noodle-borg/protocol": "0.0.0",
38
42
  "@noodle-borg/runtime": "0.0.0",
39
- "@noodleseed/assistant": "1.22.0"
43
+ "@noodleseed/assistant": "1.23.0"
40
44
  },
41
45
  "devDependencies": {
42
46
  "@types/node": "^24.13.1",
@@ -59,6 +59,7 @@ export interface AssistantLabels {
59
59
  readonly copy?: string;
60
60
  readonly newMessages?: string;
61
61
  readonly reconnect?: string;
62
+ readonly newConversation?: string;
62
63
  readonly retry?: string;
63
64
  readonly unavailable?: string;
64
65
  readonly sessionExpired?: string;
@@ -108,6 +109,11 @@ export interface OpenAICompatibleModel {
108
109
  /** Managed secret name, never the secret value. */
109
110
  readonly apiKey: string;
110
111
  }
112
+ /** Provider-neutral request for the model selected and operated by Noodle Seed Cloud. */
113
+ export interface NoodleManagedModel {
114
+ readonly kind: 'noodle-managed';
115
+ }
116
+ export type AssistantModel = OpenAICompatibleModel | NoodleManagedModel;
111
117
  /**
112
118
  * A verified session claim the embedding backend may pass at session exchange. Keys become
113
119
  * `${user.claims.<key>}` in tool fulfilment; `exposeToModel: true` additionally places the value
@@ -184,7 +190,7 @@ export interface PublicWebsiteInput {
184
190
  export declare function authenticatedWebsite(input: AuthenticatedWebsiteInput): AuthenticatedWebsiteAccess;
185
191
  export declare function publicWebsite(input: PublicWebsiteInput): PublicWebsiteAccess;
186
192
  export interface EmbeddedAssistantOptions extends AssistantUiOptions {
187
- readonly model: OpenAICompatibleModel;
193
+ readonly model: AssistantModel;
188
194
  /** One surface, or every front door this assistant serves. */
189
195
  readonly access: AssistantAccess | readonly AssistantAccess[];
190
196
  }
@@ -197,7 +203,7 @@ export interface AssistantSurfaceConfig {
197
203
  readonly sessionClaims?: Readonly<Record<string, SessionClaimDeclaration>>;
198
204
  }
199
205
  export interface EmbeddedAssistantConfig extends AssistantUiOptions {
200
- readonly model: OpenAICompatibleModel;
206
+ readonly model: AssistantModel;
201
207
  readonly surfaces: readonly AssistantSurfaceConfig[];
202
208
  /**
203
209
  * Union of every surface's origins. Kept at the top level so the service's origin check and the
@@ -208,5 +214,7 @@ export interface EmbeddedAssistantConfig extends AssistantUiOptions {
208
214
  readonly sessionClaims?: Readonly<Record<string, SessionClaimDeclaration>>;
209
215
  }
210
216
  export declare function openAICompatible(input: OpenAICompatibleModelInput): OpenAICompatibleModel;
217
+ /** Use Noodle Seed Cloud's operator-selected model. Provider details never enter authored source. */
218
+ export declare function noodleManaged(): NoodleManagedModel;
211
219
  export declare function embeddedAssistant(input: EmbeddedAssistantOptions): EmbeddedAssistantConfig;
212
220
  //# sourceMappingURL=assistant.d.ts.map
@@ -33,6 +33,10 @@ export function openAICompatible(input) {
33
33
  apiKey: serializeSecretRef(input.apiKey, 'assistant.model.apiKey'),
34
34
  };
35
35
  }
36
+ /** Use Noodle Seed Cloud's operator-selected model. Provider details never enter authored source. */
37
+ export function noodleManaged() {
38
+ return { kind: 'noodle-managed' };
39
+ }
36
40
  export function embeddedAssistant(input) {
37
41
  const { model, access, suggestedPrompts, ...ui } = input;
38
42
  assertAccessDeclared(input, access);
@@ -2,7 +2,7 @@ import type { ConnectorFile, HttpConnectorDef } from '@noodle-borg/connector-def
2
2
  import { z } from 'zod';
3
3
  export type { PackagedAssetReference } from '@noodle-borg/compiler';
4
4
  export type { AgentCapabilityKind, AgentCapabilityRef, AgentExampleSource, AgentGuideSource, AgentWorkflowSource, AgentWorkflowStepSource, } from './agent-guide.js';
5
- export { type AssistantAccess, type AssistantCapability, type AssistantLabels, type AssistantLayoutMode, type AssistantPosition, type AssistantPresentationOptions, type AssistantPresentationTone, type AssistantSurfaceConfig, type AssistantSurfaceMode, type AssistantThemeMode, type AssistantUiOptions, type AuthenticatedWebsiteAccess, type AuthenticatedWebsiteInput, authenticatedWebsite, type CapabilityRef, type EmbeddedAssistantConfig, type EmbeddedAssistantOptions, embeddedAssistant, type OpenAICompatibleModel, type OpenAICompatibleModelInput, openAICompatible, type PublicWebsiteAccess, type PublicWebsiteInput, publicWebsite, } from './assistant.js';
5
+ export { type AssistantAccess, type AssistantCapability, type AssistantLabels, type AssistantLayoutMode, type AssistantModel, type AssistantPosition, type AssistantPresentationOptions, type AssistantPresentationTone, type AssistantSurfaceConfig, type AssistantSurfaceMode, type AssistantThemeMode, type AssistantUiOptions, type AuthenticatedWebsiteAccess, type AuthenticatedWebsiteInput, authenticatedWebsite, type CapabilityRef, type EmbeddedAssistantConfig, type EmbeddedAssistantOptions, embeddedAssistant, type NoodleManagedModel, noodleManaged, type OpenAICompatibleModel, type OpenAICompatibleModelInput, openAICompatible, type PublicWebsiteAccess, type PublicWebsiteInput, publicWebsite, } from './assistant.js';
6
6
  export { type ConfigRef, type ConfigRefKind, secret, variable, } from './config.js';
7
7
  export { type BoundConnectorRef, bind, type ConnectionBinding, type ConnectionRef, type ConnectionSource, clientCredentials, connection, externalExchange, type GoogleWorkloadIdentityAccess, googleWorkloadIdentity, managedSecret, } from './connections.js';
8
8
  export { type ComputeHost, type ComputeLimits, type ComputeOperationOptions, ConnectorBuilder, type ConnectorCatalogDoc, type ConnectorOpDefinition, type ConnectorOperationOptions, type ConnectorRef, connector, type HttpConnectorOptions, } from './connectors.js';
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- export { authenticatedWebsite, embeddedAssistant, openAICompatible, publicWebsite, } from './assistant.js';
2
+ export { authenticatedWebsite, embeddedAssistant, noodleManaged, openAICompatible, publicWebsite, } from './assistant.js';
3
3
  export { secret, variable, } from './config.js';
4
4
  export { bind, clientCredentials, connection, externalExchange, googleWorkloadIdentity, managedSecret, } from './connections.js';
5
5
  export { ConnectorBuilder, connector, } from './connectors.js';
@@ -33,7 +33,7 @@ export function prepareAuthoringRuntimeDir(dir, options) {
33
33
  './react': './react.mjs',
34
34
  },
35
35
  }));
36
- writeFileSync(join(self, 'index.mjs'), "export { annotations, asset, authenticatedWebsite, bind, clientCredentials, connector, connection, customerAuth, customerEndpoint, embeddedAssistant, externalExchange, file, gmailConnector, googleWorkloadIdentity, handoffSession, knowledge, managedSecret, openAICompatible, prompt, publicWebsite, resource, secret, server, site, tool, variable, when, z } from '@noodle-borg/authoring';\n");
36
+ writeFileSync(join(self, 'index.mjs'), "export { annotations, asset, authenticatedWebsite, bind, clientCredentials, connector, connection, customerAuth, customerEndpoint, embeddedAssistant, externalExchange, file, gmailConnector, googleWorkloadIdentity, handoffSession, knowledge, managedSecret, noodleManaged, openAICompatible, prompt, publicWebsite, resource, secret, server, site, tool, variable, when, z } from '@noodle-borg/authoring';\n");
37
37
  writeFileSync(join(self, 'platform.mjs'), "export { noodlePlatform, noodlePlatformCatalog } from '@noodle-borg/authoring';\n");
38
38
  writeFileSync(join(self, 'react.mjs'), "export { generateHelpers } from '@noodle-borg/authoring/react';\n");
39
39
  }
@@ -203,6 +203,23 @@ export const ASSISTANT_COMMAND = {
203
203
  CLIENTS_SUBCOMMAND,
204
204
  EMBEDS_SUBCOMMAND,
205
205
  BUDGET_SUBCOMMAND,
206
+ subcommand({
207
+ ...SUBCOMMAND_FIELDS,
208
+ name: 'usage',
209
+ summary: 'Show assistant sessions, conversation depth, engagement, latency, and model tokens.',
210
+ flags: [
211
+ flag({
212
+ ...FLAG_FIELDS,
213
+ name: 'window',
214
+ type: 'string',
215
+ value: '<window>',
216
+ summary: 'Aggregation window.',
217
+ constraints: { choices: ['24h', '7d', '30d'], default: '7d' },
218
+ }),
219
+ ...TENANT_FLAGS,
220
+ ],
221
+ jsonOutput: { mode: 'single' },
222
+ }),
206
223
  subcommand({
207
224
  ...SUBCOMMAND_FIELDS,
208
225
  name: 'embed',
@@ -132,6 +132,13 @@ export const CATALOG_HOSTED_DEPLOYMENT = [
132
132
  summary: 'Identity access mode for the deployment.',
133
133
  constraints: { choices: ACCESS_MODES },
134
134
  }),
135
+ flag({
136
+ ...FLAG_FIELDS,
137
+ name: 'owner-subject',
138
+ type: 'string',
139
+ value: '<subject>',
140
+ summary: 'Exact OAuth subject bound when access is owner-only.',
141
+ }),
135
142
  flag({
136
143
  ...FLAG_FIELDS,
137
144
  name: 'connectors',
@@ -318,6 +325,13 @@ export const CATALOG_HOSTED_DEPLOYMENT = [
318
325
  value: '<version>',
319
326
  summary: 'Server version whose access mode is changed.',
320
327
  }),
328
+ flag({
329
+ ...FLAG_FIELDS,
330
+ name: 'owner-subject',
331
+ type: 'string',
332
+ value: '<subject>',
333
+ summary: 'Exact OAuth subject to bind or transfer for owner-only access.',
334
+ }),
321
335
  ...TENANT_TARGET_FLAGS,
322
336
  ],
323
337
  jsonOutput: { mode: 'single' },
@@ -227,13 +227,7 @@ export interface ArtifactServer {
227
227
  /** Compiled knowledge components (ADR 0202, Core v2): descriptors + generated tool metadata, never bytes. */
228
228
  readonly knowledge?: readonly CompiledKnowledgeComponent[];
229
229
  readonly assistant?: {
230
- readonly model: {
231
- readonly kind: 'openai-compatible';
232
- readonly baseUrl: string;
233
- readonly model: string;
234
- /** Managed secret name, never the secret value. */
235
- readonly apiKey: string;
236
- };
230
+ readonly model: NonNullable<Manifest['server']['assistant']>['model'];
237
231
  readonly allowedOrigins: readonly string[];
238
232
  } & Omit<NonNullable<Manifest['server']['assistant']>, 'model' | 'allowedOrigins'>;
239
233
  readonly branding?: {