@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
|
@@ -34,13 +34,22 @@ async function handleResolution(req, res, deps, legacyAcceptOnly) {
|
|
|
34
34
|
const response = parseInteractionResponse(body.value, legacyAcceptOnly);
|
|
35
35
|
if (!response.ok)
|
|
36
36
|
return sendJson(res, 400, { error: response.error });
|
|
37
|
+
const suggestions = response.value.suggestions === true;
|
|
38
|
+
if (suggestions) {
|
|
39
|
+
try {
|
|
40
|
+
await deps.store.replaceLatestSuggestions(session.id, undefined);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// Suggestions are optional; interaction resolution remains authoritative.
|
|
44
|
+
}
|
|
45
|
+
}
|
|
37
46
|
const scope = assistantInteractionScope(response.value.id, session, now(deps));
|
|
38
47
|
const interaction = await deps.store.getInteraction(scope);
|
|
39
48
|
if (!interaction) {
|
|
40
49
|
return sendJson(res, 409, { error: 'interaction is invalid or expired' });
|
|
41
50
|
}
|
|
42
51
|
if (response.value.action !== 'accept') {
|
|
43
|
-
return resolveStop(res, deps, session, interaction, response.value.action);
|
|
52
|
+
return resolveStop(res, deps, session, interaction, response.value.action, suggestions);
|
|
44
53
|
}
|
|
45
54
|
if (interaction.kind === 'confirmation' && response.value.content !== undefined) {
|
|
46
55
|
return sendJson(res, 400, { error: 'confirmation content cannot replace reviewed arguments' });
|
|
@@ -67,9 +76,9 @@ async function handleResolution(req, res, deps, legacyAcceptOnly) {
|
|
|
67
76
|
}
|
|
68
77
|
acceptedContent = validated.value;
|
|
69
78
|
}
|
|
70
|
-
return resolveAccept(res, deps, session, interaction, acceptedContent);
|
|
79
|
+
return resolveAccept(res, deps, session, interaction, acceptedContent, suggestions);
|
|
71
80
|
}
|
|
72
|
-
async function resolveAccept(res, deps, session, interaction, content) {
|
|
81
|
+
async function resolveAccept(res, deps, session, interaction, content, suggestions) {
|
|
73
82
|
if (interaction.status !== 'pending') {
|
|
74
83
|
return replayOrConflict(res, deps, session, interaction, 'accept');
|
|
75
84
|
}
|
|
@@ -149,9 +158,9 @@ async function resolveAccept(res, deps, session, interaction, content) {
|
|
|
149
158
|
? await resumeToolPreparation(target.served.artifact, continuation, { action: 'accept', content }, executeDeps)
|
|
150
159
|
: await resumeTool(target.served.artifact, continuation, { action: 'accept', content }, executeDeps);
|
|
151
160
|
}
|
|
152
|
-
return finishExecution(res, deps, session, claimed.interaction, context, result, target);
|
|
161
|
+
return finishExecution(res, deps, session, claimed.interaction, context, result, target, suggestions);
|
|
153
162
|
}
|
|
154
|
-
async function finishExecution(res, deps, session, interaction, context, result, target) {
|
|
163
|
+
async function finishExecution(res, deps, session, interaction, context, result, target, suggestions) {
|
|
155
164
|
const toolName = assistantInteractionTool(interaction) ?? 'unknown_tool';
|
|
156
165
|
if (result.status === 'confirmation_required') {
|
|
157
166
|
const tool = target.served.artifact.tools.find((candidate) => candidate.name === toolName);
|
|
@@ -357,10 +366,10 @@ async function finishExecution(res, deps, session, interaction, context, result,
|
|
|
357
366
|
}, (failure) => deps.logger?.warn('assistant.view.unresolved', { ...failure }));
|
|
358
367
|
if (view)
|
|
359
368
|
writeViewAvailable(res, view);
|
|
360
|
-
await narrateResolvedInteraction(res, deps, target, session, toolName, 'accept', safeOutput, context);
|
|
369
|
+
await narrateResolvedInteraction(res, deps, target, session, toolName, 'accept', safeOutput, context, suggestions);
|
|
361
370
|
return endInteractionSse(res);
|
|
362
371
|
}
|
|
363
|
-
async function resolveStop(res, deps, session, interaction, action) {
|
|
372
|
+
async function resolveStop(res, deps, session, interaction, action, suggestions) {
|
|
364
373
|
if (interaction.status !== 'pending') {
|
|
365
374
|
return replayOrConflict(res, deps, session, interaction, action);
|
|
366
375
|
}
|
|
@@ -426,7 +435,7 @@ async function resolveStop(res, deps, session, interaction, action) {
|
|
|
426
435
|
writeInteractionSseHeaders(res, session.origin);
|
|
427
436
|
writeInteractionResolved(res, interaction.id, action);
|
|
428
437
|
if (target && context) {
|
|
429
|
-
await narrateResolvedInteraction(res, deps, target, session, toolName, action, undefined, context);
|
|
438
|
+
await narrateResolvedInteraction(res, deps, target, session, toolName, action, undefined, context, suggestions);
|
|
430
439
|
}
|
|
431
440
|
return endInteractionSse(res);
|
|
432
441
|
}
|
|
@@ -5,10 +5,7 @@ export async function resolveAssistantModelBinding(target, tenant, deploymentId,
|
|
|
5
5
|
if (declaration === undefined)
|
|
6
6
|
return undefined;
|
|
7
7
|
if (declaration.kind === 'noodle-managed') {
|
|
8
|
-
return deps.managedModelResolver?.resolve({
|
|
9
|
-
tenant,
|
|
10
|
-
deploymentId,
|
|
11
|
-
});
|
|
8
|
+
return deps.managedModelResolver?.resolve({ tenant, deploymentId });
|
|
12
9
|
}
|
|
13
10
|
const scope = resolveConfigScope(tenant);
|
|
14
11
|
const [variables, secrets] = await Promise.all([
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomBytes } from 'node:crypto';
|
|
2
2
|
import { ADMISSION_DEFAULTS, clientAddressBucket } from '@noodle-borg/admission-limits/portable';
|
|
3
3
|
import { effectiveAssistantBrowserConfiguration, mintPublicSession, publicEmbedTenant, publicSurfaceOf, } from '@noodle-borg/assistant-gateway/portable';
|
|
4
|
-
import { assistantSessionUsageRequestEvent, captureAssistantUsage, } from '@noodle-borg/observability';
|
|
4
|
+
import { assistantSessionUsageRequestEvent, captureAssistantUsage, captureRefusedAssistantSession, } from '@noodle-borg/observability';
|
|
5
5
|
import { assistantSessionResponseSchema } from '@noodle-borg/wire-contracts';
|
|
6
6
|
import { readJsonBody, sendJson } from '../http-util.js';
|
|
7
7
|
import { applyBrowserCors, assistantSessionEndpoints, now } from './assistant-route-http.js';
|
|
@@ -25,7 +25,7 @@ export async function handlePublicAssistantSession(req, res, deps) {
|
|
|
25
25
|
const body = await readJsonBody(req, deps.maxBody);
|
|
26
26
|
if (!body.ok)
|
|
27
27
|
return sendJson(res, body.status, { error: body.error });
|
|
28
|
-
const { embedId } = body.value;
|
|
28
|
+
const { embedId, visitorId } = body.value;
|
|
29
29
|
// The browser's Origin header is the claim under test. The body never asserts its own origin.
|
|
30
30
|
const origin = typeof req.headers.origin === 'string' ? req.headers.origin : undefined;
|
|
31
31
|
// Admission tier 3. Hashed here so no raw address crosses into the decision, a counter key, or an
|
|
@@ -36,7 +36,7 @@ export async function handlePublicAssistantSession(req, res, deps) {
|
|
|
36
36
|
let configuration;
|
|
37
37
|
let sponsoredBudget;
|
|
38
38
|
let usageSession;
|
|
39
|
-
const result = await mintPublicSession({ embedId, origin, ...(addressBucket ? { addressBucket } : {}) }, deps.admissionEnvelope ?? ADMISSION_DEFAULTS, {
|
|
39
|
+
const result = await mintPublicSession({ embedId, visitorId, origin, ...(addressBucket ? { addressBucket } : {}) }, deps.admissionEnvelope ?? ADMISSION_DEFAULTS, {
|
|
40
40
|
embeds,
|
|
41
41
|
counters,
|
|
42
42
|
resolveActiveSurface: async (embed) => {
|
|
@@ -86,6 +86,10 @@ export async function handlePublicAssistantSession(req, res, deps) {
|
|
|
86
86
|
now: () => now(deps),
|
|
87
87
|
});
|
|
88
88
|
if (!result.ok) {
|
|
89
|
+
// A refusal the surface owns is usage. Without it an operator whose visitors are being turned
|
|
90
|
+
// away sees a healthy-looking page; which refusals count is the helper's decision, not this one's.
|
|
91
|
+
const refusedIn = performance.now() - usageStartedAt;
|
|
92
|
+
captureRefusedAssistantSession(deps.captureRequestEvent, result, refusedIn);
|
|
89
93
|
return sendJson(res, result.status, { error: result.message, code: result.code });
|
|
90
94
|
}
|
|
91
95
|
// Only here, with the origin proven against the live surface, may the page read the response.
|
|
@@ -56,6 +56,7 @@ export function assistantSessionEndpoints(base) {
|
|
|
56
56
|
apps: `${base}/v1/assistant/apps`,
|
|
57
57
|
sandbox: `${base}/v1/assistant/sandbox`,
|
|
58
58
|
transcript: `${base}/v1/assistant/transcript`,
|
|
59
|
+
suggestions: `${base}/v1/assistant/suggestions`,
|
|
59
60
|
};
|
|
60
61
|
}
|
|
61
62
|
//# sourceMappingURL=assistant-route-http.js.map
|
|
@@ -1,49 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
return undefined;
|
|
6
|
-
const assistant = target.served.artifact.server.assistant;
|
|
7
|
-
const bound = session.boundSurface ?? deriveLegacyBinding(assistant, session);
|
|
8
|
-
if (bound === 'pre-surfaces')
|
|
9
|
-
return target;
|
|
10
|
-
// Fail closed: an origin no surface owns must never widen to the whole server.
|
|
11
|
-
if (bound === 'unowned')
|
|
12
|
-
return undefined;
|
|
13
|
-
// Fail closed on a vanished surface. A rollback to an artifact without the bound surface must end the
|
|
14
|
-
// session's reach, never hand it the unprojected server.
|
|
15
|
-
if (bound === 'authenticated') {
|
|
16
|
-
const surface = authenticatedSurfaceOf(assistant);
|
|
17
|
-
if (surface === undefined)
|
|
18
|
-
return undefined;
|
|
19
|
-
// An omitted allowlist on an authenticated surface is the authored whole-server intent, so the
|
|
20
|
-
// exact binding still holds (instructions, budgets, attribution) without narrowing capabilities.
|
|
21
|
-
if (surface.capabilities === undefined)
|
|
22
|
-
return target;
|
|
23
|
-
return projected(target, surface.capabilities);
|
|
24
|
-
}
|
|
25
|
-
const surface = publicSurfaceOf(assistant);
|
|
26
|
-
if (surface === undefined)
|
|
27
|
-
return undefined;
|
|
28
|
-
return projected(target, surface.capabilities);
|
|
29
|
-
}
|
|
30
|
-
function projected(target, capabilities) {
|
|
31
|
-
return {
|
|
32
|
-
...target,
|
|
33
|
-
served: {
|
|
34
|
-
...target.served,
|
|
35
|
-
artifact: projectArtifactForSurface(target.served.artifact, capabilities),
|
|
36
|
-
},
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* A record minted before `boundSurface` existed: the embed id marks the public surface; otherwise the
|
|
41
|
-
* session's origin selects the owning surface on the pinned deployment. `pre-surfaces` means the whole
|
|
42
|
-
* server on that released artifact shape; `unowned` fails closed above.
|
|
43
|
-
*/
|
|
44
|
-
function deriveLegacyBinding(assistant, session) {
|
|
45
|
-
if (session.publicEmbedId !== undefined)
|
|
46
|
-
return 'public';
|
|
47
|
-
return surfaceBindingForOrigin(assistant, session.origin).kind;
|
|
1
|
+
import { resolveAssistantSessionTarget, } from '@noodle-borg/assistant-gateway/portable';
|
|
2
|
+
/** Adapt the registry to the gateway-owned, surface-projected session target decision. */
|
|
3
|
+
export function sessionScopedTarget(registry, session) {
|
|
4
|
+
return resolveAssistantSessionTarget((deploymentId) => registry.get(deploymentId), session);
|
|
48
5
|
}
|
|
49
6
|
//# sourceMappingURL=assistant-session-target.js.map
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { ADMISSION_DEFAULTS } from '@noodle-borg/admission-limits/portable';
|
|
2
|
+
import { parseAssistantContextPreferences, surfaceEnvelope, withAssistantSessionExecutionAuthority, } from '@noodle-borg/assistant-gateway/portable';
|
|
3
|
+
import { assistantSuggestionsRequestSchema } from '@noodle-borg/wire-contracts';
|
|
4
|
+
import { readJsonBody, sendJson } from '../http-util.js';
|
|
5
|
+
import { resolveInvocationContextSnapshot } from '../invocation-context.js';
|
|
6
|
+
import { generateInitialAssistantSuggestions } from './assistant-agent.js';
|
|
7
|
+
import { applyBrowserCors, authenticateSession, now } from './assistant-route-http.js';
|
|
8
|
+
import { sessionScopedTarget } from './assistant-session-target.js';
|
|
9
|
+
/** Generate or replay the one bounded initial prompt set for a session whose config omitted it. */
|
|
10
|
+
export async function handleAssistantSuggestions(req, res, deps) {
|
|
11
|
+
const session = await authenticateSession(req, res, deps);
|
|
12
|
+
if (!session)
|
|
13
|
+
return;
|
|
14
|
+
applyBrowserCors(req, res, session.origin);
|
|
15
|
+
const body = await readJsonBody(req, deps.maxBody);
|
|
16
|
+
if (!body.ok)
|
|
17
|
+
return sendJson(res, body.status, { error: body.error });
|
|
18
|
+
const parsed = assistantSuggestionsRequestSchema.safeParse(body.value);
|
|
19
|
+
if (!parsed.success)
|
|
20
|
+
return sendJson(res, 400, { error: 'invalid suggestions request' });
|
|
21
|
+
// The server enforces omission too: a modified client cannot spend a model call when the resolved
|
|
22
|
+
// developer/operator configuration supplied either exact prompts or an explicit empty array.
|
|
23
|
+
if (session.configuration?.assistant?.suggestedPrompts !== undefined) {
|
|
24
|
+
return writeEmptySuggestions(res, session.origin);
|
|
25
|
+
}
|
|
26
|
+
if (session.history.length > 0)
|
|
27
|
+
return writeEmptySuggestions(res, session.origin);
|
|
28
|
+
if (!(await publicSuggestionWorkIsAvailable(session, deps))) {
|
|
29
|
+
return sendJson(res, 429, {
|
|
30
|
+
error: 'assistant is unavailable right now',
|
|
31
|
+
code: 'daily_turn_budget_exhausted',
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
const claim = await deps.store.claimInitialSuggestions(session.id);
|
|
35
|
+
if (claim.disposition === 'ready') {
|
|
36
|
+
return writeSuggestions(res, session.origin, claim.prompts);
|
|
37
|
+
}
|
|
38
|
+
if (claim.disposition !== 'generate')
|
|
39
|
+
return writeEmptySuggestions(res, session.origin);
|
|
40
|
+
const target = await sessionScopedTarget(deps.registry, session);
|
|
41
|
+
if (!target) {
|
|
42
|
+
await deps.store.failInitialSuggestions(session.id);
|
|
43
|
+
return writeEmptySuggestions(res, session.origin);
|
|
44
|
+
}
|
|
45
|
+
const clientContext = parsed.data.clientContext === undefined
|
|
46
|
+
? undefined
|
|
47
|
+
: parseAssistantContextPreferences(parsed.data.clientContext);
|
|
48
|
+
if (clientContext?.ok === false) {
|
|
49
|
+
await deps.store.failInitialSuggestions(session.id);
|
|
50
|
+
return sendJson(res, 400, { error: 'invalid client context' });
|
|
51
|
+
}
|
|
52
|
+
const context = await resolveInvocationContextSnapshot({
|
|
53
|
+
artifact: target.served.artifact,
|
|
54
|
+
executeDeps: withAssistantSessionExecutionAuthority(target.served.deps, target.served.artifact, session),
|
|
55
|
+
caller: session.caller,
|
|
56
|
+
instant: now(deps),
|
|
57
|
+
...(session.preferences ? { applicationPreference: session.preferences } : {}),
|
|
58
|
+
...(clientContext?.ok ? { clientHint: clientContext.value } : {}),
|
|
59
|
+
});
|
|
60
|
+
try {
|
|
61
|
+
const prompts = await generateInitialAssistantSuggestions(target, session, context, deps, parsed.data.modelContext, parsed.data.pageContext);
|
|
62
|
+
if (prompts.length === 0) {
|
|
63
|
+
await deps.store.failInitialSuggestions(session.id);
|
|
64
|
+
return writeEmptySuggestions(res, session.origin);
|
|
65
|
+
}
|
|
66
|
+
await deps.store.completeInitialSuggestions(session.id, prompts);
|
|
67
|
+
return writeSuggestions(res, session.origin, prompts);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
await deps.store.failInitialSuggestions(session.id);
|
|
71
|
+
return writeEmptySuggestions(res, session.origin);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async function publicSuggestionWorkIsAvailable(session, deps) {
|
|
75
|
+
if (session.publicEmbedId === undefined)
|
|
76
|
+
return true;
|
|
77
|
+
const embed = await deps.publicEmbeds?.lookup(session.publicEmbedId);
|
|
78
|
+
if (!embed)
|
|
79
|
+
return false;
|
|
80
|
+
const bounds = session.modelSource !== 'noodle-managed'
|
|
81
|
+
? undefined
|
|
82
|
+
: (await deps.managedModelResolver?.resolve({
|
|
83
|
+
tenant: session.tenant,
|
|
84
|
+
deploymentId: session.deploymentId,
|
|
85
|
+
}))?.publicAdmission;
|
|
86
|
+
const envelope = surfaceEnvelope(deps.admissionEnvelope ?? ADMISSION_DEFAULTS, embed, bounds);
|
|
87
|
+
return envelope.mintsPerDay > 0 && envelope.turnsPerDay > 0;
|
|
88
|
+
}
|
|
89
|
+
function writeSuggestions(res, origin, prompts) {
|
|
90
|
+
writeHeaders(res, origin);
|
|
91
|
+
res.write(`event: suggested_prompts\ndata: ${JSON.stringify({ phase: 'initial', prompts })}\n\n`);
|
|
92
|
+
res.end('event: done\ndata: {}\n\n');
|
|
93
|
+
}
|
|
94
|
+
function writeEmptySuggestions(res, origin) {
|
|
95
|
+
writeHeaders(res, origin);
|
|
96
|
+
res.end('event: done\ndata: {}\n\n');
|
|
97
|
+
}
|
|
98
|
+
function writeHeaders(res, origin) {
|
|
99
|
+
res.writeHead(200, {
|
|
100
|
+
'content-type': 'text/event-stream; charset=utf-8',
|
|
101
|
+
'cache-control': 'no-store',
|
|
102
|
+
'access-control-allow-origin': origin,
|
|
103
|
+
vary: 'Origin',
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=assistant-suggestions.js.map
|
|
@@ -18,7 +18,12 @@ export async function handleAssistantTranscript(req, res, deps) {
|
|
|
18
18
|
if (!session)
|
|
19
19
|
return;
|
|
20
20
|
applyBrowserCors(req, res, session.origin);
|
|
21
|
-
const body = {
|
|
21
|
+
const body = {
|
|
22
|
+
entries: visibleTranscript(session.history),
|
|
23
|
+
...(session.latestSuggestions?.phase === 'follow_up'
|
|
24
|
+
? { suggestions: session.latestSuggestions }
|
|
25
|
+
: {}),
|
|
26
|
+
};
|
|
22
27
|
// Parse before sending, like the session exchange: a contract break fails loudly here rather than
|
|
23
28
|
// stranding deployed widgets (ADR 0151).
|
|
24
29
|
assistantTranscriptResponseSchema.parse(body);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { clientAddressBucket, } from '@noodle-borg/admission-limits/portable';
|
|
2
2
|
import { assistantModelFailure, assistantModelSource, assistantModelTransport, } from '@noodle-borg/assistant-gateway/model-runtime';
|
|
3
|
-
import { ASSISTANT_SESSION_IDLE_MS, dispatchAssistantTool, effectiveAssistantBrowserConfiguration, isAssistantPageContext, isAssistantVerifiedClaims, parseAssistantContextPreferences, parseAssistantCustomerRouting, resumeTurnMessage, resumeUnavailableMessage, shouldAutoResume, surfaceBindingForOrigin, withAssistantSessionExecutionAuthority, } from '@noodle-borg/assistant-gateway/portable';
|
|
3
|
+
import { ASSISTANT_SESSION_IDLE_MS, dispatchAssistantTool, effectiveAssistantBrowserConfiguration, isAssistantPageContext, isAssistantVerifiedClaims, parseAssistantContextPreferences, parseAssistantCustomerRouting, refuseBridgeToolCall, refusePublicTurn, resumeTurnMessage, resumeUnavailableMessage, shouldAutoResume, surfaceBindingForOrigin, withAssistantSessionExecutionAuthority, } from '@noodle-borg/assistant-gateway/portable';
|
|
4
4
|
import { canonicalizeAuthorizationClaimValues } from '@noodle-borg/auth';
|
|
5
5
|
import { validateJsonSchemaWithDefaults } from '@noodle-borg/compiler';
|
|
6
6
|
import { tenantMcpUrl } from '@noodle-borg/module';
|
|
@@ -13,7 +13,6 @@ import { resolveInvocationContextSnapshot } from '../invocation-context.js';
|
|
|
13
13
|
import { createAssistantTurnStats, runAgentTurn } from './assistant-agent.js';
|
|
14
14
|
import { elevateAssistantSession } from './assistant-elevation.js';
|
|
15
15
|
import { executeAssistantKnowledgeSearch, findAssistantKnowledgeComponent, resolveAssistantKnowledge, } from './assistant-knowledge.js';
|
|
16
|
-
import { refusePublicTurn } from './assistant-public-turn.js';
|
|
17
16
|
import { applyBrowserCors, assistantSessionEndpoints, authenticateSession, handleAssistantPreflight, now, } from './assistant-route-http.js';
|
|
18
17
|
import { sessionScopedTarget } from './assistant-session-target.js';
|
|
19
18
|
import { authorizeControlPlane } from './control-plane.js';
|
|
@@ -175,6 +174,16 @@ export async function handleAssistantSession(req, res, deps) {
|
|
|
175
174
|
captureAssistantUsage(deps.captureRequestEvent, assistantSessionUsageRequestEvent(session.session, performance.now() - usageStartedAt));
|
|
176
175
|
return sendJson(res, 201, sessionBody);
|
|
177
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* Both turn entries — a resume and a typed message — refuse identically: the refusal is spent
|
|
179
|
+
* budget, so it is recorded against the surface before the caller is told no. Kept in one place so
|
|
180
|
+
* the two entries cannot drift into recording it differently.
|
|
181
|
+
*/
|
|
182
|
+
function refuseTurn(deps, res, session, refusal, usageStartedAt) {
|
|
183
|
+
const durationMs = performance.now() - usageStartedAt;
|
|
184
|
+
captureAssistantUsage(deps.captureRequestEvent, assistantRefusedTurnUsageRequestEvent(session, refusal.code, durationMs));
|
|
185
|
+
sendJson(res, refusal.status, { error: refusal.message, code: refusal.code });
|
|
186
|
+
}
|
|
178
187
|
export async function handleAssistantTurn(req, res, deps) {
|
|
179
188
|
const usageStartedAt = performance.now();
|
|
180
189
|
const session = await authenticateSession(req, res, deps);
|
|
@@ -191,18 +200,19 @@ export async function handleAssistantTurn(req, res, deps) {
|
|
|
191
200
|
let turn;
|
|
192
201
|
let message;
|
|
193
202
|
let resumedTool;
|
|
203
|
+
let suggestionsRequested = false;
|
|
194
204
|
if (isRecord(body.value) && 'resume' in body.value) {
|
|
195
205
|
// The one-shot post-sign-in resume (issue #1177): no user text, the pending intent is server
|
|
196
206
|
// state. Admission runs FIRST so a refused resume stays armed for a retry; the consume is a
|
|
197
207
|
// single statement, so two requests racing the arm cannot both run it.
|
|
198
|
-
|
|
208
|
+
const resumeResult = assistantResumeTurnRequestSchema.safeParse(body.value);
|
|
209
|
+
if (!resumeResult.success) {
|
|
199
210
|
return sendJson(res, 400, { error: 'invalid turn request' });
|
|
200
211
|
}
|
|
212
|
+
suggestionsRequested = resumeResult.data.suggestions === true;
|
|
201
213
|
const refusal = await refusePublicTurn(deps, session, 'resume', addressBucket);
|
|
202
|
-
if (refusal)
|
|
203
|
-
|
|
204
|
-
return sendJson(res, refusal.status, { error: refusal.message, code: refusal.code });
|
|
205
|
-
}
|
|
214
|
+
if (refusal)
|
|
215
|
+
return refuseTurn(deps, res, session, refusal, usageStartedAt);
|
|
206
216
|
const pending = await deps.store.consumePendingResume(session.id);
|
|
207
217
|
if (!pending) {
|
|
208
218
|
return sendJson(res, 409, {
|
|
@@ -242,6 +252,7 @@ export async function handleAssistantTurn(req, res, deps) {
|
|
|
242
252
|
return sendJson(res, 400, { error: 'invalid turn request' });
|
|
243
253
|
}
|
|
244
254
|
turn = turnResult.data;
|
|
255
|
+
suggestionsRequested = turn.suggestions === true;
|
|
245
256
|
message = turn.message;
|
|
246
257
|
if (message.trim().length === 0) {
|
|
247
258
|
return sendJson(res, 400, { error: '"message" must be a non-empty string' });
|
|
@@ -249,10 +260,8 @@ export async function handleAssistantTurn(req, res, deps) {
|
|
|
249
260
|
// Admission before any metered work (ADR 0201 §8): ahead of the deployment read and well ahead
|
|
250
261
|
// of the model, so a refused turn costs the customer nothing.
|
|
251
262
|
const refusal = await refusePublicTurn(deps, session, message, addressBucket);
|
|
252
|
-
if (refusal)
|
|
253
|
-
|
|
254
|
-
return sendJson(res, refusal.status, { error: refusal.message, code: refusal.code });
|
|
255
|
-
}
|
|
263
|
+
if (refusal)
|
|
264
|
+
return refuseTurn(deps, res, session, refusal, usageStartedAt);
|
|
256
265
|
// The visitor typed first: any pending post-sign-in resume is moot, and must not fire later.
|
|
257
266
|
if (session.pendingResume)
|
|
258
267
|
await deps.store.consumePendingResume(session.id);
|
|
@@ -296,7 +305,15 @@ export async function handleAssistantTurn(req, res, deps) {
|
|
|
296
305
|
};
|
|
297
306
|
let turnCompleted = false;
|
|
298
307
|
try {
|
|
299
|
-
|
|
308
|
+
if (suggestionsRequested) {
|
|
309
|
+
try {
|
|
310
|
+
await deps.store.replaceLatestSuggestions(session.id, undefined);
|
|
311
|
+
}
|
|
312
|
+
catch {
|
|
313
|
+
// Suggestions are optional; the assistant turn remains authoritative.
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
await runAgentTurn(target, session, message.trim(), invocationContext, deps, emit, turn?.modelContext, turn?.pageContext, stats, suggestionsRequested);
|
|
300
317
|
turnCompleted = true;
|
|
301
318
|
}
|
|
302
319
|
catch (error) {
|
|
@@ -367,6 +384,12 @@ export async function handleAssistantAppRequest(req, res, deps) {
|
|
|
367
384
|
return sendJson(res, 409, { error: 'assistant deployment is unavailable' });
|
|
368
385
|
const method = body.value.method;
|
|
369
386
|
const params = body.value.params;
|
|
387
|
+
// Answered before any resolution work, so a refused browser agent costs nothing (ADR 0220). The
|
|
388
|
+
// marker is client-declared: it selects a budget, never authority, which the session below decides.
|
|
389
|
+
const bridged = method === 'tools/call' && body.value.bridge === 'webmcp';
|
|
390
|
+
const refusal = bridged ? await refuseBridgeToolCall(deps, session) : undefined;
|
|
391
|
+
if (refusal)
|
|
392
|
+
return sendJson(res, refusal.status, { error: refusal.message, code: refusal.code });
|
|
370
393
|
const knowledge = await resolveAssistantKnowledge(target.served);
|
|
371
394
|
if (method === 'tools/list') {
|
|
372
395
|
return sendJson(res, 200, mapToolsList(target.served.artifact, session.caller, {
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
41
41
|
"@noodle-borg/admission-limits": "0.0.0",
|
|
42
|
-
"@noodle-borg/agent-kit": "0.
|
|
42
|
+
"@noodle-borg/agent-kit": "0.88.0",
|
|
43
43
|
"@noodle-borg/app-package": "0.0.0",
|
|
44
44
|
"@noodle-borg/assistant-gateway": "0.0.0",
|
|
45
45
|
"@noodle-borg/auth": "0.0.0",
|
|
@@ -77,6 +77,7 @@ export function emitRequestEvent(input) {
|
|
|
77
77
|
...(clientVersion !== undefined ? { clientVersion } : {}),
|
|
78
78
|
clientFamily: family,
|
|
79
79
|
...(auth.accessMode !== undefined ? { accessMode: auth.accessMode } : {}),
|
|
80
|
+
surface: 'mcp',
|
|
80
81
|
subjectKind: subject !== undefined ? 'authenticated' : 'anonymous',
|
|
81
82
|
method,
|
|
82
83
|
kind: DISCOVERY_METHODS.has(method) ? 'discovery' : 'usage',
|
|
@@ -147,6 +148,7 @@ export function emitDeniedRequestEvent(input) {
|
|
|
147
148
|
sessionSource: 'none',
|
|
148
149
|
clientFamily: clientFamily(undefined, header(req, 'user-agent')),
|
|
149
150
|
...(auth.accessMode !== undefined ? { accessMode: auth.accessMode } : {}),
|
|
151
|
+
surface: 'mcp',
|
|
150
152
|
subjectKind: 'anonymous',
|
|
151
153
|
method,
|
|
152
154
|
kind: 'usage',
|
|
@@ -21,6 +21,7 @@ export declare const assistantSessionResponseSchema: z.ZodObject<{
|
|
|
21
21
|
apps: z.ZodOptional<z.ZodURL>;
|
|
22
22
|
sandbox: z.ZodOptional<z.ZodURL>;
|
|
23
23
|
transcript: z.ZodOptional<z.ZodURL>;
|
|
24
|
+
suggestions: z.ZodOptional<z.ZodURL>;
|
|
24
25
|
}, z.core.$strip>;
|
|
25
26
|
configuration: z.ZodOptional<z.ZodObject<{
|
|
26
27
|
branding: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
@@ -45,6 +46,10 @@ export declare const assistantTranscriptResponseSchema: z.ZodObject<{
|
|
|
45
46
|
}>;
|
|
46
47
|
text: z.ZodString;
|
|
47
48
|
}, z.core.$strict>>;
|
|
49
|
+
suggestions: z.ZodOptional<z.ZodObject<{
|
|
50
|
+
phase: z.ZodLiteral<"follow_up">;
|
|
51
|
+
prompts: z.ZodArray<z.ZodString>;
|
|
52
|
+
}, z.core.$strict>>;
|
|
48
53
|
}, z.core.$strict>;
|
|
49
54
|
export type AssistantTranscriptResponse = z.infer<typeof assistantTranscriptResponseSchema>;
|
|
50
55
|
/** MCP-Apps-shaped, author-selected renderer summary included on one assistant turn. */
|
|
@@ -78,9 +83,11 @@ export declare const assistantMessageTurnRequestSchema: z.ZodObject<{
|
|
|
78
83
|
structuredContent: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodJSONSchema>>;
|
|
79
84
|
}, z.core.$strict>>;
|
|
80
85
|
pageContext: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodJSONSchema>>;
|
|
86
|
+
suggestions: z.ZodOptional<z.ZodLiteral<true>>;
|
|
81
87
|
}, z.core.$strict>;
|
|
82
88
|
export declare const assistantResumeTurnRequestSchema: z.ZodObject<{
|
|
83
89
|
resume: z.ZodLiteral<true>;
|
|
90
|
+
suggestions: z.ZodOptional<z.ZodLiteral<true>>;
|
|
84
91
|
}, z.core.$strict>;
|
|
85
92
|
export type AssistantMessageTurnRequest = z.infer<typeof assistantMessageTurnRequestSchema>;
|
|
86
93
|
export declare const assistantTurnRequestSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
@@ -97,10 +104,28 @@ export declare const assistantTurnRequestSchema: z.ZodUnion<readonly [z.ZodObjec
|
|
|
97
104
|
structuredContent: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodJSONSchema>>;
|
|
98
105
|
}, z.core.$strict>>;
|
|
99
106
|
pageContext: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodJSONSchema>>;
|
|
107
|
+
suggestions: z.ZodOptional<z.ZodLiteral<true>>;
|
|
100
108
|
}, z.core.$strict>, z.ZodObject<{
|
|
101
109
|
resume: z.ZodLiteral<true>;
|
|
110
|
+
suggestions: z.ZodOptional<z.ZodLiteral<true>>;
|
|
102
111
|
}, z.core.$strict>]>;
|
|
103
112
|
export type AssistantTurnRequest = z.infer<typeof assistantTurnRequestSchema>;
|
|
113
|
+
/** Fresh untrusted context for the optional initial-suggestions request. */
|
|
114
|
+
export declare const assistantSuggestionsRequestSchema: z.ZodObject<{
|
|
115
|
+
clientContext: z.ZodOptional<z.ZodObject<{
|
|
116
|
+
locale: z.ZodOptional<z.ZodString>;
|
|
117
|
+
timeZone: z.ZodOptional<z.ZodString>;
|
|
118
|
+
}, z.core.$strict>>;
|
|
119
|
+
modelContext: z.ZodOptional<z.ZodObject<{
|
|
120
|
+
content: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
121
|
+
type: z.ZodLiteral<"text">;
|
|
122
|
+
text: z.ZodString;
|
|
123
|
+
}, z.core.$strict>>>;
|
|
124
|
+
structuredContent: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodJSONSchema>>;
|
|
125
|
+
}, z.core.$strict>>;
|
|
126
|
+
pageContext: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodJSONSchema>>;
|
|
127
|
+
}, z.core.$strict>;
|
|
128
|
+
export type AssistantSuggestionsRequest = z.infer<typeof assistantSuggestionsRequestSchema>;
|
|
104
129
|
/**
|
|
105
130
|
* Resolve one server-held assistant interaction. The client returns only its decision and optional
|
|
106
131
|
* schema-constrained input; reviewed tool arguments remain server-held and cannot be substituted here.
|
|
@@ -109,12 +134,15 @@ export declare const assistantInteractionRequestSchema: z.ZodDiscriminatedUnion<
|
|
|
109
134
|
id: z.ZodString;
|
|
110
135
|
action: z.ZodLiteral<"accept">;
|
|
111
136
|
content: z.ZodOptional<z.ZodJSONSchema>;
|
|
137
|
+
suggestions: z.ZodOptional<z.ZodLiteral<true>>;
|
|
112
138
|
}, z.core.$strict>, z.ZodObject<{
|
|
113
139
|
id: z.ZodString;
|
|
114
140
|
action: z.ZodLiteral<"decline">;
|
|
141
|
+
suggestions: z.ZodOptional<z.ZodLiteral<true>>;
|
|
115
142
|
}, z.core.$strict>, z.ZodObject<{
|
|
116
143
|
id: z.ZodString;
|
|
117
144
|
action: z.ZodLiteral<"cancel">;
|
|
145
|
+
suggestions: z.ZodOptional<z.ZodLiteral<true>>;
|
|
118
146
|
}, z.core.$strict>], "action">;
|
|
119
147
|
export type AssistantInteractionRequest = z.infer<typeof assistantInteractionRequestSchema>;
|
|
120
148
|
/**
|
|
@@ -204,6 +232,16 @@ export declare const assistantWireEventSchema: z.ZodUnion<readonly [z.ZodObject<
|
|
|
204
232
|
continuation: z.ZodString;
|
|
205
233
|
expiresAt: z.ZodString;
|
|
206
234
|
}, z.core.$strip>;
|
|
235
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
236
|
+
event: z.ZodLiteral<string>;
|
|
237
|
+
data: z.ZodObject<{
|
|
238
|
+
turnId: z.ZodOptional<z.ZodString>;
|
|
239
|
+
phase: z.ZodEnum<{
|
|
240
|
+
follow_up: "follow_up";
|
|
241
|
+
initial: "initial";
|
|
242
|
+
}>;
|
|
243
|
+
prompts: z.ZodArray<z.ZodString>;
|
|
244
|
+
}, z.core.$strip>;
|
|
207
245
|
}, z.core.$strip>, z.ZodObject<{
|
|
208
246
|
event: z.ZodLiteral<string>;
|
|
209
247
|
data: z.ZodObject<{
|
|
@@ -49,6 +49,8 @@ export const assistantSessionResponseSchema = z.object({
|
|
|
49
49
|
* starts visually fresh exactly as before.
|
|
50
50
|
*/
|
|
51
51
|
transcript: z.url().optional(),
|
|
52
|
+
/** Additive model-generated prompt suggestions; its presence is the client capability gate. */
|
|
53
|
+
suggestions: z.url().optional(),
|
|
52
54
|
}),
|
|
53
55
|
configuration: assistantSessionConfigurationSchema.optional(),
|
|
54
56
|
/**
|
|
@@ -74,6 +76,13 @@ export const assistantTranscriptResponseSchema = z
|
|
|
74
76
|
})
|
|
75
77
|
.strict())
|
|
76
78
|
.max(40),
|
|
79
|
+
suggestions: z
|
|
80
|
+
.object({
|
|
81
|
+
phase: z.literal('follow_up'),
|
|
82
|
+
prompts: z.array(z.string().trim().min(1).max(240)).max(3),
|
|
83
|
+
})
|
|
84
|
+
.strict()
|
|
85
|
+
.optional(),
|
|
77
86
|
})
|
|
78
87
|
.strict();
|
|
79
88
|
const assistantModelContentPartSchema = z
|
|
@@ -120,23 +129,54 @@ export const assistantMessageTurnRequestSchema = z
|
|
|
120
129
|
.optional(),
|
|
121
130
|
modelContext: assistantModelContextUpdateSchema.optional(),
|
|
122
131
|
pageContext: assistantPageContextSchema.optional(),
|
|
132
|
+
suggestions: z.literal(true).optional(),
|
|
123
133
|
})
|
|
124
134
|
.strict();
|
|
125
|
-
export const assistantResumeTurnRequestSchema = z
|
|
135
|
+
export const assistantResumeTurnRequestSchema = z
|
|
136
|
+
.object({ resume: z.literal(true), suggestions: z.literal(true).optional() })
|
|
137
|
+
.strict();
|
|
126
138
|
export const assistantTurnRequestSchema = z.union([
|
|
127
139
|
assistantMessageTurnRequestSchema,
|
|
128
140
|
assistantResumeTurnRequestSchema,
|
|
129
141
|
]);
|
|
142
|
+
/** Fresh untrusted context for the optional initial-suggestions request. */
|
|
143
|
+
export const assistantSuggestionsRequestSchema = z
|
|
144
|
+
.object({
|
|
145
|
+
clientContext: z
|
|
146
|
+
.object({ locale: z.string().max(160).optional(), timeZone: z.string().max(160).optional() })
|
|
147
|
+
.strict()
|
|
148
|
+
.optional(),
|
|
149
|
+
modelContext: assistantModelContextUpdateSchema.optional(),
|
|
150
|
+
pageContext: assistantPageContextSchema.optional(),
|
|
151
|
+
})
|
|
152
|
+
.strict();
|
|
130
153
|
/**
|
|
131
154
|
* Resolve one server-held assistant interaction. The client returns only its decision and optional
|
|
132
155
|
* schema-constrained input; reviewed tool arguments remain server-held and cannot be substituted here.
|
|
133
156
|
*/
|
|
134
157
|
export const assistantInteractionRequestSchema = z.discriminatedUnion('action', [
|
|
135
158
|
z
|
|
136
|
-
.object({
|
|
159
|
+
.object({
|
|
160
|
+
id: z.string().min(1),
|
|
161
|
+
action: z.literal('accept'),
|
|
162
|
+
content: z.json().optional(),
|
|
163
|
+
suggestions: z.literal(true).optional(),
|
|
164
|
+
})
|
|
165
|
+
.strict(),
|
|
166
|
+
z
|
|
167
|
+
.object({
|
|
168
|
+
id: z.string().min(1),
|
|
169
|
+
action: z.literal('decline'),
|
|
170
|
+
suggestions: z.literal(true).optional(),
|
|
171
|
+
})
|
|
172
|
+
.strict(),
|
|
173
|
+
z
|
|
174
|
+
.object({
|
|
175
|
+
id: z.string().min(1),
|
|
176
|
+
action: z.literal('cancel'),
|
|
177
|
+
suggestions: z.literal(true).optional(),
|
|
178
|
+
})
|
|
137
179
|
.strict(),
|
|
138
|
-
z.object({ id: z.string().min(1), action: z.literal('decline') }).strict(),
|
|
139
|
-
z.object({ id: z.string().min(1), action: z.literal('cancel') }).strict(),
|
|
140
180
|
]);
|
|
141
181
|
const optionalTurnId = z.string().min(1).optional();
|
|
142
182
|
const eventEnvelope = (event, data) => z.object({ event: z.literal(event), data });
|
|
@@ -206,6 +246,11 @@ export const assistantWireEventSchema = z.union([
|
|
|
206
246
|
continuation: z.string().min(1),
|
|
207
247
|
expiresAt: z.string().min(1),
|
|
208
248
|
})),
|
|
249
|
+
eventEnvelope('suggested_prompts', z.object({
|
|
250
|
+
turnId: optionalTurnId,
|
|
251
|
+
phase: z.enum(['initial', 'follow_up']),
|
|
252
|
+
prompts: z.array(z.string().trim().min(1).max(240)).max(3),
|
|
253
|
+
})),
|
|
209
254
|
eventEnvelope('error', z.object({
|
|
210
255
|
turnId: optionalTurnId,
|
|
211
256
|
code: z.string().min(1),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noodleseed/assistant",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.29.0",
|
|
4
4
|
"description": "Embed the Noodle Seed customer-branded assistant in your web app with managed or framework-owned UI, a framework-neutral MCP App host, a DOM-free client, and a backend session helper. Authoring and deploying the server is @noodleseed/one.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noodleseed/one",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.149.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Noodle CLI by Noodle Seed — author, run, and deploy declarative MCP servers. Embedding the assistant in your own web app is @noodleseed/assistant.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -236,7 +236,7 @@
|
|
|
236
236
|
"@modelcontextprotocol/client": "2.0.0",
|
|
237
237
|
"@modelcontextprotocol/server": "2.0.0",
|
|
238
238
|
"@noodle-borg/admission-limits": "0.0.0",
|
|
239
|
-
"@noodle-borg/agent-kit": "0.
|
|
239
|
+
"@noodle-borg/agent-kit": "0.88.0",
|
|
240
240
|
"@noodle-borg/app-audit": "0.0.0",
|
|
241
241
|
"@noodle-borg/app-package": "0.0.0",
|
|
242
242
|
"@noodle-borg/assistant-gateway": "0.0.0",
|