@noodleseed/one 0.147.0 → 0.148.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.
Files changed (39) hide show
  1. package/node_modules/@noodle-borg/admission-limits/dist/envelope.d.ts +10 -0
  2. package/node_modules/@noodle-borg/admission-limits/dist/envelope.js +8 -0
  3. package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +1 -1
  4. package/node_modules/@noodle-borg/agent-kit/dist/skill-embedded-assistant-ref.js +3 -1
  5. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-guide.d.ts +2 -0
  6. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-guide.js +6 -0
  7. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-model-context.js +145 -0
  8. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-store.d.ts +34 -1
  9. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-store.js +53 -1
  10. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-suggestions.js +69 -0
  11. package/node_modules/@noodle-borg/assistant-gateway/dist/model-request.d.ts +77 -0
  12. package/node_modules/@noodle-borg/assistant-gateway/dist/model-runtime.js +2 -0
  13. package/node_modules/@noodle-borg/assistant-gateway/dist/model-stream.d.ts +35 -0
  14. package/node_modules/@noodle-borg/assistant-gateway/dist/portable.d.ts +1 -0
  15. package/node_modules/@noodle-borg/assistant-gateway/dist/portable.js +1 -0
  16. package/node_modules/@noodle-borg/assistant-gateway/dist/public-turn.d.ts +53 -2
  17. package/node_modules/@noodle-borg/assistant-gateway/dist/public-turn.js +110 -0
  18. package/node_modules/@noodle-borg/assistant-gateway/dist/session-target.d.ts +27 -0
  19. package/node_modules/@noodle-borg/assistant-gateway/dist/session-target.js +50 -0
  20. package/node_modules/@noodle-borg/assistant-gateway/package.json +1 -1
  21. package/node_modules/@noodle-borg/authoring/dist/assistant.d.ts +16 -0
  22. package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +12 -0
  23. package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +5 -0
  24. package/node_modules/@noodle-borg/service/dist/invocation-context.js +1 -12
  25. package/node_modules/@noodle-borg/service/dist/routes/assistant-agent.js +104 -156
  26. package/node_modules/@noodle-borg/service/dist/routes/assistant-appearance.js +1 -3
  27. package/node_modules/@noodle-borg/service/dist/routes/assistant-dispatch.js +5 -0
  28. package/node_modules/@noodle-borg/service/dist/routes/assistant-interaction-stream.js +11 -4
  29. package/node_modules/@noodle-borg/service/dist/routes/assistant-interactions.js +17 -8
  30. package/node_modules/@noodle-borg/service/dist/routes/assistant-route-http.js +1 -0
  31. package/node_modules/@noodle-borg/service/dist/routes/assistant-session-target.js +4 -47
  32. package/node_modules/@noodle-borg/service/dist/routes/assistant-suggestions.js +106 -0
  33. package/node_modules/@noodle-borg/service/dist/routes/assistant-transcript.js +6 -1
  34. package/node_modules/@noodle-borg/service/dist/routes/assistant.js +21 -4
  35. package/node_modules/@noodle-borg/wire-contracts/dist/assistant.d.ts +38 -0
  36. package/node_modules/@noodle-borg/wire-contracts/dist/assistant.js +49 -4
  37. package/node_modules/@noodleseed/assistant/package.json +1 -1
  38. package/package.json +1 -1
  39. package/node_modules/@noodle-borg/service/dist/routes/assistant-public-turn.js +0 -37
@@ -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 = { entries: visibleTranscript(session.history) };
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';
@@ -191,13 +190,16 @@ export async function handleAssistantTurn(req, res, deps) {
191
190
  let turn;
192
191
  let message;
193
192
  let resumedTool;
193
+ let suggestionsRequested = false;
194
194
  if (isRecord(body.value) && 'resume' in body.value) {
195
195
  // The one-shot post-sign-in resume (issue #1177): no user text, the pending intent is server
196
196
  // state. Admission runs FIRST so a refused resume stays armed for a retry; the consume is a
197
197
  // single statement, so two requests racing the arm cannot both run it.
198
- if (!assistantResumeTurnRequestSchema.safeParse(body.value).success) {
198
+ const resumeResult = assistantResumeTurnRequestSchema.safeParse(body.value);
199
+ if (!resumeResult.success) {
199
200
  return sendJson(res, 400, { error: 'invalid turn request' });
200
201
  }
202
+ suggestionsRequested = resumeResult.data.suggestions === true;
201
203
  const refusal = await refusePublicTurn(deps, session, 'resume', addressBucket);
202
204
  if (refusal) {
203
205
  captureAssistantUsage(deps.captureRequestEvent, assistantRefusedTurnUsageRequestEvent(session, refusal.code, performance.now() - usageStartedAt));
@@ -242,6 +244,7 @@ export async function handleAssistantTurn(req, res, deps) {
242
244
  return sendJson(res, 400, { error: 'invalid turn request' });
243
245
  }
244
246
  turn = turnResult.data;
247
+ suggestionsRequested = turn.suggestions === true;
245
248
  message = turn.message;
246
249
  if (message.trim().length === 0) {
247
250
  return sendJson(res, 400, { error: '"message" must be a non-empty string' });
@@ -296,7 +299,15 @@ export async function handleAssistantTurn(req, res, deps) {
296
299
  };
297
300
  let turnCompleted = false;
298
301
  try {
299
- await runAgentTurn(target, session, message.trim(), invocationContext, deps, emit, turn?.modelContext, turn?.pageContext, stats);
302
+ if (suggestionsRequested) {
303
+ try {
304
+ await deps.store.replaceLatestSuggestions(session.id, undefined);
305
+ }
306
+ catch {
307
+ // Suggestions are optional; the assistant turn remains authoritative.
308
+ }
309
+ }
310
+ await runAgentTurn(target, session, message.trim(), invocationContext, deps, emit, turn?.modelContext, turn?.pageContext, stats, suggestionsRequested);
300
311
  turnCompleted = true;
301
312
  }
302
313
  catch (error) {
@@ -367,6 +378,12 @@ export async function handleAssistantAppRequest(req, res, deps) {
367
378
  return sendJson(res, 409, { error: 'assistant deployment is unavailable' });
368
379
  const method = body.value.method;
369
380
  const params = body.value.params;
381
+ // Answered before any resolution work, so a refused browser agent costs nothing (ADR 0220). The
382
+ // marker is client-declared: it selects a budget, never authority, which the session below decides.
383
+ const bridged = method === 'tools/call' && body.value.bridge === 'webmcp';
384
+ const refusal = bridged ? await refuseBridgeToolCall(deps, session) : undefined;
385
+ if (refusal)
386
+ return sendJson(res, refusal.status, { error: refusal.message, code: refusal.code });
370
387
  const knowledge = await resolveAssistantKnowledge(target.served);
371
388
  if (method === 'tools/list') {
372
389
  return sendJson(res, 200, mapToolsList(target.served.artifact, session.caller, {
@@ -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.object({ resume: z.literal(true) }).strict();
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({ id: z.string().min(1), action: z.literal('accept'), content: z.json().optional() })
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.27.0",
3
+ "version": "1.28.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.147.0",
3
+ "version": "0.148.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",
@@ -1,37 +0,0 @@
1
- import { ADMISSION_DEFAULTS } from '@noodle-borg/admission-limits/portable';
2
- import { admitPublicTurn } from '@noodle-borg/assistant-gateway/portable';
3
- import { now } from './assistant-route-http.js';
4
- export async function refusePublicTurn(deps, session, message,
5
- /** Hashed by the caller; a raw address never reaches this far. */
6
- addressBucket) {
7
- const publicEmbedId = session.publicEmbedId;
8
- if (publicEmbedId === undefined)
9
- return undefined;
10
- const embeds = deps.publicEmbeds;
11
- const counters = deps.admissionCounters;
12
- if (!embeds || !counters) {
13
- // Fail closed. A public session that reached a service with no counters would run unbounded
14
- // against the customer's model budget, which is the one outcome the envelope exists to prevent.
15
- return {
16
- status: 503,
17
- code: 'admission_unavailable',
18
- message: 'assistant is unavailable right now',
19
- };
20
- }
21
- const result = await admitPublicTurn({ sessionId: session.id, publicEmbedId, message, ...(addressBucket ? { addressBucket } : {}) }, deps.admissionEnvelope ?? ADMISSION_DEFAULTS, {
22
- counters,
23
- embeds,
24
- resolveBudgetBounds: async () => session.modelSource !== 'noodle-managed'
25
- ? undefined
26
- : (await deps.managedModelResolver?.resolve({
27
- tenant: session.tenant,
28
- deploymentId: session.deploymentId,
29
- }))?.publicAdmission,
30
- consumeTurn: (id, limit) => deps.store.consumeTurn(id, limit),
31
- now: () => now(deps),
32
- });
33
- return result.ok
34
- ? undefined
35
- : { status: result.status, code: result.code, message: result.message };
36
- }
37
- //# sourceMappingURL=assistant-public-turn.js.map