@noodleseed/one 0.147.1 → 0.149.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/commands/assistant-ops.d.ts.map +1 -1
- package/dist/commands/assistant-ops.js +34 -0
- package/dist/commands/assistant-ops.js.map +1 -1
- package/dist/commands/assistant-surface-ops.d.ts +14 -1
- package/dist/commands/assistant-surface-ops.d.ts.map +1 -1
- package/dist/commands/assistant-surface-ops.js +33 -2
- package/dist/commands/assistant-surface-ops.js.map +1 -1
- package/dist/dev-local-runtime.d.ts +1 -0
- package/dist/dev-local-runtime.d.ts.map +1 -1
- package/dist/dev-local-runtime.js +3 -0
- package/dist/dev-local-runtime.js.map +1 -1
- package/node_modules/@noodle-borg/admission-limits/dist/counter-store.d.ts +20 -0
- package/node_modules/@noodle-borg/admission-limits/dist/counter-store.js +12 -0
- package/node_modules/@noodle-borg/admission-limits/dist/envelope.d.ts +44 -2
- package/node_modules/@noodle-borg/admission-limits/dist/envelope.js +43 -11
- package/node_modules/@noodle-borg/admission-limits/dist/in-memory-counter-store.d.ts +1 -0
- package/node_modules/@noodle-borg/admission-limits/dist/in-memory-counter-store.js +14 -1
- package/node_modules/@noodle-borg/admission-limits/dist/portable.d.ts +1 -0
- package/node_modules/@noodle-borg/admission-limits/dist/portable.js +1 -0
- package/node_modules/@noodle-borg/admission-limits/dist/visitor-bucket.d.ts +20 -0
- package/node_modules/@noodle-borg/admission-limits/dist/visitor-bucket.js +30 -0
- package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +1 -1
- package/node_modules/@noodle-borg/agent-kit/dist/skill-embedded-assistant-ref.js +3 -1
- package/node_modules/@noodle-borg/agent-kit/dist/skill-tool-design-ref.js +4 -0
- package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-guide.d.ts +14 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-guide.js +21 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-model-context.js +145 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-store.d.ts +34 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-store.js +53 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-suggestions.js +69 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/embed-operator-view.d.ts +71 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/embed-operator-view.js +59 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/embed-store.d.ts +27 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/embed-store.js +35 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/in-memory-embed-store.js +6 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/managed-spend.d.ts +80 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/managed-spend.js +186 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/model-request.d.ts +93 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/model-runtime.js +2 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/model-stream.d.ts +35 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/portable.d.ts +3 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/portable.js +3 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/public-session.d.ts +14 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/public-session.js +23 -6
- package/node_modules/@noodle-borg/assistant-gateway/dist/public-turn.d.ts +53 -2
- package/node_modules/@noodle-borg/assistant-gateway/dist/public-turn.js +132 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/session-target.d.ts +27 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/session-target.js +50 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/surface-budget.d.ts +15 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/surface-budget.js +14 -0
- package/node_modules/@noodle-borg/assistant-gateway/package.json +1 -1
- package/node_modules/@noodle-borg/authoring/dist/assistant.d.ts +16 -0
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-assistant.js +15 -1
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +12 -0
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +5 -0
- package/node_modules/@noodle-borg/module/dist/request-analytics.d.ts +19 -2
- package/node_modules/@noodle-borg/module/dist/request-analytics.js +15 -2
- package/node_modules/@noodle-borg/observability/dist/assistant-usage.js +68 -10
- package/node_modules/@noodle-borg/observability/dist/index.js +1 -0
- package/node_modules/@noodle-borg/observability/dist/request-event-query.js +48 -0
- package/node_modules/@noodle-borg/observability/dist/request-events-postgres.js +13 -2
- package/node_modules/@noodle-borg/observability/dist/request-events.js +3 -0
- package/node_modules/@noodle-borg/service/dist/invocation-context.js +1 -12
- package/node_modules/@noodle-borg/service/dist/routes/analytics.js +5 -29
- package/node_modules/@noodle-borg/service/dist/routes/assistant-agent.js +152 -176
- package/node_modules/@noodle-borg/service/dist/routes/assistant-appearance.js +1 -3
- package/node_modules/@noodle-borg/service/dist/routes/assistant-dispatch.js +5 -0
- package/node_modules/@noodle-borg/service/dist/routes/assistant-embeds.js +26 -38
- package/node_modules/@noodle-borg/service/dist/routes/assistant-interaction-stream.js +11 -4
- package/node_modules/@noodle-borg/service/dist/routes/assistant-interactions.js +17 -8
- package/node_modules/@noodle-borg/service/dist/routes/assistant-model-binding.js +1 -4
- package/node_modules/@noodle-borg/service/dist/routes/assistant-public-session.js +7 -3
- package/node_modules/@noodle-borg/service/dist/routes/assistant-route-http.js +1 -0
- package/node_modules/@noodle-borg/service/dist/routes/assistant-session-target.js +4 -47
- package/node_modules/@noodle-borg/service/dist/routes/assistant-suggestions.js +106 -0
- package/node_modules/@noodle-borg/service/dist/routes/assistant-transcript.js +6 -1
- package/node_modules/@noodle-borg/service/dist/routes/assistant.js +35 -12
- package/node_modules/@noodle-borg/service/package.json +1 -1
- package/node_modules/@noodle-borg/transport-http/dist/request-capture.js +2 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/assistant.d.ts +38 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/assistant.js +49 -4
- package/node_modules/@noodleseed/assistant/package.json +1 -1
- package/package.json +2 -2
- package/node_modules/@noodle-borg/service/dist/routes/assistant-public-turn.js +0 -37
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface ModelToolCall {
|
|
2
|
+
readonly id: string;
|
|
3
|
+
readonly type: 'function';
|
|
4
|
+
readonly function: {
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly arguments: string;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Opaque provider round-trip state, echoed back unread on the follow-up request.
|
|
10
|
+
*
|
|
11
|
+
* Gemini 3.x returns a `thought_signature` here and **rejects the next request** without it, so a
|
|
12
|
+
* client that drops this can call a tool once and never finish the turn. The OpenAI wire format
|
|
13
|
+
* reserves `extra_content` for exactly this, so it is passed through rather than special-cased —
|
|
14
|
+
* nothing here reads it, and no provider is named in the code that carries it.
|
|
15
|
+
*/
|
|
16
|
+
readonly extra_content?: unknown;
|
|
17
|
+
}
|
|
18
|
+
export interface ModelCompletion {
|
|
19
|
+
readonly choices: readonly {
|
|
20
|
+
readonly message: {
|
|
21
|
+
readonly role: 'assistant';
|
|
22
|
+
readonly content?: string | null;
|
|
23
|
+
readonly tool_calls?: readonly ModelToolCall[];
|
|
24
|
+
};
|
|
25
|
+
}[];
|
|
26
|
+
readonly usage?: ModelUsage;
|
|
27
|
+
}
|
|
28
|
+
export interface ModelUsage {
|
|
29
|
+
readonly promptTokens: number;
|
|
30
|
+
readonly completionTokens: number;
|
|
31
|
+
readonly totalTokens: number;
|
|
32
|
+
readonly reasoningTokens?: number;
|
|
33
|
+
}
|
|
34
|
+
export declare function readModelCompletion(response: Response, onContent: (delta: string) => void, maxBytes?: number): Promise<ModelCompletion>;
|
|
35
|
+
//# sourceMappingURL=model-stream.d.ts.map
|
|
@@ -17,16 +17,19 @@ export * from './assistant-transcript.js';
|
|
|
17
17
|
export * from './assistant-view-availability.js';
|
|
18
18
|
export * from './elevation.js';
|
|
19
19
|
export * from './elevation-store.js';
|
|
20
|
+
export * from './embed-operator-view.js';
|
|
20
21
|
export * from './embed-script.js';
|
|
21
22
|
export * from './embed-store.js';
|
|
22
23
|
export * from './in-memory-assistant-appearance-store.js';
|
|
23
24
|
export * from './in-memory-elevation-store.js';
|
|
24
25
|
export * from './in-memory-embed-store.js';
|
|
26
|
+
export * from './managed-spend.js';
|
|
25
27
|
export * from './public-configuration.js';
|
|
26
28
|
export * from './public-session.js';
|
|
27
29
|
export * from './public-surface.js';
|
|
28
30
|
export * from './public-turn.js';
|
|
29
31
|
export * from './session-resume.js';
|
|
32
|
+
export * from './session-target.js';
|
|
30
33
|
export * from './surface-budget.js';
|
|
31
34
|
export * from './tenant-ref.js';
|
|
32
35
|
//# sourceMappingURL=portable.d.ts.map
|
|
@@ -17,16 +17,19 @@ export * from './assistant-transcript.js';
|
|
|
17
17
|
export * from './assistant-view-availability.js';
|
|
18
18
|
export * from './elevation.js';
|
|
19
19
|
export * from './elevation-store.js';
|
|
20
|
+
export * from './embed-operator-view.js';
|
|
20
21
|
export * from './embed-script.js';
|
|
21
22
|
export * from './embed-store.js';
|
|
22
23
|
export * from './in-memory-assistant-appearance-store.js';
|
|
23
24
|
export * from './in-memory-elevation-store.js';
|
|
24
25
|
export * from './in-memory-embed-store.js';
|
|
26
|
+
export * from './managed-spend.js';
|
|
25
27
|
export * from './public-configuration.js';
|
|
26
28
|
export * from './public-session.js';
|
|
27
29
|
export * from './public-surface.js';
|
|
28
30
|
export * from './public-turn.js';
|
|
29
31
|
export * from './session-resume.js';
|
|
32
|
+
export * from './session-target.js';
|
|
30
33
|
export * from './surface-budget.js';
|
|
31
34
|
export * from './tenant-ref.js';
|
|
32
35
|
//# sourceMappingURL=portable.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type AdmissionEnvelope, type 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
4
|
import { type SurfaceBudgetBounds } from './surface-budget.js';
|
|
@@ -43,6 +43,13 @@ export interface PublicSessionRequest {
|
|
|
43
43
|
* be parsed, in which case the surface tier alone applies: fairness degrades, solvency does not.
|
|
44
44
|
*/
|
|
45
45
|
readonly addressBucket?: string | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* The browser's own visitor identifier (admission tier 4), unvalidated exactly like `embedId`:
|
|
48
|
+
* it arrives from the request body, so the decision owns its parse rather than the transport.
|
|
49
|
+
* Client-supplied and therefore rotatable, so it bounds fairness and never abuse — the address
|
|
50
|
+
* tier underneath is the one that cannot be rotated.
|
|
51
|
+
*/
|
|
52
|
+
readonly visitorId?: unknown;
|
|
46
53
|
}
|
|
47
54
|
export type PublicSessionResult = {
|
|
48
55
|
readonly ok: true;
|
|
@@ -54,6 +61,12 @@ export type PublicSessionResult = {
|
|
|
54
61
|
readonly status: number;
|
|
55
62
|
readonly code: string;
|
|
56
63
|
readonly message: string;
|
|
64
|
+
/**
|
|
65
|
+
* The surface this refusal belongs to, once it is known. Present for every refusal an operator
|
|
66
|
+
* needs to see — out of budget, address at its ceiling, origin off the live allowlist — and
|
|
67
|
+
* absent for the ones that cannot be attributed to a tenant at all, which are prober noise.
|
|
68
|
+
*/
|
|
69
|
+
readonly embed?: PublicEmbedRecord;
|
|
57
70
|
};
|
|
58
71
|
export declare function mintPublicSession(request: PublicSessionRequest, envelope: AdmissionEnvelope, ports: PublicSessionPorts): Promise<PublicSessionResult>;
|
|
59
72
|
//# sourceMappingURL=public-session.d.ts.map
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { visitorBucket, } from '@noodle-borg/admission-limits/portable';
|
|
1
2
|
import { isPublicEmbedId } from './embed-store.js';
|
|
2
3
|
import { surfaceEnvelope } from './surface-budget.js';
|
|
3
|
-
function refuse(status, code, message) {
|
|
4
|
-
return { ok: false, status, code, message };
|
|
4
|
+
function refuse(status, code, message, embed) {
|
|
5
|
+
return { ok: false, status, code, message, ...(embed === undefined ? {} : { embed }) };
|
|
5
6
|
}
|
|
6
7
|
export async function mintPublicSession(request, envelope, ports) {
|
|
7
8
|
if (typeof request.embedId !== 'string' || !isPublicEmbedId(request.embedId)) {
|
|
@@ -22,17 +23,33 @@ export async function mintPublicSession(request, envelope, ports) {
|
|
|
22
23
|
return refuse(403, 'embed_not_found', 'embed is not available');
|
|
23
24
|
const surface = await ports.resolveActiveSurface(embed);
|
|
24
25
|
if (surface === undefined) {
|
|
25
|
-
return refuse(409, 'surface_unavailable', 'assistant deployment is unavailable');
|
|
26
|
+
return refuse(409, 'surface_unavailable', 'assistant deployment is unavailable', embed);
|
|
26
27
|
}
|
|
27
28
|
// Origins come from the live artifact, so `noodle deploy` alone updates a pasted snippet.
|
|
28
29
|
if (!surface.origins.includes(request.origin)) {
|
|
29
|
-
return refuse(403, 'origin_not_allowed', 'origin is not allowed');
|
|
30
|
+
return refuse(403, 'origin_not_allowed', 'origin is not allowed', embed);
|
|
30
31
|
}
|
|
31
32
|
// The surface's own budget, which an operator may have lowered — or set to zero, which is the kill
|
|
32
33
|
// switch. Resolved after the embed is known and before anything is spent.
|
|
33
34
|
const budgetBounds = await ports.resolveBudgetBounds?.(embed);
|
|
34
35
|
const surfaceLimits = surfaceEnvelope(envelope, embed, budgetBounds);
|
|
35
36
|
const now = ports.now();
|
|
37
|
+
// Tier 4 before tier 3, which is before solvency. A visitor at their own hourly ceiling is refused
|
|
38
|
+
// without spending any of the shared address allowance, so one person behind a corporate NAT cannot
|
|
39
|
+
// consume the allowance the other three hundred are also using. An abuser who rotates the
|
|
40
|
+
// identifier simply never meets this tier and lands on the address bound below, which is the point:
|
|
41
|
+
// this is fairness, that is abuse control, and they are deliberately not the same limit.
|
|
42
|
+
const visitor = visitorBucket(request.visitorId);
|
|
43
|
+
if (visitor !== undefined) {
|
|
44
|
+
const perVisitor = await ports.counters.consume({
|
|
45
|
+
key: `mints:vis:${embed.embedId}:${visitor}`,
|
|
46
|
+
limit: surfaceLimits.mintsPerVisitorHour,
|
|
47
|
+
window: 'hour',
|
|
48
|
+
}, now);
|
|
49
|
+
if (!perVisitor.allowed) {
|
|
50
|
+
return refuse(429, 'visitor_session_budget_exhausted', 'assistant is unavailable right now', embed);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
36
53
|
// Fairness before solvency: an address at its hourly ceiling is refused without spending any of the
|
|
37
54
|
// surface's day, so one visitor cannot burn the customer's budget on refusals.
|
|
38
55
|
if (typeof request.addressBucket === 'string') {
|
|
@@ -42,14 +59,14 @@ export async function mintPublicSession(request, envelope, ports) {
|
|
|
42
59
|
window: 'hour',
|
|
43
60
|
}, now);
|
|
44
61
|
if (!perAddress.allowed) {
|
|
45
|
-
return refuse(429, 'address_session_budget_exhausted', 'assistant is unavailable right now');
|
|
62
|
+
return refuse(429, 'address_session_budget_exhausted', 'assistant is unavailable right now', embed);
|
|
46
63
|
}
|
|
47
64
|
}
|
|
48
65
|
const mints = await ports.counters.consume({ key: `mints:${embed.embedId}`, limit: surfaceLimits.mintsPerDay }, now);
|
|
49
66
|
if (!mints.allowed) {
|
|
50
67
|
// Distinct from a hard error: the widget renders this calmly, and an operator sees a surface that
|
|
51
68
|
// is switched off (limit 0) or spent, not a broken embed.
|
|
52
|
-
return refuse(429, 'daily_session_budget_exhausted', 'assistant is unavailable right now');
|
|
69
|
+
return refuse(429, 'daily_session_budget_exhausted', 'assistant is unavailable right now', embed);
|
|
53
70
|
}
|
|
54
71
|
const session = await ports.createSession({
|
|
55
72
|
embed,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import type { AssistantTurnConsumption } from './assistant-store.js';
|
|
1
|
+
import { type AdmissionEnvelope, type DailyCounterStore } from '@noodle-borg/admission-limits/portable';
|
|
2
|
+
import type { AssistantSessionRecord, AssistantStore, AssistantTurnConsumption } from './assistant-store.js';
|
|
3
3
|
import type { PublicEmbedRecord, PublicEmbedStore } from './embed-store.js';
|
|
4
|
+
import type { ManagedAssistantModelResolver } from './model-request.js';
|
|
4
5
|
import { type SurfaceBudgetBounds } from './surface-budget.js';
|
|
5
6
|
/**
|
|
6
7
|
* Whether an anonymous visitor's next turn runs.
|
|
@@ -43,5 +44,55 @@ export type PublicTurnResult = {
|
|
|
43
44
|
readonly code: string;
|
|
44
45
|
readonly message: string;
|
|
45
46
|
};
|
|
47
|
+
export interface AssistantPublicTurnDeps {
|
|
48
|
+
readonly publicEmbeds?: PublicEmbedStore;
|
|
49
|
+
readonly admissionCounters?: DailyCounterStore;
|
|
50
|
+
readonly admissionEnvelope?: AdmissionEnvelope;
|
|
51
|
+
readonly managedModelResolver?: ManagedAssistantModelResolver;
|
|
52
|
+
readonly store: Pick<AssistantStore, 'consumeTurn'>;
|
|
53
|
+
readonly clock?: () => Date;
|
|
54
|
+
}
|
|
55
|
+
export interface PublicTurnRefusal {
|
|
56
|
+
readonly status: number;
|
|
57
|
+
readonly code: string;
|
|
58
|
+
readonly message: string;
|
|
59
|
+
}
|
|
60
|
+
/** Adapt a session plus serving ports to the transport-free public-turn decision below. */
|
|
61
|
+
export declare function refusePublicTurn(deps: AssistantPublicTurnDeps, session: AssistantSessionRecord, message: string, addressBucket: string | undefined): Promise<PublicTurnRefusal | undefined>;
|
|
62
|
+
/**
|
|
63
|
+
* The same gate in front of a WebMCP bridge tool call (ADR 0220). A browser agent can call governed
|
|
64
|
+
* tools without ever running a model turn, so the turn budget above would never refuse it.
|
|
65
|
+
*/
|
|
66
|
+
export declare function refuseBridgeToolCall(deps: AssistantPublicTurnDeps, session: AssistantSessionRecord): Promise<PublicTurnRefusal | undefined>;
|
|
46
67
|
export declare function admitPublicTurn(request: PublicTurnRequest, defaults: AdmissionEnvelope, ports: PublicTurnPorts): Promise<PublicTurnResult>;
|
|
68
|
+
/**
|
|
69
|
+
* Whether a browser agent's next bridge tool call runs (ADR 0220).
|
|
70
|
+
*
|
|
71
|
+
* Separate from {@link admitPublicTurn} because a WebMCP call is not a turn: it spends no model budget,
|
|
72
|
+
* so the turn caps would never refuse it however long an agent kept going. It still reaches connectors
|
|
73
|
+
* and customer backends, so it gets its own pair of bounds — and, like every other admission decision
|
|
74
|
+
* here, the envelope is read per call so an operator lowering a cap stops traffic already under way.
|
|
75
|
+
*
|
|
76
|
+
* Authority is not decided here and never could be: this runs *after* the route has authenticated the
|
|
77
|
+
* session, and the call it admits still goes through tool authorization and confirmation downstream.
|
|
78
|
+
*/
|
|
79
|
+
export interface BridgeToolCallPorts {
|
|
80
|
+
readonly counters: DailyCounterStore;
|
|
81
|
+
readonly embeds: Pick<PublicEmbedStore, 'lookup'>;
|
|
82
|
+
readonly resolveBudgetBounds?: (embed: PublicEmbedRecord) => Promise<SurfaceBudgetBounds | undefined>;
|
|
83
|
+
now(): Date;
|
|
84
|
+
}
|
|
85
|
+
export interface BridgeToolCallRequest {
|
|
86
|
+
readonly sessionId: string;
|
|
87
|
+
readonly publicEmbedId: string;
|
|
88
|
+
}
|
|
89
|
+
export type BridgeToolCallResult = {
|
|
90
|
+
readonly ok: true;
|
|
91
|
+
} | {
|
|
92
|
+
readonly ok: false;
|
|
93
|
+
readonly status: number;
|
|
94
|
+
readonly code: string;
|
|
95
|
+
readonly message: string;
|
|
96
|
+
};
|
|
97
|
+
export declare function admitBridgeToolCall(request: BridgeToolCallRequest, defaults: AdmissionEnvelope, ports: BridgeToolCallPorts): Promise<BridgeToolCallResult>;
|
|
47
98
|
//# sourceMappingURL=public-turn.d.ts.map
|
|
@@ -1,4 +1,65 @@
|
|
|
1
|
+
import { ADMISSION_DEFAULTS, isDisabled, } from '@noodle-borg/admission-limits/portable';
|
|
1
2
|
import { surfaceEnvelope } from './surface-budget.js';
|
|
3
|
+
/** Adapt a session plus serving ports to the transport-free public-turn decision below. */
|
|
4
|
+
export async function refusePublicTurn(deps, session, message, addressBucket) {
|
|
5
|
+
const publicEmbedId = session.publicEmbedId;
|
|
6
|
+
if (publicEmbedId === undefined)
|
|
7
|
+
return undefined;
|
|
8
|
+
if (!deps.publicEmbeds || !deps.admissionCounters) {
|
|
9
|
+
return {
|
|
10
|
+
status: 503,
|
|
11
|
+
code: 'admission_unavailable',
|
|
12
|
+
message: 'assistant is unavailable right now',
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
const result = await admitPublicTurn({ sessionId: session.id, publicEmbedId, message, ...(addressBucket ? { addressBucket } : {}) }, deps.admissionEnvelope ?? ADMISSION_DEFAULTS, {
|
|
16
|
+
counters: deps.admissionCounters,
|
|
17
|
+
embeds: deps.publicEmbeds,
|
|
18
|
+
resolveBudgetBounds: async () => session.modelSource !== 'noodle-managed'
|
|
19
|
+
? undefined
|
|
20
|
+
: (await deps.managedModelResolver?.resolve({
|
|
21
|
+
tenant: session.tenant,
|
|
22
|
+
deploymentId: session.deploymentId,
|
|
23
|
+
}))?.publicAdmission,
|
|
24
|
+
consumeTurn: (id, limit) => deps.store.consumeTurn(id, limit),
|
|
25
|
+
now: () => deps.clock?.() ?? new Date(),
|
|
26
|
+
});
|
|
27
|
+
return result.ok
|
|
28
|
+
? undefined
|
|
29
|
+
: { status: result.status, code: result.code, message: result.message };
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The same gate in front of a WebMCP bridge tool call (ADR 0220). A browser agent can call governed
|
|
33
|
+
* tools without ever running a model turn, so the turn budget above would never refuse it.
|
|
34
|
+
*/
|
|
35
|
+
export async function refuseBridgeToolCall(deps, session) {
|
|
36
|
+
const publicEmbedId = session.publicEmbedId;
|
|
37
|
+
if (publicEmbedId === undefined)
|
|
38
|
+
return undefined;
|
|
39
|
+
if (!deps.publicEmbeds || !deps.admissionCounters) {
|
|
40
|
+
return {
|
|
41
|
+
status: 503,
|
|
42
|
+
code: 'admission_unavailable',
|
|
43
|
+
message: 'assistant is unavailable right now',
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const result = await admitBridgeToolCall({ sessionId: session.id, publicEmbedId }, deps.admissionEnvelope ?? ADMISSION_DEFAULTS, {
|
|
47
|
+
counters: deps.admissionCounters,
|
|
48
|
+
embeds: deps.publicEmbeds,
|
|
49
|
+
// Resolved live rather than snapshotted, so removing a surface from a sponsored cohort lowers
|
|
50
|
+
// its envelope immediately instead of at the next mint.
|
|
51
|
+
resolveBudgetBounds: async () => session.modelSource !== 'noodle-managed'
|
|
52
|
+
? undefined
|
|
53
|
+
: (await deps.managedModelResolver?.resolve({
|
|
54
|
+
tenant: session.tenant,
|
|
55
|
+
deploymentId: session.deploymentId,
|
|
56
|
+
}))?.publicAdmission,
|
|
57
|
+
now: () => deps.clock?.() ?? new Date(),
|
|
58
|
+
});
|
|
59
|
+
return result.ok
|
|
60
|
+
? undefined
|
|
61
|
+
: { status: result.status, code: result.code, message: result.message };
|
|
62
|
+
}
|
|
2
63
|
export async function admitPublicTurn(request, defaults, ports) {
|
|
3
64
|
// Cheapest first, so an oversized body costs neither a session slot nor surface budget. The public
|
|
4
65
|
// bound is narrower than the shared request schema's, which stays as it is for authenticated callers.
|
|
@@ -64,6 +125,77 @@ export async function admitPublicTurn(request, defaults, ports) {
|
|
|
64
125
|
message: 'assistant is unavailable right now',
|
|
65
126
|
};
|
|
66
127
|
}
|
|
128
|
+
// Platform spend last, when someone other than the customer is paying. Every counter here is
|
|
129
|
+
// all-or-nothing with no refund, so whichever runs first is charged for turns the ones after it
|
|
130
|
+
// refuse — and a session ending at its length limit is ordinary traffic, many times a day, while
|
|
131
|
+
// spend exhaustion is rare and for one day only. Charging the sponsor for every visitor's last
|
|
132
|
+
// turn is the worse of the two leaks, so this is the counter that goes last.
|
|
133
|
+
//
|
|
134
|
+
// It reports the surface's own exhaustion code on purpose: every published widget already treats
|
|
135
|
+
// `daily_turn_budget_exhausted` as final, and a new code would be read as retryable by all of them
|
|
136
|
+
// — precisely the load this ceiling exists to refuse. At the ladder's closing rung the surface
|
|
137
|
+
// ceiling is already zero, so that refusal arrives above; this is the backstop between rungs.
|
|
138
|
+
if (budgetBounds?.spend !== undefined) {
|
|
139
|
+
const { key, units, allowance } = budgetBounds.spend;
|
|
140
|
+
const spend = await ports.counters.consume({ key, limit: allowance, amount: units }, ports.now());
|
|
141
|
+
if (!spend.allowed) {
|
|
142
|
+
return {
|
|
143
|
+
ok: false,
|
|
144
|
+
status: 429,
|
|
145
|
+
code: 'daily_turn_budget_exhausted',
|
|
146
|
+
message: 'assistant is unavailable right now',
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
}
|
|
67
150
|
return { ok: true, turnCount: session.turnCount };
|
|
68
151
|
}
|
|
152
|
+
export async function admitBridgeToolCall(request, defaults, ports) {
|
|
153
|
+
try {
|
|
154
|
+
const embed = await ports.embeds.lookup(request.publicEmbedId);
|
|
155
|
+
if (embed === undefined) {
|
|
156
|
+
return {
|
|
157
|
+
ok: false,
|
|
158
|
+
status: 403,
|
|
159
|
+
code: 'embed_not_found',
|
|
160
|
+
message: 'assistant is unavailable right now',
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
const budgetBounds = await ports.resolveBudgetBounds?.(embed);
|
|
164
|
+
const envelope = surfaceEnvelope(defaults, embed, budgetBounds);
|
|
165
|
+
// One kill switch, not two. An operator who zeroes a surface's day means all of it, including the
|
|
166
|
+
// tools a page agent can reach without ever running a turn.
|
|
167
|
+
if (isDisabled(envelope))
|
|
168
|
+
return bridgeBudgetExhausted('daily');
|
|
169
|
+
// The session's own allowance first, so a session with nothing left cannot spend the customer's
|
|
170
|
+
// day on a call that is refused a moment later. Mirrors the turn path's ordering.
|
|
171
|
+
const session = await ports.counters.consume({
|
|
172
|
+
key: `bridge:ses:${request.sessionId}`,
|
|
173
|
+
limit: envelope.bridgeToolCallsPerSession,
|
|
174
|
+
}, ports.now());
|
|
175
|
+
if (!session.allowed)
|
|
176
|
+
return bridgeBudgetExhausted('session');
|
|
177
|
+
const surface = await ports.counters.consume({ key: `bridge:${request.publicEmbedId}`, limit: envelope.bridgeToolCallsPerDay }, ports.now());
|
|
178
|
+
if (!surface.allowed)
|
|
179
|
+
return bridgeBudgetExhausted('daily');
|
|
180
|
+
return { ok: true };
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
// Fail closed. A counter store we cannot reach is a budget we cannot enforce, and an unbounded
|
|
184
|
+
// agent loop against a customer's backend is the exact outcome these caps exist to prevent.
|
|
185
|
+
return {
|
|
186
|
+
ok: false,
|
|
187
|
+
status: 503,
|
|
188
|
+
code: 'bridge_admission_unavailable',
|
|
189
|
+
message: 'assistant is unavailable right now',
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function bridgeBudgetExhausted(scope) {
|
|
194
|
+
return {
|
|
195
|
+
ok: false,
|
|
196
|
+
status: 429,
|
|
197
|
+
code: `${scope}_bridge_budget_exhausted`,
|
|
198
|
+
message: 'assistant is unavailable right now',
|
|
199
|
+
};
|
|
200
|
+
}
|
|
69
201
|
//# sourceMappingURL=public-turn.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { RuntimeArtifact } from '@noodle-borg/compiler';
|
|
2
|
+
import type { AssistantSessionRecord } from './assistant-store.js';
|
|
3
|
+
/**
|
|
4
|
+
* The deployment a session may act on, already narrowed to the surface that admitted it.
|
|
5
|
+
*
|
|
6
|
+
* This is the seam ADR 0201's projection rests on. Every session-authenticated route resolves its target
|
|
7
|
+
* through here instead of calling `registry.get` directly, so the dozen `artifact.tools.find(...)` sites
|
|
8
|
+
* downstream get a projected artifact without knowing projection exists. Delete the projection call
|
|
9
|
+
* below and the projection tests fail — one deletion, not a dozen.
|
|
10
|
+
*
|
|
11
|
+
* The surface is read from the session's pinned deployment, never from whatever is active now: projecting
|
|
12
|
+
* artifact N by a surface read from artifact N+1 is a skew bug that would widen or narrow a live session
|
|
13
|
+
* on someone else's deploy. 5.1b's `assertDistinctSurfaces` guarantees at most one public-audience
|
|
14
|
+
* surface and at most one authenticated surface per artifact, so a binding never needs disambiguation.
|
|
15
|
+
*
|
|
16
|
+
* Sessions carry their binding explicitly (`boundSurface`, written at mint). A record from before the
|
|
17
|
+
* binding existed derives it here from the pinned deployment and the session's own origin, so in-flight
|
|
18
|
+
* sessions stay correct across the deploy that introduced binding; only a pre-surfaces artifact still
|
|
19
|
+
* reaches the whole server, because the deployment-wide union is that released shape's entire contract.
|
|
20
|
+
*/
|
|
21
|
+
export interface AssistantSessionTarget {
|
|
22
|
+
readonly served: {
|
|
23
|
+
readonly artifact: RuntimeArtifact;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export declare function resolveAssistantSessionTarget<Target extends AssistantSessionTarget>(load: (deploymentId: string) => Promise<Target | undefined>, session: AssistantSessionRecord): Promise<Target | undefined>;
|
|
27
|
+
//# sourceMappingURL=session-target.d.ts.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { projectArtifactForSurface } from './artifact-projection.js';
|
|
2
|
+
import { authenticatedSurfaceOf, publicSurfaceOf, surfaceBindingForOrigin, } from './public-surface.js';
|
|
3
|
+
export async function resolveAssistantSessionTarget(load, session) {
|
|
4
|
+
const target = await load(session.deploymentId);
|
|
5
|
+
if (!target)
|
|
6
|
+
return undefined;
|
|
7
|
+
const assistant = target.served.artifact.server.assistant;
|
|
8
|
+
const bound = session.boundSurface ?? deriveLegacyBinding(assistant, session);
|
|
9
|
+
if (bound === 'pre-surfaces')
|
|
10
|
+
return target;
|
|
11
|
+
// Fail closed: an origin no surface owns must never widen to the whole server.
|
|
12
|
+
if (bound === 'unowned')
|
|
13
|
+
return undefined;
|
|
14
|
+
// Fail closed on a vanished surface. A rollback to an artifact without the bound surface must end the
|
|
15
|
+
// session's reach, never hand it the unprojected server.
|
|
16
|
+
if (bound === 'authenticated') {
|
|
17
|
+
const surface = authenticatedSurfaceOf(assistant);
|
|
18
|
+
if (surface === undefined)
|
|
19
|
+
return undefined;
|
|
20
|
+
// An omitted allowlist on an authenticated surface is the authored whole-server intent, so the
|
|
21
|
+
// exact binding still holds (instructions, budgets, attribution) without narrowing capabilities.
|
|
22
|
+
if (surface.capabilities === undefined)
|
|
23
|
+
return target;
|
|
24
|
+
return projected(target, surface.capabilities);
|
|
25
|
+
}
|
|
26
|
+
const surface = publicSurfaceOf(assistant);
|
|
27
|
+
if (surface === undefined)
|
|
28
|
+
return undefined;
|
|
29
|
+
return projected(target, surface.capabilities);
|
|
30
|
+
}
|
|
31
|
+
function projected(target, capabilities) {
|
|
32
|
+
return {
|
|
33
|
+
...target,
|
|
34
|
+
served: {
|
|
35
|
+
...target.served,
|
|
36
|
+
artifact: projectArtifactForSurface(target.served.artifact, capabilities),
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A record minted before `boundSurface` existed: the embed id marks the public surface; otherwise the
|
|
42
|
+
* session's origin selects the owning surface on the pinned deployment. `pre-surfaces` means the whole
|
|
43
|
+
* server on that released artifact shape; `unowned` fails closed above.
|
|
44
|
+
*/
|
|
45
|
+
function deriveLegacyBinding(assistant, session) {
|
|
46
|
+
if (session.publicEmbedId !== undefined)
|
|
47
|
+
return 'public';
|
|
48
|
+
return surfaceBindingForOrigin(assistant, session.origin).kind;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=session-target.js.map
|
|
@@ -4,6 +4,20 @@ import type { PublicEmbedRecord } from './embed-store.js';
|
|
|
4
4
|
export interface SurfaceBudgetBounds {
|
|
5
5
|
readonly defaults: Pick<AdmissionEnvelope, 'turnsPerSession' | 'turnsPerDay' | 'mintsPerDay'>;
|
|
6
6
|
readonly ceiling: Pick<AdmissionEnvelope, 'turnsPerDay' | 'mintsPerDay'>;
|
|
7
|
+
/**
|
|
8
|
+
* Platform spend accounting for this turn, present only when someone other than the customer is
|
|
9
|
+
* paying. Absent means customer-funded, and nothing is charged.
|
|
10
|
+
*
|
|
11
|
+
* `units` is what this turn costs at the tenant's current rung and `allowance` is its ceiling for
|
|
12
|
+
* the day; the ladder that produces both lives in `managed-spend.ts`. The ceilings above remain the
|
|
13
|
+
* primary mechanism — this counter is the exact backstop for the window in which a cached rung
|
|
14
|
+
* read still says "plenty left".
|
|
15
|
+
*/
|
|
16
|
+
readonly spend?: {
|
|
17
|
+
readonly key: string;
|
|
18
|
+
readonly units: number;
|
|
19
|
+
readonly allowance: number;
|
|
20
|
+
};
|
|
7
21
|
}
|
|
8
22
|
/**
|
|
9
23
|
* One surface's daily budget, resolved from the operator's override and the deployment's defaults.
|
|
@@ -17,5 +31,5 @@ export interface SurfaceBudgetBounds {
|
|
|
17
31
|
* operator any more than by app code, so an over-eager `--turns-per-day 999999999` degrades to the
|
|
18
32
|
* ceiling instead of becoming the ceiling. Zero survives clamping and is the kill switch.
|
|
19
33
|
*/
|
|
20
|
-
export declare function surfaceEnvelope(base: AdmissionEnvelope, embed: Pick<PublicEmbedRecord, 'turnsPerDay' | 'mintsPerDay'>, bounds?: SurfaceBudgetBounds): AdmissionEnvelope;
|
|
34
|
+
export declare function surfaceEnvelope(base: AdmissionEnvelope, embed: Pick<PublicEmbedRecord, 'turnsPerDay' | 'mintsPerDay' | 'mintsPerAddressHour' | 'turnsPerAddressHour'>, bounds?: SurfaceBudgetBounds): AdmissionEnvelope;
|
|
21
35
|
//# sourceMappingURL=surface-budget.d.ts.map
|
|
@@ -17,6 +17,20 @@ export function surfaceEnvelope(base, embed, bounds) {
|
|
|
17
17
|
...effectiveBase,
|
|
18
18
|
...(embed.turnsPerDay !== undefined ? { turnsPerDay: embed.turnsPerDay } : {}),
|
|
19
19
|
...(embed.mintsPerDay !== undefined ? { mintsPerDay: embed.mintsPerDay } : {}),
|
|
20
|
+
// The per-address bounds carry no hosted ceiling, because they govern abuse rather than spend —
|
|
21
|
+
// so the deployed default is what caps them, and an operator may only lower it. A surface that
|
|
22
|
+
// could raise its own abuse bound toward the structural maximum would be turning the bound off
|
|
23
|
+
// for everyone behind one address, which is not a budget an operator gets to spend.
|
|
24
|
+
...(embed.mintsPerAddressHour !== undefined
|
|
25
|
+
? {
|
|
26
|
+
mintsPerAddressHour: Math.min(embed.mintsPerAddressHour, effectiveBase.mintsPerAddressHour),
|
|
27
|
+
}
|
|
28
|
+
: {}),
|
|
29
|
+
...(embed.turnsPerAddressHour !== undefined
|
|
30
|
+
? {
|
|
31
|
+
turnsPerAddressHour: Math.min(embed.turnsPerAddressHour, effectiveBase.turnsPerAddressHour),
|
|
32
|
+
}
|
|
33
|
+
: {}),
|
|
20
34
|
});
|
|
21
35
|
if (bounds === undefined)
|
|
22
36
|
return requested;
|
|
@@ -102,6 +102,7 @@ export interface AssistantUiOptions {
|
|
|
102
102
|
};
|
|
103
103
|
readonly labels?: AssistantLabels;
|
|
104
104
|
readonly presentation?: AssistantPresentationOptions;
|
|
105
|
+
/** Exact initial prompts only; omit to generate context-aware initial prompts, or pass [] for none. */
|
|
105
106
|
readonly suggestedPrompts?: readonly string[];
|
|
106
107
|
readonly privacyUrl?: string;
|
|
107
108
|
readonly termsUrl?: string;
|
|
@@ -207,6 +208,17 @@ export interface EmbeddedAssistantOptions extends AssistantUiOptions {
|
|
|
207
208
|
readonly model: AssistantModel;
|
|
208
209
|
/** One surface, or every front door this assistant serves. */
|
|
209
210
|
readonly access: AssistantAccess | readonly AssistantAccess[];
|
|
211
|
+
/**
|
|
212
|
+
* Let a browser agent (Gemini-in-Chrome, Claude-in-Chrome) call this assistant's tools through the
|
|
213
|
+
* page's WebMCP API. Off unless set. Calls carry exactly the embed session's authority and take the
|
|
214
|
+
* same authorization, limits, confirmation, and audit path as the assistant's own — see ADR 0220.
|
|
215
|
+
*
|
|
216
|
+
* Deployment-wide on purpose: it decides which callers may reach the server's tools, which is
|
|
217
|
+
* surface authority rather than presentation, so it is not refinable per surface.
|
|
218
|
+
*/
|
|
219
|
+
readonly webmcp?: {
|
|
220
|
+
readonly enabled?: boolean;
|
|
221
|
+
};
|
|
210
222
|
}
|
|
211
223
|
/** One projected surface as it appears in compiled data. */
|
|
212
224
|
export interface AssistantSurfaceConfig {
|
|
@@ -226,6 +238,10 @@ export interface EmbeddedAssistantConfig extends AssistantUiOptions {
|
|
|
226
238
|
readonly allowedOrigins: readonly string[];
|
|
227
239
|
/** Mirrored from the authenticated surface, for the same reason. */
|
|
228
240
|
readonly sessionClaims?: Readonly<Record<string, SessionClaimDeclaration>>;
|
|
241
|
+
/** @see EmbeddedAssistantOptions.webmcp — deployment-wide, never per surface. */
|
|
242
|
+
readonly webmcp?: {
|
|
243
|
+
readonly enabled?: boolean;
|
|
244
|
+
};
|
|
229
245
|
}
|
|
230
246
|
export declare function openAICompatible(input: OpenAICompatibleModelInput): OpenAICompatibleModel;
|
|
231
247
|
/** Use Noodle Seed Cloud's operator-selected model. Provider details never enter authored source. */
|
|
@@ -96,7 +96,7 @@ const EMBEDS_SUBCOMMAND = subcommand({
|
|
|
96
96
|
const BUDGET_SUBCOMMAND = subcommand({
|
|
97
97
|
...SUBCOMMAND_FIELDS,
|
|
98
98
|
name: 'budget',
|
|
99
|
-
summary: 'Set what a public website surface may spend per day
|
|
99
|
+
summary: 'Set what a public website surface may spend, per day and per source address.',
|
|
100
100
|
subcommands: [
|
|
101
101
|
clientLeaf('set', 'Raise, lower, or switch off a public surface’s daily caps.', {
|
|
102
102
|
...LEAF_FIELDS,
|
|
@@ -115,6 +115,20 @@ const BUDGET_SUBCOMMAND = subcommand({
|
|
|
115
115
|
value: '<count>',
|
|
116
116
|
summary: 'Sessions this surface may open per UTC day. 0 stops it serving anyone.',
|
|
117
117
|
}),
|
|
118
|
+
flag({
|
|
119
|
+
...FLAG_FIELDS,
|
|
120
|
+
name: 'mints-per-address-hour',
|
|
121
|
+
type: 'string',
|
|
122
|
+
value: '<count>',
|
|
123
|
+
summary: 'Sessions one source address may open per hour. The abuse bound, not fairness.',
|
|
124
|
+
}),
|
|
125
|
+
flag({
|
|
126
|
+
...FLAG_FIELDS,
|
|
127
|
+
name: 'turns-per-address-hour',
|
|
128
|
+
type: 'string',
|
|
129
|
+
value: '<count>',
|
|
130
|
+
summary: 'Model turns one source address may run per hour. The abuse bound, not fairness.',
|
|
131
|
+
}),
|
|
118
132
|
flag({
|
|
119
133
|
...FLAG_FIELDS,
|
|
120
134
|
name: 'surface',
|
|
@@ -381,6 +381,9 @@ export declare const manifestV1Schema: z.ZodObject<{
|
|
|
381
381
|
sessionClaims: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
382
382
|
exposeToModel: z.ZodOptional<z.ZodBoolean>;
|
|
383
383
|
}, z.core.$strict>>>;
|
|
384
|
+
webmcp: z.ZodOptional<z.ZodObject<{
|
|
385
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
386
|
+
}, z.core.$strict>>;
|
|
384
387
|
}, z.core.$strict>>;
|
|
385
388
|
branding: z.ZodOptional<z.ZodObject<{
|
|
386
389
|
name: z.ZodOptional<z.ZodString>;
|
|
@@ -857,6 +860,9 @@ export declare const manifestV2Schema: z.ZodObject<{
|
|
|
857
860
|
sessionClaims: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
858
861
|
exposeToModel: z.ZodOptional<z.ZodBoolean>;
|
|
859
862
|
}, z.core.$strict>>>;
|
|
863
|
+
webmcp: z.ZodOptional<z.ZodObject<{
|
|
864
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
865
|
+
}, z.core.$strict>>;
|
|
860
866
|
}, z.core.$strict>>;
|
|
861
867
|
branding: z.ZodOptional<z.ZodObject<{
|
|
862
868
|
name: z.ZodOptional<z.ZodString>;
|
|
@@ -1543,6 +1549,9 @@ export declare const manifestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
1543
1549
|
sessionClaims: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
1544
1550
|
exposeToModel: z.ZodOptional<z.ZodBoolean>;
|
|
1545
1551
|
}, z.core.$strict>>>;
|
|
1552
|
+
webmcp: z.ZodOptional<z.ZodObject<{
|
|
1553
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
1554
|
+
}, z.core.$strict>>;
|
|
1546
1555
|
}, z.core.$strict>>;
|
|
1547
1556
|
branding: z.ZodOptional<z.ZodObject<{
|
|
1548
1557
|
name: z.ZodOptional<z.ZodString>;
|
|
@@ -2018,6 +2027,9 @@ export declare const manifestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
2018
2027
|
sessionClaims: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
2019
2028
|
exposeToModel: z.ZodOptional<z.ZodBoolean>;
|
|
2020
2029
|
}, z.core.$strict>>>;
|
|
2030
|
+
webmcp: z.ZodOptional<z.ZodObject<{
|
|
2031
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
2032
|
+
}, z.core.$strict>>;
|
|
2021
2033
|
}, z.core.$strict>>;
|
|
2022
2034
|
branding: z.ZodOptional<z.ZodObject<{
|
|
2023
2035
|
name: z.ZodOptional<z.ZodString>;
|
|
@@ -359,6 +359,11 @@ const embeddedAssistantSchema = z
|
|
|
359
359
|
// Mirrored from the authenticated surface; the session exchange reads it here (ADR 0141,
|
|
360
360
|
// 2026-07-14). Keys become `${user.claims.<key>}`; `exposeToModel` adds it to the identity line.
|
|
361
361
|
sessionClaims: sessionClaimsSchema.optional(),
|
|
362
|
+
// Additive optional field: a v1.x minor under ADR 0150, so no existing manifest can observe it.
|
|
363
|
+
// Declared here rather than in `assistantUiSchema` deliberately — that shape is also spread into
|
|
364
|
+
// every surface, and which callers may reach the server's tools is surface authority, not
|
|
365
|
+
// renderer-owned presentation a surface may refine.
|
|
366
|
+
webmcp: optionalStrictObject({ enabled: z.boolean().optional() }),
|
|
362
367
|
...assistantUiSchema.shape,
|
|
363
368
|
})
|
|
364
369
|
.strict()
|