@noodleseed/one 0.148.0 → 0.150.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 +17 -1
- package/dist/commands/assistant-surface-ops.d.ts.map +1 -1
- package/dist/commands/assistant-surface-ops.js +38 -2
- package/dist/commands/assistant-surface-ops.js.map +1 -1
- package/dist/commands/intents-ops.js +1 -1
- package/dist/commands/intents-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 +34 -2
- package/node_modules/@noodle-borg/admission-limits/dist/envelope.js +45 -15
- 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/skill-embedded-assistant-ref.js +1 -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/app-tool-call.d.ts +42 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/app-tool-call.js +45 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-configuration.d.ts +2 -2
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-configuration.js +17 -3
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-context.d.ts +13 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-context.js +53 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-guide.d.ts +12 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-guide.js +15 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/embed-operator-view.d.ts +79 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/embed-operator-view.js +67 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/embed-store.d.ts +37 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/embed-store.js +37 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/in-memory-embed-store.js +12 -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 +16 -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.js +22 -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 +24 -0
- package/node_modules/@noodle-borg/assistant-gateway/package.json +1 -1
- package/node_modules/@noodle-borg/authoring/dist/assistant.d.ts +31 -3
- package/node_modules/@noodle-borg/authoring/dist/assistant.js +3 -0
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-assistant.js +29 -1
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-hosted-observability.js +1 -1
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +12 -0
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +8 -3
- 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-app-tool-usage.js +25 -0
- package/node_modules/@noodle-borg/observability/dist/assistant-usage.js +68 -10
- package/node_modules/@noodle-borg/observability/dist/index.js +2 -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 -53
- package/node_modules/@noodle-borg/service/dist/observability-runtime.js +4 -6
- package/node_modules/@noodle-borg/service/dist/routes/analytics.js +5 -29
- package/node_modules/@noodle-borg/service/dist/routes/assistant-agent.js +57 -29
- package/node_modules/@noodle-borg/service/dist/routes/assistant-embeds.js +26 -38
- package/node_modules/@noodle-borg/service/dist/routes/assistant-interaction-resolution.js +1 -2
- package/node_modules/@noodle-borg/service/dist/routes/assistant-model-binding.js +1 -4
- package/node_modules/@noodle-borg/service/dist/routes/assistant-public-configuration.js +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/assistant-public-session.js +8 -4
- package/node_modules/@noodle-borg/service/dist/routes/assistant-suggestions.js +1 -2
- package/node_modules/@noodle-borg/service/dist/routes/assistant.js +64 -49
- package/node_modules/@noodle-borg/service/dist/routes/intent-capture.js +0 -12
- package/node_modules/@noodle-borg/service/dist/service.js +2 -3
- 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/@noodleseed/assistant/package.json +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { AdmissionEnvelope } from '@noodle-borg/admission-limits/portable';
|
|
2
|
+
import type { AssistantModelRequestPolicy } from './model-request.js';
|
|
3
|
+
import type { SurfaceBudgetBounds } from './surface-budget.js';
|
|
4
|
+
/**
|
|
5
|
+
* Cost breakers for the sponsored managed-assistant beta (ADR 0213 amendment §3).
|
|
6
|
+
*
|
|
7
|
+
* Noodle pays for `noodle-managed` inference and charges nothing for it, so aggregate spend is a
|
|
8
|
+
* platform expense with no customer-side limit standing behind it. Per-surface daily caps bound one
|
|
9
|
+
* surface; they do not bound how many surfaces enrol. This is the missing ceiling, expressed as a
|
|
10
|
+
* ladder rather than a switch: a tenant approaching its allowance gets shorter answers before it gets
|
|
11
|
+
* a closed door, and the door itself is the kill switch that already exists.
|
|
12
|
+
*
|
|
13
|
+
* Two things make it small. Every rung is expressed through `SurfaceBudgetBounds` and
|
|
14
|
+
* `AssistantModelRequestPolicy`, which the resolver already returns and admission already reads live
|
|
15
|
+
* per turn — so degradation applies immediately and reverses on its own when the UTC day rolls, with
|
|
16
|
+
* no unwind path to write. And the unit is one delivered bounded turn, which is the billable unit ADR
|
|
17
|
+
* 0213 §12 already defines.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Billable tokens per charged unit. Prompt and completion are counted together and completion is
|
|
21
|
+
* weighted, because output costs several times input on every provider we have measured.
|
|
22
|
+
*/
|
|
23
|
+
export declare const MANAGED_SPEND_UNIT_TOKENS = 4000;
|
|
24
|
+
export interface ManagedSpendPolicy {
|
|
25
|
+
readonly requestPolicy: AssistantModelRequestPolicy;
|
|
26
|
+
readonly admission: SurfaceBudgetBounds;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Which rung a tenant is on.
|
|
30
|
+
*
|
|
31
|
+
* An absent or nonsensical allowance means "unbudgeted", never "exhausted". Failing closed on a typo
|
|
32
|
+
* would take a customer's surface dark for a configuration mistake, and the per-surface daily cap
|
|
33
|
+
* still bounds what that costs.
|
|
34
|
+
*/
|
|
35
|
+
export declare function managedSpendRung(unitsUsed: number, allowance: number): number;
|
|
36
|
+
export declare function managedSpendPolicy(rung: number): ManagedSpendPolicy;
|
|
37
|
+
/**
|
|
38
|
+
* Units one turn costs at this rung: its worst case under the rung's own policy, in whole units.
|
|
39
|
+
*
|
|
40
|
+
* Prompt-inclusive on purpose. `maxCompletionTokens` says nothing about how large a prompt the agent
|
|
41
|
+
* loop may resend on each step, and the prompt is where an unbounded turn actually spends.
|
|
42
|
+
*/
|
|
43
|
+
export declare function managedSpendUnitWeight(rung: number): number;
|
|
44
|
+
/**
|
|
45
|
+
* The unit ledger for a day's worth of ordinary turns.
|
|
46
|
+
*
|
|
47
|
+
* Units are worst-case, so a turn is charged what its rung's policy *permits* rather than what it
|
|
48
|
+
* happened to use. That is deliberate — the alternative under-charges exactly the adversary the cap
|
|
49
|
+
* exists for — but it means the number is only meaningful denominated in turns. An allowance is
|
|
50
|
+
* therefore configured as "this many sponsored turns a day" and converted here, which is also the
|
|
51
|
+
* form the operator surface reports.
|
|
52
|
+
*
|
|
53
|
+
* The conservatism is uniform within a rung, so it does not distort when the ladder trips; across
|
|
54
|
+
* rungs it is the intended effect, since a degraded turn genuinely costs less and the same allowance
|
|
55
|
+
* stretches further.
|
|
56
|
+
*/
|
|
57
|
+
export declare function managedSpendAllowanceForTurns(turnsPerDay: number): number;
|
|
58
|
+
/**
|
|
59
|
+
* Is the ledger telling the truth?
|
|
60
|
+
*
|
|
61
|
+
* A unit weight is a claim about what a turn's own rung permits. If the rung's `requestPolicy` does
|
|
62
|
+
* not actually bound what it says, the claim is fiction and the whole cap is decorative — and the
|
|
63
|
+
* failure is silent, because a cheap-looking ledger is exactly what an unbounded turn produces.
|
|
64
|
+
*
|
|
65
|
+
* So telemetry checks the counter rather than replacing it. Observed billable tokens per delivered
|
|
66
|
+
* turn must stay under what rung 0 permits, since rung 0 is the most permissive rung any turn could
|
|
67
|
+
* have run at. Exceeding it is not a budgeting question; it means an enforcer is not enforcing.
|
|
68
|
+
*/
|
|
69
|
+
export declare function managedSpendDrift(observed: {
|
|
70
|
+
readonly deliveredTurns: number;
|
|
71
|
+
readonly promptTokens: number;
|
|
72
|
+
readonly completionTokens: number;
|
|
73
|
+
}): {
|
|
74
|
+
readonly observedPerTurn: number;
|
|
75
|
+
readonly permittedPerTurn: number;
|
|
76
|
+
readonly exceeded: boolean;
|
|
77
|
+
};
|
|
78
|
+
/** Convenience for readers that only care whether a tenant is being held back at all. */
|
|
79
|
+
export declare function isManagedSpendDegraded(envelope: Pick<AdmissionEnvelope, 'turnsPerDay'>): boolean;
|
|
80
|
+
//# sourceMappingURL=managed-spend.d.ts.map
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cost breakers for the sponsored managed-assistant beta (ADR 0213 amendment §3).
|
|
3
|
+
*
|
|
4
|
+
* Noodle pays for `noodle-managed` inference and charges nothing for it, so aggregate spend is a
|
|
5
|
+
* platform expense with no customer-side limit standing behind it. Per-surface daily caps bound one
|
|
6
|
+
* surface; they do not bound how many surfaces enrol. This is the missing ceiling, expressed as a
|
|
7
|
+
* ladder rather than a switch: a tenant approaching its allowance gets shorter answers before it gets
|
|
8
|
+
* a closed door, and the door itself is the kill switch that already exists.
|
|
9
|
+
*
|
|
10
|
+
* Two things make it small. Every rung is expressed through `SurfaceBudgetBounds` and
|
|
11
|
+
* `AssistantModelRequestPolicy`, which the resolver already returns and admission already reads live
|
|
12
|
+
* per turn — so degradation applies immediately and reverses on its own when the UTC day rolls, with
|
|
13
|
+
* no unwind path to write. And the unit is one delivered bounded turn, which is the billable unit ADR
|
|
14
|
+
* 0213 §12 already defines.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Billable tokens per charged unit. Prompt and completion are counted together and completion is
|
|
18
|
+
* weighted, because output costs several times input on every provider we have measured.
|
|
19
|
+
*/
|
|
20
|
+
export const MANAGED_SPEND_UNIT_TOKENS = 4_000;
|
|
21
|
+
const OUTPUT_WEIGHT = 8;
|
|
22
|
+
const BYTES_PER_TOKEN = 4;
|
|
23
|
+
/**
|
|
24
|
+
* The rungs, tightest-claim-first.
|
|
25
|
+
*
|
|
26
|
+
* Rung 0 is not "today's policy": a weight is only honest if the rung's own policy enforces it, and
|
|
27
|
+
* an unbounded prompt makes the worst case forty times the typical turn. Bounding the request keeps
|
|
28
|
+
* the ledger true. These numbers are the starting point and are expected to move once measured P99
|
|
29
|
+
* prompt sizes are read off the live surface — that measurement is a gate on raising the ceilings,
|
|
30
|
+
* not on shipping the breaker.
|
|
31
|
+
*/
|
|
32
|
+
const RUNGS = [
|
|
33
|
+
{
|
|
34
|
+
requestPolicy: {
|
|
35
|
+
maxRequestBytes: 32 * 1_024,
|
|
36
|
+
maxCompletionTokens: 1_500,
|
|
37
|
+
maxTokensPerTurn: 3_000,
|
|
38
|
+
maxModelStepsPerTurn: 6,
|
|
39
|
+
maxToolCallsPerTurn: 8,
|
|
40
|
+
},
|
|
41
|
+
admission: {
|
|
42
|
+
defaults: { turnsPerSession: 40, turnsPerDay: 20_000, mintsPerDay: 5_000 },
|
|
43
|
+
ceiling: { turnsPerDay: 20_000, mintsPerDay: 5_000 },
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
// Shorter answers. Nothing is announced: telling a stranger on a customer's marketing site that
|
|
48
|
+
// it is on a reduced tier harms the customer and helps no one, and a shorter answer explains
|
|
49
|
+
// itself.
|
|
50
|
+
requestPolicy: {
|
|
51
|
+
maxRequestBytes: 24 * 1_024,
|
|
52
|
+
maxCompletionTokens: 900,
|
|
53
|
+
maxTokensPerTurn: 1_800,
|
|
54
|
+
maxModelStepsPerTurn: 4,
|
|
55
|
+
maxToolCallsPerTurn: 6,
|
|
56
|
+
},
|
|
57
|
+
admission: {
|
|
58
|
+
defaults: { turnsPerSession: 40, turnsPerDay: 20_000, mintsPerDay: 5_000 },
|
|
59
|
+
ceiling: { turnsPerDay: 20_000, mintsPerDay: 5_000 },
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
// Shorter conversations. This does not lower a turn's worst case — `maxRequestBytes` already caps
|
|
64
|
+
// that — it lowers the typical prompt by cutting history growth, which stretches the remaining
|
|
65
|
+
// allowance without shortening any individual answer. The visitor meets
|
|
66
|
+
// `session_turn_budget_exhausted`, which the widget already renders as its existing "new
|
|
67
|
+
// conversation" affordance.
|
|
68
|
+
requestPolicy: {
|
|
69
|
+
maxRequestBytes: 16 * 1_024,
|
|
70
|
+
maxCompletionTokens: 700,
|
|
71
|
+
maxTokensPerTurn: 1_400,
|
|
72
|
+
maxModelStepsPerTurn: 3,
|
|
73
|
+
maxToolCallsPerTurn: 4,
|
|
74
|
+
},
|
|
75
|
+
admission: {
|
|
76
|
+
defaults: { turnsPerSession: 8, turnsPerDay: 20_000, mintsPerDay: 5_000 },
|
|
77
|
+
ceiling: { turnsPerDay: 20_000, mintsPerDay: 5_000 },
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
// No tools. `projectAssistantGuide` projects the per-turn tool set, so the guide never promises a
|
|
82
|
+
// tool that is not offered; the assistant simply answers from knowledge and its own words.
|
|
83
|
+
requestPolicy: {
|
|
84
|
+
maxRequestBytes: 12 * 1_024,
|
|
85
|
+
maxCompletionTokens: 400,
|
|
86
|
+
maxTokensPerTurn: 800,
|
|
87
|
+
maxModelStepsPerTurn: 1,
|
|
88
|
+
maxToolCallsPerTurn: 0,
|
|
89
|
+
},
|
|
90
|
+
admission: {
|
|
91
|
+
defaults: { turnsPerSession: 8, turnsPerDay: 20_000, mintsPerDay: 5_000 },
|
|
92
|
+
ceiling: { turnsPerDay: 20_000, mintsPerDay: 5_000 },
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
// At cap. Deliberately the *existing* kill switch and not a new failure mode: zero ceilings refuse
|
|
97
|
+
// at admission with `daily_turn_budget_exhausted`, a code every published widget already treats as
|
|
98
|
+
// final. A new code would be read as retryable by every widget predating it, and they would hammer
|
|
99
|
+
// the endpoint this cap exists to protect.
|
|
100
|
+
requestPolicy: {
|
|
101
|
+
maxRequestBytes: 12 * 1_024,
|
|
102
|
+
maxCompletionTokens: 400,
|
|
103
|
+
maxTokensPerTurn: 800,
|
|
104
|
+
maxModelStepsPerTurn: 1,
|
|
105
|
+
maxToolCallsPerTurn: 0,
|
|
106
|
+
},
|
|
107
|
+
admission: {
|
|
108
|
+
defaults: { turnsPerSession: 8, turnsPerDay: 0, mintsPerDay: 0 },
|
|
109
|
+
ceiling: { turnsPerDay: 0, mintsPerDay: 0 },
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
];
|
|
113
|
+
/** Fractions of the allowance at which each rung begins. */
|
|
114
|
+
const THRESHOLDS = [0.6, 0.8, 0.95, 1];
|
|
115
|
+
/**
|
|
116
|
+
* Which rung a tenant is on.
|
|
117
|
+
*
|
|
118
|
+
* An absent or nonsensical allowance means "unbudgeted", never "exhausted". Failing closed on a typo
|
|
119
|
+
* would take a customer's surface dark for a configuration mistake, and the per-surface daily cap
|
|
120
|
+
* still bounds what that costs.
|
|
121
|
+
*/
|
|
122
|
+
export function managedSpendRung(unitsUsed, allowance) {
|
|
123
|
+
if (!Number.isFinite(allowance) || allowance <= 0)
|
|
124
|
+
return 0;
|
|
125
|
+
const share = Math.max(0, unitsUsed) / allowance;
|
|
126
|
+
return THRESHOLDS.filter((threshold) => share >= threshold).length;
|
|
127
|
+
}
|
|
128
|
+
export function managedSpendPolicy(rung) {
|
|
129
|
+
return RUNGS[Math.min(Math.max(Math.trunc(rung), 0), RUNGS.length - 1)];
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Units one turn costs at this rung: its worst case under the rung's own policy, in whole units.
|
|
133
|
+
*
|
|
134
|
+
* Prompt-inclusive on purpose. `maxCompletionTokens` says nothing about how large a prompt the agent
|
|
135
|
+
* loop may resend on each step, and the prompt is where an unbounded turn actually spends.
|
|
136
|
+
*/
|
|
137
|
+
export function managedSpendUnitWeight(rung) {
|
|
138
|
+
const { requestPolicy } = managedSpendPolicy(rung);
|
|
139
|
+
const prompt = ((requestPolicy.maxModelStepsPerTurn ?? 1) * (requestPolicy.maxRequestBytes ?? 0)) /
|
|
140
|
+
BYTES_PER_TOKEN;
|
|
141
|
+
const output = OUTPUT_WEIGHT * (requestPolicy.maxTokensPerTurn ?? 0);
|
|
142
|
+
return Math.max(1, Math.ceil((prompt + output) / MANAGED_SPEND_UNIT_TOKENS));
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* The unit ledger for a day's worth of ordinary turns.
|
|
146
|
+
*
|
|
147
|
+
* Units are worst-case, so a turn is charged what its rung's policy *permits* rather than what it
|
|
148
|
+
* happened to use. That is deliberate — the alternative under-charges exactly the adversary the cap
|
|
149
|
+
* exists for — but it means the number is only meaningful denominated in turns. An allowance is
|
|
150
|
+
* therefore configured as "this many sponsored turns a day" and converted here, which is also the
|
|
151
|
+
* form the operator surface reports.
|
|
152
|
+
*
|
|
153
|
+
* The conservatism is uniform within a rung, so it does not distort when the ladder trips; across
|
|
154
|
+
* rungs it is the intended effect, since a degraded turn genuinely costs less and the same allowance
|
|
155
|
+
* stretches further.
|
|
156
|
+
*/
|
|
157
|
+
export function managedSpendAllowanceForTurns(turnsPerDay) {
|
|
158
|
+
if (!Number.isFinite(turnsPerDay) || turnsPerDay <= 0)
|
|
159
|
+
return 0;
|
|
160
|
+
return Math.ceil(turnsPerDay) * managedSpendUnitWeight(0);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Is the ledger telling the truth?
|
|
164
|
+
*
|
|
165
|
+
* A unit weight is a claim about what a turn's own rung permits. If the rung's `requestPolicy` does
|
|
166
|
+
* not actually bound what it says, the claim is fiction and the whole cap is decorative — and the
|
|
167
|
+
* failure is silent, because a cheap-looking ledger is exactly what an unbounded turn produces.
|
|
168
|
+
*
|
|
169
|
+
* So telemetry checks the counter rather than replacing it. Observed billable tokens per delivered
|
|
170
|
+
* turn must stay under what rung 0 permits, since rung 0 is the most permissive rung any turn could
|
|
171
|
+
* have run at. Exceeding it is not a budgeting question; it means an enforcer is not enforcing.
|
|
172
|
+
*/
|
|
173
|
+
export function managedSpendDrift(observed) {
|
|
174
|
+
const permittedPerTurn = managedSpendUnitWeight(0) * MANAGED_SPEND_UNIT_TOKENS;
|
|
175
|
+
if (observed.deliveredTurns <= 0) {
|
|
176
|
+
return { observedPerTurn: 0, permittedPerTurn, exceeded: false };
|
|
177
|
+
}
|
|
178
|
+
const billable = observed.promptTokens + OUTPUT_WEIGHT * observed.completionTokens;
|
|
179
|
+
const observedPerTurn = Math.round(billable / observed.deliveredTurns);
|
|
180
|
+
return { observedPerTurn, permittedPerTurn, exceeded: observedPerTurn > permittedPerTurn };
|
|
181
|
+
}
|
|
182
|
+
/** Convenience for readers that only care whether a tenant is being held back at all. */
|
|
183
|
+
export function isManagedSpendDegraded(envelope) {
|
|
184
|
+
return envelope.turnsPerDay === 0;
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=managed-spend.js.map
|
|
@@ -5,6 +5,12 @@ export interface AssistantModelRequestPolicy {
|
|
|
5
5
|
readonly maxCompletionTokens?: number;
|
|
6
6
|
readonly maxTokensPerTurn?: number;
|
|
7
7
|
readonly maxRequestBytes?: number;
|
|
8
|
+
/**
|
|
9
|
+
* Per-tenant agent-loop bounds. The deployment-wide admission envelope still wins whenever it is
|
|
10
|
+
* lower — these narrow a turn for one tenant, they never widen it for anyone.
|
|
11
|
+
*/
|
|
12
|
+
readonly maxModelStepsPerTurn?: number;
|
|
13
|
+
readonly maxToolCallsPerTurn?: number;
|
|
8
14
|
readonly timeoutMs?: number;
|
|
9
15
|
readonly maxTurnMs?: number;
|
|
10
16
|
/** Trusted operator-selected OpenAI-compatible request extensions. */
|
|
@@ -26,6 +32,16 @@ interface ResolvedAssistantModelBase {
|
|
|
26
32
|
readonly turnsPerDay: number;
|
|
27
33
|
readonly mintsPerDay: number;
|
|
28
34
|
};
|
|
35
|
+
/**
|
|
36
|
+
* Platform spend accounting, present only when someone other than the customer is paying. Passed
|
|
37
|
+
* through to `SurfaceBudgetBounds` unchanged, which is why the shape is declared identically
|
|
38
|
+
* here rather than imported — the two contracts stay independent by design.
|
|
39
|
+
*/
|
|
40
|
+
readonly spend?: {
|
|
41
|
+
readonly key: string;
|
|
42
|
+
readonly units: number;
|
|
43
|
+
readonly allowance: number;
|
|
44
|
+
};
|
|
29
45
|
};
|
|
30
46
|
}
|
|
31
47
|
export type ResolvedAssistantModel = ResolvedAssistantModelBase & ({
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** Assistant gateway contracts and in-memory adapters safe for the published CLI runtime. */
|
|
2
|
+
export * from './app-tool-call.js';
|
|
2
3
|
export * from './artifact-projection.js';
|
|
3
4
|
export * from './assistant-appearance.js';
|
|
4
5
|
export * from './assistant-appearance-store.js';
|
|
@@ -17,11 +18,13 @@ export * from './assistant-transcript.js';
|
|
|
17
18
|
export * from './assistant-view-availability.js';
|
|
18
19
|
export * from './elevation.js';
|
|
19
20
|
export * from './elevation-store.js';
|
|
21
|
+
export * from './embed-operator-view.js';
|
|
20
22
|
export * from './embed-script.js';
|
|
21
23
|
export * from './embed-store.js';
|
|
22
24
|
export * from './in-memory-assistant-appearance-store.js';
|
|
23
25
|
export * from './in-memory-elevation-store.js';
|
|
24
26
|
export * from './in-memory-embed-store.js';
|
|
27
|
+
export * from './managed-spend.js';
|
|
25
28
|
export * from './public-configuration.js';
|
|
26
29
|
export * from './public-session.js';
|
|
27
30
|
export * from './public-surface.js';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** Assistant gateway contracts and in-memory adapters safe for the published CLI runtime. */
|
|
2
|
+
export * from './app-tool-call.js';
|
|
2
3
|
export * from './artifact-projection.js';
|
|
3
4
|
export * from './assistant-appearance.js';
|
|
4
5
|
export * from './assistant-appearance-store.js';
|
|
@@ -17,11 +18,13 @@ export * from './assistant-transcript.js';
|
|
|
17
18
|
export * from './assistant-view-availability.js';
|
|
18
19
|
export * from './elevation.js';
|
|
19
20
|
export * from './elevation-store.js';
|
|
21
|
+
export * from './embed-operator-view.js';
|
|
20
22
|
export * from './embed-script.js';
|
|
21
23
|
export * from './embed-store.js';
|
|
22
24
|
export * from './in-memory-assistant-appearance-store.js';
|
|
23
25
|
export * from './in-memory-elevation-store.js';
|
|
24
26
|
export * from './in-memory-embed-store.js';
|
|
27
|
+
export * from './managed-spend.js';
|
|
25
28
|
export * from './public-configuration.js';
|
|
26
29
|
export * from './public-session.js';
|
|
27
30
|
export * from './public-surface.js';
|
|
@@ -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,
|
|
@@ -125,6 +125,28 @@ export async function admitPublicTurn(request, defaults, ports) {
|
|
|
125
125
|
message: 'assistant is unavailable right now',
|
|
126
126
|
};
|
|
127
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
|
+
}
|
|
128
150
|
return { ok: true, turnCount: session.turnCount };
|
|
129
151
|
}
|
|
130
152
|
export async function admitBridgeToolCall(request, defaults, ports) {
|
|
@@ -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' | 'bridgeToolCallsPerSession' | 'bridgeToolCallsPerDay'>, bounds?: SurfaceBudgetBounds): AdmissionEnvelope;
|
|
21
35
|
//# sourceMappingURL=surface-budget.d.ts.map
|
|
@@ -17,6 +17,30 @@ 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
|
+
: {}),
|
|
34
|
+
// The bridge bounds are the operator's to spend in either direction, like the daily caps and
|
|
35
|
+
// unlike the per-address ones: they govern this surface's own agent traffic against its own
|
|
36
|
+
// connectors, not a bound shared with everyone behind one address. `clamp` still holds them
|
|
37
|
+
// under the structural maximum.
|
|
38
|
+
...(embed.bridgeToolCallsPerSession !== undefined
|
|
39
|
+
? { bridgeToolCallsPerSession: embed.bridgeToolCallsPerSession }
|
|
40
|
+
: {}),
|
|
41
|
+
...(embed.bridgeToolCallsPerDay !== undefined
|
|
42
|
+
? { bridgeToolCallsPerDay: embed.bridgeToolCallsPerDay }
|
|
43
|
+
: {}),
|
|
20
44
|
});
|
|
21
45
|
if (bounds === undefined)
|
|
22
46
|
return requested;
|
|
@@ -168,12 +168,20 @@ export interface AuthenticatedWebsiteAccess {
|
|
|
168
168
|
/** Allowlist of verified session claims; undeclared claims are dropped at session exchange. */
|
|
169
169
|
readonly sessionClaims?: Readonly<Record<string, SessionClaimDeclaration>>;
|
|
170
170
|
readonly capabilities?: readonly CapabilityRef[];
|
|
171
|
+
/** @see AuthenticatedWebsiteInput.webmcp */
|
|
172
|
+
readonly webmcp?: {
|
|
173
|
+
readonly enabled?: boolean;
|
|
174
|
+
};
|
|
171
175
|
}
|
|
172
176
|
export interface PublicWebsiteAccess {
|
|
173
177
|
readonly mode: 'public' | 'mixed';
|
|
174
178
|
readonly origins: readonly string[];
|
|
175
179
|
readonly capabilities: readonly CapabilityRef[];
|
|
176
180
|
readonly instructions?: string;
|
|
181
|
+
/** @see AuthenticatedWebsiteInput.webmcp */
|
|
182
|
+
readonly webmcp?: {
|
|
183
|
+
readonly enabled?: boolean;
|
|
184
|
+
};
|
|
177
185
|
}
|
|
178
186
|
export type AssistantAccess = AuthenticatedWebsiteAccess | PublicWebsiteAccess;
|
|
179
187
|
export interface AuthenticatedWebsiteInput {
|
|
@@ -183,6 +191,19 @@ export interface AuthenticatedWebsiteInput {
|
|
|
183
191
|
readonly sessionClaims?: Readonly<Record<string, SessionClaimDeclaration>>;
|
|
184
192
|
/** Optional narrowing; omitted means the signed-in surface projects the whole server. */
|
|
185
193
|
readonly capabilities?: readonly CapabilityRef[];
|
|
194
|
+
/**
|
|
195
|
+
* Override this assistant's WebMCP opt-in for sessions minted on this surface, in either
|
|
196
|
+
* direction (ADR 0220, amended). Omitted inherits the assistant's value.
|
|
197
|
+
*
|
|
198
|
+
* It governs **discovery**: whether the embed registers this session's tools with
|
|
199
|
+
* `document.modelContext`, and so whether a browser agent learns they exist. It is not a second
|
|
200
|
+
* authorization boundary — every call the bridge makes already carries exactly this session's
|
|
201
|
+
* authority. A deployment serving a marketing page and a signed-in app usually has two honest
|
|
202
|
+
* answers, which one switch cannot express.
|
|
203
|
+
*/
|
|
204
|
+
readonly webmcp?: {
|
|
205
|
+
readonly enabled?: boolean;
|
|
206
|
+
};
|
|
186
207
|
}
|
|
187
208
|
export interface PublicWebsiteInput {
|
|
188
209
|
readonly origins: readonly (string | ConfigRef)[];
|
|
@@ -201,6 +222,10 @@ export interface PublicWebsiteInput {
|
|
|
201
222
|
* never operates one — and its backend completes the exchange.
|
|
202
223
|
*/
|
|
203
224
|
readonly signIn?: boolean;
|
|
225
|
+
/** @see AuthenticatedWebsiteInput.webmcp */
|
|
226
|
+
readonly webmcp?: {
|
|
227
|
+
readonly enabled?: boolean;
|
|
228
|
+
};
|
|
204
229
|
}
|
|
205
230
|
export declare function authenticatedWebsite(input: AuthenticatedWebsiteInput): AuthenticatedWebsiteAccess;
|
|
206
231
|
export declare function publicWebsite(input: PublicWebsiteInput): PublicWebsiteAccess;
|
|
@@ -213,8 +238,7 @@ export interface EmbeddedAssistantOptions extends AssistantUiOptions {
|
|
|
213
238
|
* page's WebMCP API. Off unless set. Calls carry exactly the embed session's authority and take the
|
|
214
239
|
* same authorization, limits, confirmation, and audit path as the assistant's own — see ADR 0220.
|
|
215
240
|
*
|
|
216
|
-
*
|
|
217
|
-
* surface authority rather than presentation, so it is not refinable per surface.
|
|
241
|
+
* The default for every surface, which any surface may override in either direction.
|
|
218
242
|
*/
|
|
219
243
|
readonly webmcp?: {
|
|
220
244
|
readonly enabled?: boolean;
|
|
@@ -227,6 +251,10 @@ export interface AssistantSurfaceConfig {
|
|
|
227
251
|
readonly instructions?: string;
|
|
228
252
|
readonly capabilities?: readonly AssistantCapability[];
|
|
229
253
|
readonly sessionClaims?: Readonly<Record<string, SessionClaimDeclaration>>;
|
|
254
|
+
/** @see AuthenticatedWebsiteInput.webmcp */
|
|
255
|
+
readonly webmcp?: {
|
|
256
|
+
readonly enabled?: boolean;
|
|
257
|
+
};
|
|
230
258
|
}
|
|
231
259
|
export interface EmbeddedAssistantConfig extends AssistantUiOptions {
|
|
232
260
|
readonly model: AssistantModel;
|
|
@@ -238,7 +266,7 @@ export interface EmbeddedAssistantConfig extends AssistantUiOptions {
|
|
|
238
266
|
readonly allowedOrigins: readonly string[];
|
|
239
267
|
/** Mirrored from the authenticated surface, for the same reason. */
|
|
240
268
|
readonly sessionClaims?: Readonly<Record<string, SessionClaimDeclaration>>;
|
|
241
|
-
/** @see EmbeddedAssistantOptions.webmcp —
|
|
269
|
+
/** @see EmbeddedAssistantOptions.webmcp — the default each surface may override. */
|
|
242
270
|
readonly webmcp?: {
|
|
243
271
|
readonly enabled?: boolean;
|
|
244
272
|
};
|
|
@@ -6,6 +6,7 @@ export function authenticatedWebsite(input) {
|
|
|
6
6
|
...(input.instructions === undefined ? {} : { instructions: input.instructions }),
|
|
7
7
|
...(input.sessionClaims ? { sessionClaims: structuredClone(input.sessionClaims) } : {}),
|
|
8
8
|
...(input.capabilities ? { capabilities: [...input.capabilities] } : {}),
|
|
9
|
+
...(input.webmcp === undefined ? {} : { webmcp: { ...input.webmcp } }),
|
|
9
10
|
};
|
|
10
11
|
}
|
|
11
12
|
export function publicWebsite(input) {
|
|
@@ -14,6 +15,7 @@ export function publicWebsite(input) {
|
|
|
14
15
|
origins: serializeOrigins(input.origins, 'assistant.access.origins'),
|
|
15
16
|
capabilities: [...input.capabilities],
|
|
16
17
|
...(input.instructions === undefined ? {} : { instructions: input.instructions }),
|
|
18
|
+
...(input.webmcp === undefined ? {} : { webmcp: { ...input.webmcp } }),
|
|
17
19
|
};
|
|
18
20
|
}
|
|
19
21
|
function serializeOrigins(origins, path) {
|
|
@@ -56,6 +58,7 @@ export function embeddedAssistant(input) {
|
|
|
56
58
|
...(surface.mode === 'authenticated' && surface.sessionClaims
|
|
57
59
|
? { sessionClaims: structuredClone(surface.sessionClaims) }
|
|
58
60
|
: {}),
|
|
61
|
+
...(surface.webmcp === undefined ? {} : { webmcp: { ...surface.webmcp } }),
|
|
59
62
|
})),
|
|
60
63
|
allowedOrigins: surfaces.flatMap((surface) => [...surface.origins]),
|
|
61
64
|
...structuredClone(ui),
|