@noodleseed/one 0.149.0 → 0.151.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 +4 -1
- package/dist/commands/assistant-ops.js.map +1 -1
- package/dist/commands/assistant-sponsorship-ops.d.ts +3 -0
- package/dist/commands/assistant-sponsorship-ops.d.ts.map +1 -0
- package/dist/commands/assistant-sponsorship-ops.js +170 -0
- package/dist/commands/assistant-sponsorship-ops.js.map +1 -0
- package/dist/commands/assistant-surface-ops.d.ts +3 -0
- package/dist/commands/assistant-surface-ops.d.ts.map +1 -1
- package/dist/commands/assistant-surface-ops.js +9 -2
- package/dist/commands/assistant-surface-ops.js.map +1 -1
- package/node_modules/@noodle-borg/admission-limits/dist/counter-store.d.ts +11 -0
- package/node_modules/@noodle-borg/admission-limits/dist/envelope.js +12 -6
- package/node_modules/@noodle-borg/admission-limits/dist/in-memory-counter-store.d.ts +3 -2
- package/node_modules/@noodle-borg/admission-limits/dist/in-memory-counter-store.js +22 -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 +2 -2
- 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 +48 -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/embed-operator-view.d.ts +8 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/embed-operator-view.js +9 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/embed-store.d.ts +11 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/embed-store.js +2 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/in-memory-embed-store.js +6 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/model-error.js +6 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/model-request.d.ts +11 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/model-request.js +22 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/portable.d.ts +1 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/portable.js +1 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/surface-budget.d.ts +1 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/surface-budget.js +10 -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 +108 -0
- 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/index.d.ts +1 -0
- package/node_modules/@noodle-borg/module/dist/index.js +1 -0
- package/node_modules/@noodle-borg/module/dist/logger.d.ts +15 -0
- package/node_modules/@noodle-borg/module/dist/logger.js +16 -0
- package/node_modules/@noodle-borg/observability/dist/assistant-app-tool-usage.js +47 -0
- package/node_modules/@noodle-borg/observability/dist/index.js +1 -0
- package/node_modules/@noodle-borg/service/dist/invocation-context.js +1 -53
- package/node_modules/@noodle-borg/service/dist/routes/assistant-interaction-resolution.js +1 -2
- 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 +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/assistant-suggestions.js +1 -2
- package/node_modules/@noodle-borg/service/dist/routes/assistant.js +63 -44
- package/node_modules/@noodle-borg/service/package.json +1 -1
- package/node_modules/@noodle-borg/wire-contracts/dist/index.d.ts +1 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/index.js +1 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/managed-assistant-sponsorship.d.ts +297 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/managed-assistant-sponsorship.js +94 -0
- package/node_modules/@noodleseed/assistant/package.json +1 -1
- package/package.json +2 -2
|
@@ -15,10 +15,18 @@ export interface AssistantEmbedOperatorView {
|
|
|
15
15
|
readonly capabilities: readonly string[];
|
|
16
16
|
readonly turnsPerDay: number;
|
|
17
17
|
readonly mintsPerDay: number;
|
|
18
|
+
/**
|
|
19
|
+
* What a browser agent may spend on this surface through the WebMCP bridge. Reported beside the
|
|
20
|
+
* turn caps because "is my budget about to run out" is the same question, and a bridge call spends
|
|
21
|
+
* neither a turn nor a mint — an operator reading only those two would see a quiet day.
|
|
22
|
+
*/
|
|
23
|
+
readonly bridgeCallsPerSession: number;
|
|
24
|
+
readonly bridgeCallsPerDay: number;
|
|
18
25
|
/** True when the caps are the deployment defaults rather than something an operator chose. */
|
|
19
26
|
readonly budgetIsDefault: boolean;
|
|
20
27
|
readonly turnsToday: number;
|
|
21
28
|
readonly mintsToday: number;
|
|
29
|
+
readonly bridgeCallsToday: number;
|
|
22
30
|
readonly createdAt: string;
|
|
23
31
|
/**
|
|
24
32
|
* Present only when Noodle is funding this surface's model. It exists so an operator can tell
|
|
@@ -42,9 +42,17 @@ export async function assistantEmbedOperatorView(input) {
|
|
|
42
42
|
capabilities: surface?.capabilities.map((entry) => entry.name) ?? [],
|
|
43
43
|
turnsPerDay: envelope.turnsPerDay,
|
|
44
44
|
mintsPerDay: envelope.mintsPerDay,
|
|
45
|
-
|
|
45
|
+
bridgeCallsPerSession: envelope.bridgeToolCallsPerSession,
|
|
46
|
+
bridgeCallsPerDay: envelope.bridgeToolCallsPerDay,
|
|
47
|
+
budgetIsDefault: record.turnsPerDay === undefined &&
|
|
48
|
+
record.mintsPerDay === undefined &&
|
|
49
|
+
record.bridgeToolCallsPerSession === undefined &&
|
|
50
|
+
record.bridgeToolCallsPerDay === undefined,
|
|
46
51
|
turnsToday: await input.peek(`turns:${record.embedId}`),
|
|
47
52
|
mintsToday: await input.peek(`mints:${record.embedId}`),
|
|
53
|
+
// Keyed as `admitBridgeToolCall` writes it, so the number an operator reads is the one the
|
|
54
|
+
// gate consumed rather than a parallel count that can drift from it.
|
|
55
|
+
bridgeCallsToday: await input.peek(`bridge:${record.embedId}`),
|
|
48
56
|
createdAt: record.createdAt.toISOString(),
|
|
49
57
|
...(spend === undefined
|
|
50
58
|
? {}
|
|
@@ -38,6 +38,14 @@ export interface PublicEmbedRecord {
|
|
|
38
38
|
*/
|
|
39
39
|
readonly mintsPerAddressHour?: number;
|
|
40
40
|
readonly turnsPerAddressHour?: number;
|
|
41
|
+
/**
|
|
42
|
+
* Operator overrides for this surface's WebMCP bridge budgets. The shipped defaults are
|
|
43
|
+
* proportionate rather than measured (ADR 0220), so an operator who can see their own agent
|
|
44
|
+
* traffic needs to move them without waiting for a release. `0` is the kill switch here too,
|
|
45
|
+
* though zeroing `turnsPerDay` already stops bridge calls with it.
|
|
46
|
+
*/
|
|
47
|
+
readonly bridgeToolCallsPerSession?: number;
|
|
48
|
+
readonly bridgeToolCallsPerDay?: number;
|
|
41
49
|
}
|
|
42
50
|
/** Project the non-secret embed coordinate onto the shared assistant tenant shape. */
|
|
43
51
|
export declare function publicEmbedTenant(embed: PublicEmbedRecord): TenantRef;
|
|
@@ -47,6 +55,8 @@ export interface PublicEmbedBudget {
|
|
|
47
55
|
readonly mintsPerDay?: number;
|
|
48
56
|
readonly mintsPerAddressHour?: number;
|
|
49
57
|
readonly turnsPerAddressHour?: number;
|
|
58
|
+
readonly bridgeToolCallsPerSession?: number;
|
|
59
|
+
readonly bridgeToolCallsPerDay?: number;
|
|
50
60
|
}
|
|
51
61
|
export interface PublicEmbedStore {
|
|
52
62
|
/**
|
|
@@ -89,7 +99,7 @@ import type { TenantRef } from './tenant-ref.js';
|
|
|
89
99
|
* cap may be, not about HTTP, and a second caller must not be able to reach the store with a shape
|
|
90
100
|
* this would have rejected.
|
|
91
101
|
*/
|
|
92
|
-
export type BudgetField = 'turnsPerDay' | 'mintsPerDay' | 'mintsPerAddressHour' | 'turnsPerAddressHour';
|
|
102
|
+
export type BudgetField = 'turnsPerDay' | 'mintsPerDay' | 'mintsPerAddressHour' | 'turnsPerAddressHour' | 'bridgeToolCallsPerSession' | 'bridgeToolCallsPerDay';
|
|
93
103
|
type BudgetValue = Partial<Record<BudgetField, number>>;
|
|
94
104
|
type ParsedBudget = {
|
|
95
105
|
readonly ok: true;
|
|
@@ -28,6 +28,8 @@ const BUDGET_FIELDS = [
|
|
|
28
28
|
'mintsPerDay',
|
|
29
29
|
'mintsPerAddressHour',
|
|
30
30
|
'turnsPerAddressHour',
|
|
31
|
+
'bridgeToolCallsPerSession',
|
|
32
|
+
'bridgeToolCallsPerDay',
|
|
31
33
|
];
|
|
32
34
|
export function parsePublicEmbedBudget(body) {
|
|
33
35
|
// A JSON body of `null` is an object to `typeof` and reads as a crash to a property access, so it
|
|
@@ -45,6 +45,12 @@ export class InMemoryPublicEmbedStore {
|
|
|
45
45
|
...(budget.turnsPerAddressHour !== undefined
|
|
46
46
|
? { turnsPerAddressHour: budget.turnsPerAddressHour }
|
|
47
47
|
: {}),
|
|
48
|
+
...(budget.bridgeToolCallsPerSession !== undefined
|
|
49
|
+
? { bridgeToolCallsPerSession: budget.bridgeToolCallsPerSession }
|
|
50
|
+
: {}),
|
|
51
|
+
...(budget.bridgeToolCallsPerDay !== undefined
|
|
52
|
+
? { bridgeToolCallsPerDay: budget.bridgeToolCallsPerDay }
|
|
53
|
+
: {}),
|
|
48
54
|
};
|
|
49
55
|
this.#byId.set(embedId, updated);
|
|
50
56
|
return updated;
|
|
@@ -52,6 +52,12 @@ export function modelFetchError(cause) {
|
|
|
52
52
|
export function modelResponseError(cause) {
|
|
53
53
|
return new AssistantModelError('model_response_invalid', { retryable: false, cause });
|
|
54
54
|
}
|
|
55
|
+
export function managedSponsorshipExhausted() {
|
|
56
|
+
return new AssistantModelError('daily_turn_budget_exhausted', { retryable: false });
|
|
57
|
+
}
|
|
58
|
+
export function managedSponsorshipUnavailable(cause) {
|
|
59
|
+
return new AssistantModelError('model_unavailable', { retryable: true, cause });
|
|
60
|
+
}
|
|
55
61
|
function isTimeoutError(error) {
|
|
56
62
|
return error instanceof Error && error.name === 'TimeoutError';
|
|
57
63
|
}
|
|
@@ -22,6 +22,17 @@ interface ResolvedAssistantModelBase {
|
|
|
22
22
|
readonly baseUrl: string;
|
|
23
23
|
readonly model: string;
|
|
24
24
|
readonly requestPolicy?: AssistantModelRequestPolicy;
|
|
25
|
+
/**
|
|
26
|
+
* Private hosted sponsorship admission. It is deliberately attached to the resolved binding so
|
|
27
|
+
* every path that reaches provider I/O passes the same gate, including suggestions, interaction
|
|
28
|
+
* narration, authenticated assistants, and doctor probes.
|
|
29
|
+
*/
|
|
30
|
+
readonly sponsorship?: {
|
|
31
|
+
readonly accountKey: string;
|
|
32
|
+
readonly allowance: number;
|
|
33
|
+
readonly units: number;
|
|
34
|
+
admit(): Promise<boolean>;
|
|
35
|
+
};
|
|
25
36
|
readonly publicAdmission?: {
|
|
26
37
|
readonly defaults: {
|
|
27
38
|
readonly turnsPerSession: number;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AssistantModelError, modelFetchError, modelHttpError, modelRequestError, modelResponseError, } from './model-error.js';
|
|
1
|
+
import { AssistantModelError, managedSponsorshipExhausted, managedSponsorshipUnavailable, modelFetchError, modelHttpError, modelRequestError, modelResponseError, } from './model-error.js';
|
|
2
2
|
import { readResponsesCompletion, responsesInput, responsesTools } from './model-responses.js';
|
|
3
3
|
import { readModelCompletion } from './model-stream.js';
|
|
4
4
|
export async function requestModelCompletion(input) {
|
|
@@ -34,6 +34,7 @@ export async function requestModelCompletion(input) {
|
|
|
34
34
|
if (maxRequestBytes !== undefined && requestBytes > maxRequestBytes) {
|
|
35
35
|
throw modelRequestError('model request too large');
|
|
36
36
|
}
|
|
37
|
+
await admitSponsorship(binding);
|
|
37
38
|
const bearerToken = binding.apiKey ?? (await binding.bearerToken());
|
|
38
39
|
if (!/^[^\s]{1,8192}$/.test(bearerToken)) {
|
|
39
40
|
throw modelRequestError('invalid model bearer token');
|
|
@@ -63,4 +64,24 @@ export async function requestModelCompletion(input) {
|
|
|
63
64
|
throw modelResponseError(error);
|
|
64
65
|
}
|
|
65
66
|
}
|
|
67
|
+
/** One resolved binding represents one bounded model operation, even when its agent loop has steps. */
|
|
68
|
+
const sponsorshipAdmissions = new WeakMap();
|
|
69
|
+
async function admitSponsorship(binding) {
|
|
70
|
+
if (binding.source !== 'noodle-managed' || binding.sponsorship === undefined)
|
|
71
|
+
return;
|
|
72
|
+
let admission = sponsorshipAdmissions.get(binding);
|
|
73
|
+
if (admission === undefined) {
|
|
74
|
+
admission = binding.sponsorship.admit();
|
|
75
|
+
sponsorshipAdmissions.set(binding, admission);
|
|
76
|
+
}
|
|
77
|
+
let allowed;
|
|
78
|
+
try {
|
|
79
|
+
allowed = await admission;
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
throw managedSponsorshipUnavailable(error);
|
|
83
|
+
}
|
|
84
|
+
if (!allowed)
|
|
85
|
+
throw managedSponsorshipExhausted();
|
|
86
|
+
}
|
|
66
87
|
//# sourceMappingURL=model-request.js.map
|
|
@@ -31,5 +31,5 @@ export interface SurfaceBudgetBounds {
|
|
|
31
31
|
* operator any more than by app code, so an over-eager `--turns-per-day 999999999` degrades to the
|
|
32
32
|
* ceiling instead of becoming the ceiling. Zero survives clamping and is the kill switch.
|
|
33
33
|
*/
|
|
34
|
-
export declare function surfaceEnvelope(base: AdmissionEnvelope, embed: Pick<PublicEmbedRecord, 'turnsPerDay' | 'mintsPerDay' | 'mintsPerAddressHour' | 'turnsPerAddressHour'>, bounds?: SurfaceBudgetBounds): AdmissionEnvelope;
|
|
34
|
+
export declare function surfaceEnvelope(base: AdmissionEnvelope, embed: Pick<PublicEmbedRecord, 'turnsPerDay' | 'mintsPerDay' | 'mintsPerAddressHour' | 'turnsPerAddressHour' | 'bridgeToolCallsPerSession' | 'bridgeToolCallsPerDay'>, bounds?: SurfaceBudgetBounds): AdmissionEnvelope;
|
|
35
35
|
//# sourceMappingURL=surface-budget.d.ts.map
|
|
@@ -31,6 +31,16 @@ export function surfaceEnvelope(base, embed, bounds) {
|
|
|
31
31
|
turnsPerAddressHour: Math.min(embed.turnsPerAddressHour, effectiveBase.turnsPerAddressHour),
|
|
32
32
|
}
|
|
33
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
|
+
: {}),
|
|
34
44
|
});
|
|
35
45
|
if (bounds === undefined)
|
|
36
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),
|
|
@@ -129,6 +129,20 @@ const BUDGET_SUBCOMMAND = subcommand({
|
|
|
129
129
|
value: '<count>',
|
|
130
130
|
summary: 'Model turns one source address may run per hour. The abuse bound, not fairness.',
|
|
131
131
|
}),
|
|
132
|
+
flag({
|
|
133
|
+
...FLAG_FIELDS,
|
|
134
|
+
name: 'bridge-calls-per-session',
|
|
135
|
+
type: 'string',
|
|
136
|
+
value: '<count>',
|
|
137
|
+
summary: 'Tool calls a browser agent may make in one session over the WebMCP bridge.',
|
|
138
|
+
}),
|
|
139
|
+
flag({
|
|
140
|
+
...FLAG_FIELDS,
|
|
141
|
+
name: 'bridge-calls-per-day',
|
|
142
|
+
type: 'string',
|
|
143
|
+
value: '<count>',
|
|
144
|
+
summary: 'Bridge tool calls this surface may serve per UTC day. 0 closes the bridge.',
|
|
145
|
+
}),
|
|
132
146
|
flag({
|
|
133
147
|
...FLAG_FIELDS,
|
|
134
148
|
name: 'surface',
|
|
@@ -140,6 +154,99 @@ const BUDGET_SUBCOMMAND = subcommand({
|
|
|
140
154
|
}),
|
|
141
155
|
],
|
|
142
156
|
});
|
|
157
|
+
const SPONSORSHIP_ACCOUNT_ARGUMENT = argument({
|
|
158
|
+
...ARGUMENT_FIELDS,
|
|
159
|
+
name: 'billing-account-id',
|
|
160
|
+
type: 'string',
|
|
161
|
+
summary: 'Billing account receiving the sponsored allowance.',
|
|
162
|
+
required: true,
|
|
163
|
+
});
|
|
164
|
+
const SPONSORSHIP_FLAGS = [SERVICE_FLAG, AUTH_TOKEN_FLAG, JSON_FLAG];
|
|
165
|
+
const SPONSORSHIP_REASON_FLAG = flag({
|
|
166
|
+
...FLAG_FIELDS,
|
|
167
|
+
name: 'reason',
|
|
168
|
+
type: 'string',
|
|
169
|
+
value: '<text>',
|
|
170
|
+
summary: 'Auditable reason for granting or revoking sponsored usage.',
|
|
171
|
+
required: true,
|
|
172
|
+
});
|
|
173
|
+
const SPONSORSHIP_IDEMPOTENCY_FLAG = flag({
|
|
174
|
+
...FLAG_FIELDS,
|
|
175
|
+
name: 'idempotency-key',
|
|
176
|
+
type: 'string',
|
|
177
|
+
value: '<key>',
|
|
178
|
+
summary: 'Stable retry key for exactly-once mutation semantics.',
|
|
179
|
+
required: true,
|
|
180
|
+
});
|
|
181
|
+
const SPONSORSHIP_SUBCOMMAND = subcommand({
|
|
182
|
+
...SUBCOMMAND_FIELDS,
|
|
183
|
+
name: 'sponsorship',
|
|
184
|
+
summary: 'Inspect sponsored managed-assistant usage or operate expiring bonus grants.',
|
|
185
|
+
subcommands: [
|
|
186
|
+
subcommand({
|
|
187
|
+
...SUBCOMMAND_FIELDS,
|
|
188
|
+
name: 'inspect',
|
|
189
|
+
summary: 'Show one billing account’s allowance, usage, reset, and grants.',
|
|
190
|
+
arguments: [SPONSORSHIP_ACCOUNT_ARGUMENT],
|
|
191
|
+
flags: [...SPONSORSHIP_FLAGS],
|
|
192
|
+
jsonOutput: { mode: 'single' },
|
|
193
|
+
}),
|
|
194
|
+
subcommand({
|
|
195
|
+
...SUBCOMMAND_FIELDS,
|
|
196
|
+
name: 'grant',
|
|
197
|
+
summary: 'Add a bounded daily allowance until an explicit expiry.',
|
|
198
|
+
arguments: [SPONSORSHIP_ACCOUNT_ARGUMENT],
|
|
199
|
+
flags: [
|
|
200
|
+
flag({
|
|
201
|
+
...FLAG_FIELDS,
|
|
202
|
+
name: 'turns-per-day',
|
|
203
|
+
type: 'integer',
|
|
204
|
+
value: '<count>',
|
|
205
|
+
summary: 'Additional sponsored turns per UTC day.',
|
|
206
|
+
required: true,
|
|
207
|
+
constraints: { minimum: 1, maximum: 100_000 },
|
|
208
|
+
}),
|
|
209
|
+
flag({
|
|
210
|
+
...FLAG_FIELDS,
|
|
211
|
+
name: 'starts-at',
|
|
212
|
+
type: 'string',
|
|
213
|
+
value: '<timestamp>',
|
|
214
|
+
summary: 'Optional ISO timestamp; defaults to now.',
|
|
215
|
+
constraints: { default: 'now' },
|
|
216
|
+
}),
|
|
217
|
+
flag({
|
|
218
|
+
...FLAG_FIELDS,
|
|
219
|
+
name: 'expires-at',
|
|
220
|
+
type: 'string',
|
|
221
|
+
value: '<timestamp>',
|
|
222
|
+
summary: 'Required ISO timestamp after which the grant stops applying.',
|
|
223
|
+
required: true,
|
|
224
|
+
}),
|
|
225
|
+
SPONSORSHIP_REASON_FLAG,
|
|
226
|
+
SPONSORSHIP_IDEMPOTENCY_FLAG,
|
|
227
|
+
...SPONSORSHIP_FLAGS,
|
|
228
|
+
],
|
|
229
|
+
jsonOutput: { mode: 'single' },
|
|
230
|
+
}),
|
|
231
|
+
subcommand({
|
|
232
|
+
...SUBCOMMAND_FIELDS,
|
|
233
|
+
name: 'revoke',
|
|
234
|
+
summary: 'Revoke one bonus grant without deleting its audit history.',
|
|
235
|
+
arguments: [
|
|
236
|
+
SPONSORSHIP_ACCOUNT_ARGUMENT,
|
|
237
|
+
argument({
|
|
238
|
+
...ARGUMENT_FIELDS,
|
|
239
|
+
name: 'grant-id',
|
|
240
|
+
type: 'string',
|
|
241
|
+
summary: 'Managed-assistant sponsorship grant identifier.',
|
|
242
|
+
required: true,
|
|
243
|
+
}),
|
|
244
|
+
],
|
|
245
|
+
flags: [SPONSORSHIP_REASON_FLAG, SPONSORSHIP_IDEMPOTENCY_FLAG, ...SPONSORSHIP_FLAGS],
|
|
246
|
+
jsonOutput: { mode: 'single' },
|
|
247
|
+
}),
|
|
248
|
+
],
|
|
249
|
+
});
|
|
143
250
|
const CLIENTS_SUBCOMMAND = subcommand({
|
|
144
251
|
...SUBCOMMAND_FIELDS,
|
|
145
252
|
name: 'clients',
|
|
@@ -255,6 +362,7 @@ export const ASSISTANT_COMMAND = {
|
|
|
255
362
|
CLIENTS_SUBCOMMAND,
|
|
256
363
|
EMBEDS_SUBCOMMAND,
|
|
257
364
|
BUDGET_SUBCOMMAND,
|
|
365
|
+
SPONSORSHIP_SUBCOMMAND,
|
|
258
366
|
subcommand({
|
|
259
367
|
...SUBCOMMAND_FIELDS,
|
|
260
368
|
name: 'usage',
|
|
@@ -377,6 +377,9 @@ export declare const manifestV1Schema: z.ZodObject<{
|
|
|
377
377
|
sessionClaims: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
378
378
|
exposeToModel: z.ZodOptional<z.ZodBoolean>;
|
|
379
379
|
}, z.core.$strict>>>;
|
|
380
|
+
webmcp: z.ZodOptional<z.ZodObject<{
|
|
381
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
382
|
+
}, z.core.$strict>>;
|
|
380
383
|
}, z.core.$strict>>>;
|
|
381
384
|
sessionClaims: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
382
385
|
exposeToModel: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -856,6 +859,9 @@ export declare const manifestV2Schema: z.ZodObject<{
|
|
|
856
859
|
sessionClaims: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
857
860
|
exposeToModel: z.ZodOptional<z.ZodBoolean>;
|
|
858
861
|
}, z.core.$strict>>>;
|
|
862
|
+
webmcp: z.ZodOptional<z.ZodObject<{
|
|
863
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
864
|
+
}, z.core.$strict>>;
|
|
859
865
|
}, z.core.$strict>>>;
|
|
860
866
|
sessionClaims: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
861
867
|
exposeToModel: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -1545,6 +1551,9 @@ export declare const manifestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
1545
1551
|
sessionClaims: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
1546
1552
|
exposeToModel: z.ZodOptional<z.ZodBoolean>;
|
|
1547
1553
|
}, z.core.$strict>>>;
|
|
1554
|
+
webmcp: z.ZodOptional<z.ZodObject<{
|
|
1555
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
1556
|
+
}, z.core.$strict>>;
|
|
1548
1557
|
}, z.core.$strict>>>;
|
|
1549
1558
|
sessionClaims: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
1550
1559
|
exposeToModel: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -2023,6 +2032,9 @@ export declare const manifestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
2023
2032
|
sessionClaims: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
2024
2033
|
exposeToModel: z.ZodOptional<z.ZodBoolean>;
|
|
2025
2034
|
}, z.core.$strict>>>;
|
|
2035
|
+
webmcp: z.ZodOptional<z.ZodObject<{
|
|
2036
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
2037
|
+
}, z.core.$strict>>;
|
|
2026
2038
|
}, z.core.$strict>>>;
|
|
2027
2039
|
sessionClaims: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
2028
2040
|
exposeToModel: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -352,6 +352,11 @@ const embeddedAssistantSchema = z
|
|
|
352
352
|
instructions: z.string().trim().min(1).max(MAX_SERVER_INSTRUCTIONS).optional(),
|
|
353
353
|
capabilities: z.array(assistantCapabilitySchema).optional(),
|
|
354
354
|
sessionClaims: sessionClaimsSchema.optional(),
|
|
355
|
+
// Overrides the assistant's own opt-in for sessions minted on this surface, in either
|
|
356
|
+
// direction (ADR 0220, amended). Declared explicitly rather than inherited from
|
|
357
|
+
// `assistantUiSchema.shape` — this is not renderer presentation, and a surface that
|
|
358
|
+
// acquired it by a spread would be an accident rather than a decision.
|
|
359
|
+
webmcp: optionalStrictObject({ enabled: z.boolean().optional() }),
|
|
355
360
|
})
|
|
356
361
|
.strict())
|
|
357
362
|
.min(1)
|
|
@@ -360,9 +365,9 @@ const embeddedAssistantSchema = z
|
|
|
360
365
|
// 2026-07-14). Keys become `${user.claims.<key>}`; `exposeToModel` adds it to the identity line.
|
|
361
366
|
sessionClaims: sessionClaimsSchema.optional(),
|
|
362
367
|
// Additive optional field: a v1.x minor under ADR 0150, so no existing manifest can observe it.
|
|
363
|
-
//
|
|
364
|
-
//
|
|
365
|
-
//
|
|
368
|
+
// The deployment's default, which any surface may override. Declared here rather than in
|
|
369
|
+
// `assistantUiSchema` because that shape is spread into every surface as renderer-owned
|
|
370
|
+
// presentation, and this is not presentation — the surface carries its own copy on purpose.
|
|
366
371
|
webmcp: optionalStrictObject({ enabled: z.boolean().optional() }),
|
|
367
372
|
...assistantUiSchema.shape,
|
|
368
373
|
})
|
|
@@ -4,6 +4,7 @@ export * from './deployment-activation.js';
|
|
|
4
4
|
export * from './deployment-automation.js';
|
|
5
5
|
export * from './endpoint-url.js';
|
|
6
6
|
export * from './intent.js';
|
|
7
|
+
export * from './logger.js';
|
|
7
8
|
export * from './organization-provisioning.js';
|
|
8
9
|
export * from './platform-human-identity.js';
|
|
9
10
|
export * from './request-analytics.js';
|
|
@@ -4,6 +4,7 @@ export * from './deployment-activation.js';
|
|
|
4
4
|
export * from './deployment-automation.js';
|
|
5
5
|
export * from './endpoint-url.js';
|
|
6
6
|
export * from './intent.js';
|
|
7
|
+
export * from './logger.js';
|
|
7
8
|
export * from './organization-provisioning.js';
|
|
8
9
|
export * from './platform-human-identity.js';
|
|
9
10
|
export * from './request-analytics.js';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ModuleLogger } from './contract.js';
|
|
2
|
+
export type ModuleLogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
3
|
+
export type ModuleLogFields = Readonly<Record<string, unknown>>;
|
|
4
|
+
/** Adapt a module host logger to the child-capable shape used by service integrations. */
|
|
5
|
+
export interface AdaptedModuleLogger {
|
|
6
|
+
readonly level: ModuleLogLevel;
|
|
7
|
+
log(level: ModuleLogLevel, event: string, fields?: ModuleLogFields): void;
|
|
8
|
+
debug(event: string, fields?: ModuleLogFields): void;
|
|
9
|
+
info(event: string, fields?: ModuleLogFields): void;
|
|
10
|
+
warn(event: string, fields?: ModuleLogFields): void;
|
|
11
|
+
error(event: string, fields?: ModuleLogFields): void;
|
|
12
|
+
child(fields: ModuleLogFields): AdaptedModuleLogger;
|
|
13
|
+
}
|
|
14
|
+
export declare function adaptModuleLogger(logger: ModuleLogger, base?: ModuleLogFields): AdaptedModuleLogger;
|
|
15
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function adaptModuleLogger(logger, base = {}) {
|
|
2
|
+
const fields = (extra) => ({ ...base, ...extra });
|
|
3
|
+
const log = (level, event, extra) => {
|
|
4
|
+
logger[level](event, fields(extra));
|
|
5
|
+
};
|
|
6
|
+
return {
|
|
7
|
+
level: 'debug',
|
|
8
|
+
log,
|
|
9
|
+
debug: (event, extra) => log('debug', event, extra),
|
|
10
|
+
info: (event, extra) => log('info', event, extra),
|
|
11
|
+
warn: (event, extra) => log('warn', event, extra),
|
|
12
|
+
error: (event, extra) => log('error', event, extra),
|
|
13
|
+
child: (extra) => adaptModuleLogger(logger, fields(extra)),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { captureAssistantUsage } from './assistant-usage.js';
|
|
3
|
+
/** Project one apps-bridge tool call onto the shared tenant request stream. */
|
|
4
|
+
export function assistantAppToolCallRequestEvent(session, input) {
|
|
5
|
+
const isPublic = session.publicEmbedId !== undefined;
|
|
6
|
+
return {
|
|
7
|
+
...session.tenant,
|
|
8
|
+
deploymentId: session.deploymentId,
|
|
9
|
+
requestId: randomUUID(),
|
|
10
|
+
sessionId: session.id,
|
|
11
|
+
sessionSource: 'synthetic',
|
|
12
|
+
subjectKind: isPublic ? 'anonymous' : 'authenticated',
|
|
13
|
+
accessMode: isPublic ? 'public' : 'authenticated',
|
|
14
|
+
surface: input.bridged ? 'webmcp' : isPublic ? 'assistant-public' : 'assistant-authenticated',
|
|
15
|
+
method: 'tools/call',
|
|
16
|
+
kind: 'usage',
|
|
17
|
+
...(input.toolName === undefined ? {} : { toolName: input.toolName }),
|
|
18
|
+
outcome: input.outcome === 'ok' ? 'ok' : input.outcome === 'refused' ? 'tool_error' : 'mcp_error',
|
|
19
|
+
...(input.errorKind === undefined ? {} : { errorKind: input.errorKind }),
|
|
20
|
+
durationMs: Number.isFinite(input.durationMs) && input.durationMs > 0 ? input.durationMs : 0,
|
|
21
|
+
// Scalar-only, like every other assistant event: tool arguments, results, page context, and
|
|
22
|
+
// credentials have no field to enter through.
|
|
23
|
+
details: { eventKind: 'appToolCall', bridged: input.bridged },
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Bind one apps-bridge tool call's fixed facts, leaving the caller a recorder it can run at any
|
|
28
|
+
* exit. The route reaches several of them -- a budget refusal before any resolution work, an
|
|
29
|
+
* unprojected tool, bad arguments, a raised confirmation, a result -- and assembling the event at
|
|
30
|
+
* each one is how one of them ends up forgotten, which is what happened to the budget refusal.
|
|
31
|
+
*/
|
|
32
|
+
export function assistantAppToolCallRecorder(input) {
|
|
33
|
+
const startedAt = input.clock();
|
|
34
|
+
return (outcome, errorKind) => {
|
|
35
|
+
captureAssistantUsage(input.capture, assistantAppToolCallRequestEvent(input.session, {
|
|
36
|
+
// Absent when the caller named no tool. That row still matters: a nameless call that
|
|
37
|
+
// exhausts a budget is the shape abusive traffic takes, and it is the row an operator
|
|
38
|
+
// watching a browser agent would miss if a missing name suppressed the event.
|
|
39
|
+
...(input.toolName === undefined ? {} : { toolName: input.toolName }),
|
|
40
|
+
bridged: input.bridged,
|
|
41
|
+
outcome,
|
|
42
|
+
...(errorKind === undefined ? {} : { errorKind }),
|
|
43
|
+
durationMs: input.clock() - startedAt,
|
|
44
|
+
}));
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=assistant-app-tool-usage.js.map
|