@noodleseed/one 0.137.0 → 0.139.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.
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/assistant-ops.d.ts.map +1 -1
- package/dist/commands/assistant-ops.js +30 -1
- package/dist/commands/assistant-ops.js.map +1 -1
- package/dist/commands/deploy-first-flow.d.ts +2 -0
- package/dist/commands/deploy-first-flow.d.ts.map +1 -1
- package/dist/commands/deploy-first-flow.js +46 -14
- package/dist/commands/deploy-first-flow.js.map +1 -1
- package/dist/commands/deploy-ops.d.ts.map +1 -1
- package/dist/commands/deploy-ops.js +27 -1
- package/dist/commands/deploy-ops.js.map +1 -1
- package/dist/commands/deploy-preflight.d.ts +1 -0
- package/dist/commands/deploy-preflight.d.ts.map +1 -1
- package/dist/commands/deploy-preflight.js +1 -0
- package/dist/commands/deploy-preflight.js.map +1 -1
- package/dist/commands/deploy-status-ops.d.ts +1 -0
- package/dist/commands/deploy-status-ops.d.ts.map +1 -1
- package/dist/commands/deploy-status-ops.js +59 -4
- package/dist/commands/deploy-status-ops.js.map +1 -1
- package/dist/commands/deployments-ops.d.ts.map +1 -1
- package/dist/commands/deployments-ops.js +2 -0
- package/dist/commands/deployments-ops.js.map +1 -1
- package/dist/commands/diagnostics-ops.d.ts +1 -0
- package/dist/commands/diagnostics-ops.d.ts.map +1 -1
- package/dist/commands/diagnostics-ops.js +3 -0
- package/dist/commands/diagnostics-ops.js.map +1 -1
- package/dist/commands/import.d.ts +4 -0
- package/dist/commands/import.d.ts.map +1 -0
- package/dist/commands/import.js +198 -0
- package/dist/commands/import.js.map +1 -0
- package/dist/commands/project-setup.d.ts +1 -1
- package/dist/commands/project-setup.d.ts.map +1 -1
- package/dist/commands/project-setup.js +2 -48
- package/dist/commands/project-setup.js.map +1 -1
- package/dist/commands/service-ops.d.ts.map +1 -1
- package/dist/commands/service-ops.js +44 -51
- package/dist/commands/service-ops.js.map +1 -1
- package/dist/deploy.d.ts +3 -0
- package/dist/deploy.d.ts.map +1 -1
- package/dist/deploy.js +4 -0
- package/dist/deploy.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-import.d.ts +22 -0
- package/dist/mcp-import.d.ts.map +1 -0
- package/dist/mcp-import.js +91 -0
- package/dist/mcp-import.js.map +1 -0
- package/dist/self-host-init.d.ts +23 -0
- package/dist/self-host-init.d.ts.map +1 -0
- package/dist/self-host-init.js +315 -0
- package/dist/self-host-init.js.map +1 -0
- package/node_modules/@noodle-borg/admission-limits/dist/envelope.js +1 -1
- package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +6 -6
- package/node_modules/@noodle-borg/agent-kit/dist/generated/surface.js +1 -0
- package/node_modules/@noodle-borg/agent-kit/dist/skill-authoring-refs.js +11 -2
- package/node_modules/@noodle-borg/agent-kit/dist/skill-connect-refs.js +31 -1
- package/node_modules/@noodle-borg/agent-kit/dist/skill-content.js +3 -2
- package/node_modules/@noodle-borg/agent-kit/dist/skill-embedded-assistant-ref.js +6 -7
- package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-guide.d.ts +1 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-guide.js +35 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-store.d.ts +6 -2
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-store.js +5 -2
- package/node_modules/@noodle-borg/assistant-gateway/dist/model-request.js +37 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/model-runtime.js +4 -0
- package/node_modules/@noodle-borg/{service/dist/assistant-model-stream.js → assistant-gateway/dist/model-stream.js} +34 -2
- package/node_modules/@noodle-borg/assistant-gateway/dist/public-session.d.ts +3 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/public-session.js +2 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/public-turn.d.ts +4 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/public-turn.js +2 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/surface-budget.d.ts +6 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/surface-budget.js +11 -5
- package/node_modules/@noodle-borg/assistant-gateway/package.json +5 -1
- package/node_modules/@noodle-borg/authoring/dist/assistant.d.ts +10 -2
- package/node_modules/@noodle-borg/authoring/dist/assistant.js +4 -0
- package/node_modules/@noodle-borg/authoring/dist/connections.js +1 -0
- package/node_modules/@noodle-borg/authoring/dist/connectors.d.ts +25 -3
- package/node_modules/@noodle-borg/authoring/dist/connectors.js +82 -7
- package/node_modules/@noodle-borg/authoring/dist/index.d.ts +2 -2
- package/node_modules/@noodle-borg/authoring/dist/index.js +1 -1
- package/node_modules/@noodle-borg/authoring/dist/load-authored.js +1 -1
- package/node_modules/@noodle-borg/authoring/dist/model-tool-visibility.js +33 -0
- package/node_modules/@noodle-borg/authoring/dist/server.d.ts +5 -0
- package/node_modules/@noodle-borg/authoring/dist/server.js +7 -2
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-account.js +13 -1
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-assistant.js +17 -0
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-bootstrap-authoring.js +30 -1
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-hosted-deployment.js +14 -0
- package/node_modules/@noodle-borg/compiler/dist/artifact/types.d.ts +1 -7
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +20 -8
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +12 -8
- package/node_modules/@noodle-borg/connector-defs/dist/call-graph.js +1 -1
- package/node_modules/@noodle-borg/connector-defs/dist/compile-mcp.js +95 -0
- package/node_modules/@noodle-borg/connector-defs/dist/compile.js +4 -0
- package/node_modules/@noodle-borg/connector-defs/dist/customer-routing.js +1 -1
- package/node_modules/@noodle-borg/connector-defs/dist/index.d.ts +1 -1
- package/node_modules/@noodle-borg/connector-defs/dist/schema.d.ts +642 -3
- package/node_modules/@noodle-borg/connector-defs/dist/schema.js +143 -1
- package/node_modules/@noodle-borg/connector-http/dist/index.d.ts +1 -0
- package/node_modules/@noodle-borg/connector-http/dist/index.js +1 -0
- package/node_modules/@noodle-borg/connector-http/dist/mcp/config.js +76 -0
- package/node_modules/@noodle-borg/connector-http/dist/mcp/egress.js +125 -0
- package/node_modules/@noodle-borg/connector-http/dist/mcp/failure.js +48 -0
- package/node_modules/@noodle-borg/connector-http/dist/mcp/index.d.ts +3 -0
- package/node_modules/@noodle-borg/connector-http/dist/mcp/index.js +2 -0
- package/node_modules/@noodle-borg/connector-http/dist/mcp/mcp-connector.d.ts +12 -0
- package/node_modules/@noodle-borg/connector-http/dist/mcp/mcp-connector.js +125 -0
- package/node_modules/@noodle-borg/connector-http/dist/mcp/result.js +55 -0
- package/node_modules/@noodle-borg/connector-http/dist/mcp/types.d.ts +52 -0
- package/node_modules/@noodle-borg/connector-http/dist/mcp/types.js +10 -0
- package/node_modules/@noodle-borg/connector-http/package.json +4 -1
- package/node_modules/@noodle-borg/control-plane/dist/portable.d.ts +2 -0
- package/node_modules/@noodle-borg/control-plane/dist/portable.js +1 -0
- package/node_modules/@noodle-borg/control-plane/dist/rollback-deployment.d.ts +61 -0
- package/node_modules/@noodle-borg/{service/dist/operations → control-plane/dist}/rollback-deployment.js +3 -2
- package/node_modules/@noodle-borg/control-plane/dist/rollback-result.d.ts +19 -0
- package/node_modules/@noodle-borg/control-plane/package.json +3 -1
- package/node_modules/@noodle-borg/developer-mcp/dist/contracts.js +4 -0
- package/node_modules/@noodle-borg/developer-mcp/dist/widgets/app-overview.js +1 -1
- package/node_modules/@noodle-borg/developer-mcp/dist/widgets/deployment-detail.js +1 -1
- package/node_modules/@noodle-borg/module/dist/contract.d.ts +1 -0
- package/node_modules/@noodle-borg/module/dist/deployment-automation.d.ts +1 -1
- package/node_modules/@noodle-borg/module/dist/endpoint-url.d.ts +11 -0
- package/node_modules/@noodle-borg/module/dist/endpoint-url.js +20 -0
- package/node_modules/@noodle-borg/module/dist/index.d.ts +1 -0
- package/node_modules/@noodle-borg/module/dist/index.js +1 -0
- package/node_modules/@noodle-borg/observability/dist/assistant-usage.js +217 -0
- package/node_modules/@noodle-borg/observability/dist/index.js +2 -0
- package/node_modules/@noodle-borg/{service → observability}/dist/request-event-metrics.js +5 -2
- package/node_modules/@noodle-borg/observability/dist/request-events-postgres.js +6 -0
- package/node_modules/@noodle-borg/observability/dist/request-events.js +2 -0
- package/node_modules/@noodle-borg/openapi-import/dist/index.js +1 -0
- package/node_modules/@noodle-borg/openapi-import/dist/mcp/index.js +4 -0
- package/node_modules/@noodle-borg/openapi-import/dist/mcp/probe.js +165 -0
- package/node_modules/@noodle-borg/openapi-import/dist/mcp/render.js +99 -0
- package/node_modules/@noodle-borg/openapi-import/dist/mcp/snapshot.js +290 -0
- package/node_modules/@noodle-borg/openapi-import/package.json +6 -2
- package/node_modules/@noodle-borg/runtime/dist/connector/types.d.ts +2 -0
- package/node_modules/@noodle-borg/runtime/dist/operation-execution.js +1 -0
- package/node_modules/@noodle-borg/service/dist/assistant-bindings.js +3 -1
- package/node_modules/@noodle-borg/service/dist/developer-mcp/control-plane-adapter.js +3 -3
- package/node_modules/@noodle-borg/service/dist/developer-mcp/inspection.js +2 -1
- package/node_modules/@noodle-borg/service/dist/registry-access.js +73 -24
- package/node_modules/@noodle-borg/service/dist/registry-deploy-transaction.js +5 -2
- package/node_modules/@noodle-borg/service/dist/registry-helpers.d.ts +5 -9
- package/node_modules/@noodle-borg/service/dist/registry-helpers.js +4 -18
- package/node_modules/@noodle-borg/service/dist/registry-rollback.js +3 -1
- package/node_modules/@noodle-borg/service/dist/registry-status.js +4 -1
- package/node_modules/@noodle-borg/service/dist/registry-targets.js +4 -4
- package/node_modules/@noodle-borg/service/dist/registry-types.d.ts +11 -16
- package/node_modules/@noodle-borg/service/dist/registry.d.ts +8 -5
- package/node_modules/@noodle-borg/service/dist/registry.js +8 -5
- package/node_modules/@noodle-borg/service/dist/routes/access.js +19 -1
- package/node_modules/@noodle-borg/service/dist/routes/analytics-dispatch.js +17 -1
- package/node_modules/@noodle-borg/service/dist/routes/analytics.js +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/assistant-agent.js +81 -64
- package/node_modules/@noodle-borg/service/dist/routes/assistant-doctor.js +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/assistant-model-binding.js +28 -0
- package/node_modules/@noodle-borg/service/dist/routes/assistant-public-session.js +18 -0
- package/node_modules/@noodle-borg/service/dist/routes/assistant-public-turn.js +6 -0
- package/node_modules/@noodle-borg/service/dist/routes/assistant-usage.js +42 -0
- package/node_modules/@noodle-borg/service/dist/routes/assistant.js +20 -3
- package/node_modules/@noodle-borg/service/dist/routes/auth-doctor-live.js +1 -2
- package/node_modules/@noodle-borg/service/dist/routes/control-plane.js +49 -4
- package/node_modules/@noodle-borg/service/dist/routes/deploy-preflight.js +11 -1
- package/node_modules/@noodle-borg/service/dist/routes/endpoint-enrichment.js +2 -1
- package/node_modules/@noodle-borg/service/dist/routes/paths.js +4 -0
- package/node_modules/@noodle-borg/service/dist/routes/rollback.js +1 -1
- package/node_modules/@noodle-borg/service/dist/service.js +6 -0
- package/node_modules/@noodle-borg/service/dist/store/in-memory.d.ts +2 -5
- package/node_modules/@noodle-borg/service/dist/store/in-memory.js +9 -21
- package/node_modules/@noodle-borg/service/dist/store/json-file.d.ts +2 -5
- package/node_modules/@noodle-borg/service/dist/store/json-file.js +8 -20
- package/node_modules/@noodle-borg/service/dist/store/records.js +39 -0
- package/node_modules/@noodle-borg/service/dist/store.d.ts +21 -10
- package/node_modules/@noodle-borg/service/package.json +5 -1
- package/node_modules/@noodle-borg/wire-contracts/dist/control-plane.d.ts +120 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/control-plane.js +123 -1
- package/node_modules/@noodle-borg/wire-contracts/dist/index.d.ts +4 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/index.js +15 -1
- package/node_modules/@noodleseed/assistant/package.json +1 -1
- package/package.json +2 -2
|
@@ -40,7 +40,8 @@ export function renderAuthoringWorkflowReference() {
|
|
|
40
40
|
'',
|
|
41
41
|
'1. **Website scrape** — if the user gives a URL, scrape it for surface hints (products, services, hours, contact, pricing). Stop there: the URL does not reveal CRM, booking systems, custom APIs, auth model, eligibility rules, quoting logic, or approval flows. Those live in the business systems and the owner’s head — ask.',
|
|
42
42
|
'2. **OpenAPI import** — `noodle import openapi <file>` emits a starter `server.ts` from a spec. Use it when the user provides an OpenAPI document.',
|
|
43
|
-
'3. **
|
|
43
|
+
'3. **Upstream MCP import** — `noodle import mcp <url> --name <slug> --output <dir>` discovers `tools/list` once, validates and freezes tool schemas into TypeScript, and writes a secret-free drift snapshot. Upstream annotations are untrusted, so generated tools remain destructive confirmed actions until an author verifies and narrows them. Use `--header-env <header>=<ENV_NAME>` for import-only auth and `--check` for classified, non-mutating drift detection. Runtime never performs discovery.',
|
|
44
|
+
'4. **User interview** — Noodle does not interview; you do. Cover custom APIs/integrations, eligibility rules, quoting/approval logic, and private schemas (SQL DDL or JSON samples for custom `connector` declarations). Ask for concrete examples and sample payloads; do not guess a schema from a URL or invent endpoints.',
|
|
44
45
|
'',
|
|
45
46
|
'## Fit check',
|
|
46
47
|
'',
|
|
@@ -59,10 +60,18 @@ export function renderAuthoringWorkflowReference() {
|
|
|
59
60
|
'Declare connectors as data, not imperative code:',
|
|
60
61
|
'',
|
|
61
62
|
'- **HTTP**: `connector("id").version("1.0.0").http({ baseUrl, allowedOrigins, auth, operations })` with per-operation `request`/`response` mapping using `${args...}` / `${response...}` expressions.',
|
|
63
|
+
'- **MCP**: `connector("id").version("1.0.0").mcp({ endpoint, allowedOrigins, auth?, operations })` where each operation freezes the separate upstream `tool` wire name plus input/output schema. Import with `noodle import mcp`; do not hand-copy a live surface or call `tools/list` at runtime.',
|
|
62
64
|
'- **Compute**: `connector("id").version("1.0.0").compute(name, { input, output, calls?, run })` — a self-contained, sandboxed function (no imports/closure capture) that may call allowlisted operations via `callOperation`.',
|
|
63
65
|
'',
|
|
64
66
|
'Tools record connector calls into a flow; recording is not execution. Do not branch on runtime outputs with native `if` — use declarative `when(...)` conditions.',
|
|
65
67
|
'',
|
|
68
|
+
'MCP connectors are curated backing operations, never transparent proxies. Publish ordinary Noodle tools',
|
|
69
|
+
'with stable intent-shaped names, descriptions, annotations, authored schemas, confirmation, visibility,',
|
|
70
|
+
'and optional React `view`. Normalize a text-only upstream result through compute before rendering when the',
|
|
71
|
+
'view needs a richer stable output. Upstream resources, prompts, `_meta`, annotations, widgets, and CSP are',
|
|
72
|
+
'not imported or forwarded. The runtime opens one guarded session for one operation, uses only a broker-minted',
|
|
73
|
+
'credential, and closes it; it does not act as an agent for upstream sampling, roots, or elicitation.',
|
|
74
|
+
'',
|
|
66
75
|
'HTTP connector auth variants: `bearer` (`{ kind: "bearer", secret: secret("API_TOKEN") }`), `apiKey` (`{ kind: "apiKey", header: "X-API-Key", secret: secret("API_KEY") }`), `clientCredentials`, `delegatedOAuth`, `delegatedSessionCookie`, and `delegatedTokenExchange` (per-user calls to your own API — see "Delegated downstream auth" below). Use managed `secret(...)` / `variable(...)` refs for all values that differ by org/app/env.',
|
|
67
76
|
'',
|
|
68
77
|
'When one connector needs independently selectable accounts, declare catalog `credentialProfiles` plus each operation’s accepted `credentials.profiles`, then bind each `server.use` alias with `bind(connector, { profile, connection: connection("logical_id", managedSecret(secret("NAME"), { scopes, audience })) })`. The alias is the stable account boundary; never put provider account ids, labels, or credential values in it. `gmailConnector()` is the curated Gmail catalog helper; reuse it under independent aliases and accept canonical `accounts` arrays in tools (one account for writes, or an explicitly ordered supported combination for reads). See the bundled `gmail-multi-account` flagship. Bound managed secrets are supported by hosted execution. For deployed-server access to Google APIs, use `googleWorkloadIdentity({ provider: variable("GOOGLE_WIF_PROVIDER"), access: { kind: "direct" } })`, or add `serviceAccountImpersonation` with a managed service-account email. This is keyless Google Workload Identity Federation: exact Google scopes/audience come from the catalog operation, while `noodle auth google prepare|status|doctor|revoke` owns operator lifecycle. See the bundled `google-bigquery` flagship. `externalExchange()` is runnable only when the deployment operator injects an exact HTTPS provider endpoint/origin/audience and durable shared subject-pin store through service ports; Noodle sends a short-lived platform-signed deployment workload assertion and accepts only a bounded bearer response. Provider implementations must consume assertion replay ids through durable shared atomic storage across instances and restarts. There is intentionally no hosted enrollment or provider CRUD surface yet. The provider wire contract is public, but its conformance kit is workspace/source-only and is not an installable npm package. Bound `clientCredentials(...)` remains fail-closed until its provider slice lands.',
|
|
@@ -271,7 +280,7 @@ export function renderWidgetsAndAppsReference() {
|
|
|
271
280
|
'',
|
|
272
281
|
'## Tools and views',
|
|
273
282
|
'',
|
|
274
|
-
'Use `tool(name, { description, input, output, fulfil, view })` for a model-visible tool that renders a widget, and the same `tool(name, { ..., visibility: ["app"] })` for an app-only helper hidden from the model. A `view` is `{ component: "name", entry: "./views/name.tsx" }` — a React component the compiler bundles at validate/deploy time.',
|
|
283
|
+
'Use `tool(name, { description, input, output, fulfil, view })` for a model-visible tool that renders a widget, and the same `tool(name, { ..., visibility: ["app"] })` for an app-only helper hidden from the model. When a view is valid only for narrow explicit intent, add `modelVisibility: { latestMessageIncludesAny: ["open the form", ...] }`; Noodle normalizes and matches those literal phrases against the latest user message before model discovery and fails closed on malformed data. This is relevance filtering, never authorization. A `view` is `{ component: "name", entry: "./views/name.tsx" }` — a React component the compiler bundles at validate/deploy time.',
|
|
275
284
|
'',
|
|
276
285
|
'## Noodle Design default',
|
|
277
286
|
'',
|
|
@@ -16,6 +16,7 @@ export function renderConnectAnApiReference() {
|
|
|
16
16
|
'- Secure the key first',
|
|
17
17
|
'- Probe the live API',
|
|
18
18
|
'- Model the connector from the observed shape',
|
|
19
|
+
'- Import and curate an upstream MCP server',
|
|
19
20
|
'- Return a list',
|
|
20
21
|
'- Create, update, delete',
|
|
21
22
|
'- Design intent tools',
|
|
@@ -27,7 +28,7 @@ export function renderConnectAnApiReference() {
|
|
|
27
28
|
'',
|
|
28
29
|
'## Use when',
|
|
29
30
|
'',
|
|
30
|
-
'- The user provides credentials, a reachable API,
|
|
31
|
+
'- The user provides credentials, a reachable API, an OpenAPI document, or an upstream MCP endpoint and wants real MCP behavior backed by it.',
|
|
31
32
|
'- Existing connector behavior compiles but still needs proof against the actual service and data shape.',
|
|
32
33
|
'',
|
|
33
34
|
'## Do not use when',
|
|
@@ -85,6 +86,35 @@ export function renderConnectAnApiReference() {
|
|
|
85
86
|
'The full connector shape, every `auth.kind`, and compute connectors are in',
|
|
86
87
|
'`references/authoring-workflow.md`.',
|
|
87
88
|
'',
|
|
89
|
+
'### Import and curate an upstream MCP server',
|
|
90
|
+
'',
|
|
91
|
+
'Use an explicit import to discover the upstream tool surface once and generate TypeScript:',
|
|
92
|
+
'',
|
|
93
|
+
'```sh',
|
|
94
|
+
'noodle import mcp https://store.example/api/mcp --name store --output store-app',
|
|
95
|
+
'# When import discovery needs a credential, read it only from the environment:',
|
|
96
|
+
'noodle import mcp https://store.example/api/mcp --name store \\',
|
|
97
|
+
' --header-env Authorization=STORE_MCP_TOKEN',
|
|
98
|
+
'```',
|
|
99
|
+
'',
|
|
100
|
+
'The importer freezes upstream tool names and normalized schemas into `.mcp({ operations })`; it never',
|
|
101
|
+
'persists the import credential. Runtime calls only those declared operations and never runs `tools/list`.',
|
|
102
|
+
'MCP annotations are untrusted hints, so every generated tool starts as a destructive confirmed action.',
|
|
103
|
+
'Verify real behavior and deliberately narrow proven reads before accepting the generated source. Use',
|
|
104
|
+
'`noodle import mcp <url> --output <dir> --check` in CI to detect upstream additions, removals, or schema',
|
|
105
|
+
'changes without mutating source; its review labels drift additive, breaking, or metadata-only.',
|
|
106
|
+
'',
|
|
107
|
+
'Treat the imported connector operation as a backing-system contract, not the public tool design. Publish',
|
|
108
|
+
'ordinary intent-shaped `tool(...)` capabilities with stable names and narrower schemas, normalize text or',
|
|
109
|
+
'large upstream output through compute, and attach a normal Noodle React `view` when UI helps. Upstream',
|
|
110
|
+
'`_meta`, resources, prompts, annotations, widgets, and CSP are never forwarded. A Noodle-owned widget may',
|
|
111
|
+
'therefore enrich a headless upstream tool without trusting upstream executable UI.',
|
|
112
|
+
'',
|
|
113
|
+
'Managed endpoints require an exact managed origin allowlist. Configure each deployment with',
|
|
114
|
+
'`variable(...)` and `secret(...)`; inbound MCP/OAuth bearer tokens are never reusable upstream credentials.',
|
|
115
|
+
'The first release supports no auth, bearer, API key, and client credentials through the broker. Do not',
|
|
116
|
+
'invent a delegated-user OAuth shortcut for an upstream MCP server.',
|
|
117
|
+
'',
|
|
88
118
|
'### Return a list',
|
|
89
119
|
'',
|
|
90
120
|
'Most real tools return a variable-length list (search results, a user’s tasks). Bind the **whole array** — a single `${response.path}` returns the referenced value verbatim, arrays included:',
|
|
@@ -129,7 +129,7 @@ const SDK_RECIPES = [
|
|
|
129
129
|
'',
|
|
130
130
|
'### Non-trivial tool: ctx connectors, annotations, visibility, async',
|
|
131
131
|
'',
|
|
132
|
-
"`ctx` is `{ input, user, connectors }`. Bind connectors with `use` on the server, then call one inside `fulfil` to record a step. `annotations.readOnly()` declares a closed-world safe read. TypeScript action helpers enforce confirmation only with `{ confirm: true }`; omitted or `false` executes directly, and action/destructive/open-world hints alone never enable the gate. For stateless hosts that cannot present Noodle confirmation, set `interactions: { confirmationFallback: 'host' }` in the `server` options to explicitly trust native host write approval; omission remains fail-closed and the fallback never supplies missing `ctx.elicit` input. `visibility` defaults to `['model', 'app']` — set `['app']` to hide a helper from the model. `fulfil` may be `async` (the compiler awaits it while recording).",
|
|
132
|
+
"`ctx` is `{ input, user, connectors }`. Bind connectors with `use` on the server, then call one inside `fulfil` to record a step. `annotations.readOnly()` declares a closed-world safe read. TypeScript action helpers enforce confirmation only with `{ confirm: true }`; omitted or `false` executes directly, and action/destructive/open-world hints alone never enable the gate. For stateless hosts that cannot present Noodle confirmation, set `interactions: { confirmationFallback: 'host' }` in the `server` options to explicitly trust native host write approval; omission remains fail-closed and the fallback never supplies missing `ctx.elicit` input. `visibility` defaults to `['model', 'app']` — set `['app']` to hide a helper from the model. For a narrow explicit-intent tool, `modelVisibility: { latestMessageIncludesAny: [...] }` deterministically limits model discovery to a latest user message containing one of the normalized literal phrases; it is not authorization. `fulfil` may be `async` (the compiler awaits it while recording).",
|
|
133
133
|
'',
|
|
134
134
|
'```ts',
|
|
135
135
|
"import { annotations, connector, server, tool, z } from '@noodleseed/one';",
|
|
@@ -160,6 +160,7 @@ const SDK_RECIPES = [
|
|
|
160
160
|
' output: z.object({ subject: z.string(), status: z.string() }),',
|
|
161
161
|
' annotations: annotations.readOnly(), // read-only hint for hosts',
|
|
162
162
|
" visibility: ['model', 'app'], // default; use ['app'] to hide the tool from the model",
|
|
163
|
+
" modelVisibility: { latestMessageIncludesAny: ['show ticket', 'open ticket'] },",
|
|
163
164
|
' // ctx is { input, user, connectors }. A connector call records one flow step (a Ref) —',
|
|
164
165
|
' // recording is not execution, so never branch on the result with native if (use when).',
|
|
165
166
|
' fulfil: ({ input, connectors }) => {',
|
|
@@ -243,7 +244,7 @@ const SDK_AUTHORING_SIGNATURES = [
|
|
|
243
244
|
'## Authoring signatures',
|
|
244
245
|
'',
|
|
245
246
|
'- `server(name, options, definitions)` — `options` commonly includes `title`, `version`, `instructions`, `agentGuide`, `distribution`, `branding`, `auth`, `use`, `provides`, `state`, and `handoff`; `definitions` is the array of tools/resources/prompts.',
|
|
246
|
-
'- `tool(name, { description, input, output, annotations?, visibility?, view?, fulfil })` — `input`/`output` are Zod schemas; `fulfil({ input, connectors, user })` returns data matching `output`. Add `view: { component, entry }` for a React widget; use `visibility: ["app"]` for an app-only helper.',
|
|
247
|
+
'- `tool(name, { description, input, output, annotations?, visibility?, modelVisibility?, view?, fulfil })` — `input`/`output` are Zod schemas; `fulfil({ input, connectors, user })` returns data matching `output`. Add `view: { component, entry }` for a React widget; use `visibility: ["app"]` for an app-only helper. Use `modelVisibility.latestMessageIncludesAny` only for normalized literal explicit-intent discovery; it does not authorize execution.',
|
|
247
248
|
'- Keep tool input names application-owned and meaningful; `__noodleIntent` is reserved for an optional serve-time operator analytics adapter and never reaches `fulfil`.',
|
|
248
249
|
'- `resource(name, { uri, description?, mimeType?, fulfil })` and `prompt(name, { description?, arguments?, fulfil })` expose MCP resources/prompts.',
|
|
249
250
|
'- View metadata (`viewTitle`, `viewDescription`, `csp`, `domain`, `permissions`) belongs on the tool that renders it; `asset("./path")` packages local files.',
|
|
@@ -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
|
|
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:
|
|
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.
|
|
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
|
'',
|
|
@@ -27,6 +27,7 @@ export declare function selectAssistantModelTools(artifact: RuntimeArtifact, cal
|
|
|
27
27
|
readonly identityKind?: string;
|
|
28
28
|
}) | undefined, options?: {
|
|
29
29
|
readonly anonymousSignInOfferSurface?: readonly SurfaceCapabilityRef[];
|
|
30
|
+
readonly latestMessage?: string;
|
|
30
31
|
}): readonly ArtifactTool[];
|
|
31
32
|
/**
|
|
32
33
|
* Build bounded product guidance from structured App Package data, never from rendered host files.
|
|
@@ -3,6 +3,9 @@ import { filterAuthorizedTools } from '@noodle-borg/protocol';
|
|
|
3
3
|
import { projectArtifactForSurface } from './artifact-projection.js';
|
|
4
4
|
/** Model-context budget for the embedded assistant's compact product workflow projection. */
|
|
5
5
|
export const ASSISTANT_GUIDE_MAX_BYTES = 16 * 1024;
|
|
6
|
+
const LATEST_MESSAGE_INCLUDES_ANY = 'x-noodleseed-model-latest-message-includes-any';
|
|
7
|
+
const MAX_VISIBILITY_PHRASES = 32;
|
|
8
|
+
const MAX_VISIBILITY_PHRASE_CHARS = 128;
|
|
6
9
|
/**
|
|
7
10
|
* Select the exact deployment tools an embedded model may see for this turn.
|
|
8
11
|
*
|
|
@@ -16,7 +19,38 @@ export function selectAssistantModelTools(artifact, caller, options = {}) {
|
|
|
16
19
|
? projectArtifactForSurface(artifact, options.anonymousSignInOfferSurface)
|
|
17
20
|
: undefined;
|
|
18
21
|
const authorized = signInOfferArtifact?.tools ?? filterAuthorizedTools(artifact.tools, caller);
|
|
19
|
-
return authorized.filter((tool) => tool._meta?.ui?.visibility?.includes('model') !== false
|
|
22
|
+
return authorized.filter((tool) => tool._meta?.ui?.visibility?.includes('model') !== false &&
|
|
23
|
+
matchesLatestMessageConstraint(tool, options.latestMessage));
|
|
24
|
+
}
|
|
25
|
+
function matchesLatestMessageConstraint(tool, latestMessage) {
|
|
26
|
+
if (!Object.hasOwn(tool.annotations ?? {}, LATEST_MESSAGE_INCLUDES_ANY)) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
const phrases = tool.annotations?.[LATEST_MESSAGE_INCLUDES_ANY];
|
|
30
|
+
if (!Array.isArray(phrases) ||
|
|
31
|
+
phrases.length < 1 ||
|
|
32
|
+
phrases.length > MAX_VISIBILITY_PHRASES ||
|
|
33
|
+
phrases.some((phrase) => typeof phrase !== 'string' ||
|
|
34
|
+
phrase.length > MAX_VISIBILITY_PHRASE_CHARS ||
|
|
35
|
+
normalizeVisibilityText(phrase).length === 0)) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
const normalizedMessage = normalizeVisibilityText(latestMessage ?? '');
|
|
39
|
+
if (normalizedMessage.length === 0)
|
|
40
|
+
return false;
|
|
41
|
+
const paddedMessage = ` ${normalizedMessage} `;
|
|
42
|
+
return phrases.some((phrase) => {
|
|
43
|
+
const normalizedPhrase = normalizeVisibilityText(phrase);
|
|
44
|
+
return paddedMessage.includes(` ${normalizedPhrase} `);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
function normalizeVisibilityText(value) {
|
|
48
|
+
return value
|
|
49
|
+
.normalize('NFKC')
|
|
50
|
+
.toLocaleLowerCase('en-US')
|
|
51
|
+
.replace(/[^\p{L}\p{N}]+/gu, ' ')
|
|
52
|
+
.trim()
|
|
53
|
+
.replace(/\s+/gu, ' ');
|
|
20
54
|
}
|
|
21
55
|
/**
|
|
22
56
|
* Build bounded product guidance from structured App Package data, never from rendered host files.
|
|
@@ -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
|
|
63
|
-
*
|
|
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 >
|
|
167
|
-
session.history.splice(0, session.history.length -
|
|
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
|
|
@@ -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
|
|
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=
|
|
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
|
|
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
|
|
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'
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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.
|
|
43
|
+
"@noodleseed/assistant": "1.24.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:
|
|
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:
|
|
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);
|
|
@@ -20,6 +20,7 @@ export function bind(connector, options) {
|
|
|
20
20
|
: {}),
|
|
21
21
|
...(connector.definitions !== undefined ? { definitions: connector.definitions } : {}),
|
|
22
22
|
...(connector.httpDef !== undefined ? { httpDef: connector.httpDef } : {}),
|
|
23
|
+
...(connector.mcpDef !== undefined ? { mcpDef: connector.mcpDef } : {}),
|
|
23
24
|
binding: { profile: options.profile, connection: options.connection },
|
|
24
25
|
};
|
|
25
26
|
}
|