@anuma/sdk 1.0.0 → 1.1.1

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 (87) hide show
  1. package/README.md +54 -53
  2. package/dist/api/spec.cjs +30 -0
  3. package/dist/api/spec.d.mts +3 -0
  4. package/dist/api/spec.d.ts +3 -0
  5. package/dist/api/spec.mjs +12 -0
  6. package/dist/chunk-4ETHWSXJ.mjs +25 -0
  7. package/dist/client/index.cjs +2344 -0
  8. package/dist/client/index.d.mts +10368 -0
  9. package/dist/client/index.d.ts +10368 -0
  10. package/dist/client/index.mjs +2168 -0
  11. package/dist/clientConfig-WITO4T6L.mjs +12 -0
  12. package/dist/constants/index.cjs +30 -0
  13. package/dist/constants/index.d.mts +20 -0
  14. package/dist/constants/index.d.ts +20 -0
  15. package/dist/constants/index.mjs +5 -0
  16. package/dist/design/index.cjs +1523 -0
  17. package/dist/design/index.d.mts +561 -0
  18. package/dist/design/index.d.ts +561 -0
  19. package/dist/design/index.mjs +1468 -0
  20. package/dist/expo/chunk-3HZFU3PV.mjs +5283 -0
  21. package/dist/expo/chunk-DFVCS3QQ.mjs +246 -0
  22. package/dist/{react/chunk-MJJIYFAX.mjs → expo/chunk-GYKJOWEO.mjs} +13 -1
  23. package/dist/expo/chunk-Y7XT2VNQ.mjs +646 -0
  24. package/dist/expo/clientConfig-VPOGQRNP.mjs +12 -0
  25. package/dist/expo/consolidate-HEMQGOUD.mjs +131 -0
  26. package/dist/expo/index.cjs +16904 -6750
  27. package/dist/expo/index.d.mts +5755 -1474
  28. package/dist/expo/index.d.ts +5755 -1474
  29. package/dist/expo/index.mjs +9718 -6182
  30. package/dist/expo/recall-MCB3TVBW.mjs +8 -0
  31. package/dist/index.cjs +2992 -42
  32. package/dist/index.d.mts +7719 -1835
  33. package/dist/index.d.ts +7719 -1835
  34. package/dist/index.mjs +2859 -39
  35. package/dist/next/index.cjs +6 -2
  36. package/dist/next/index.d.mts +24 -1
  37. package/dist/next/index.d.ts +24 -1
  38. package/dist/next/index.mjs +6 -2
  39. package/dist/pii/transformers.cjs +126 -0
  40. package/dist/pii/transformers.d.mts +103 -0
  41. package/dist/pii/transformers.d.ts +103 -0
  42. package/dist/pii/transformers.mjs +90 -0
  43. package/dist/react/chunk-DFVCS3QQ.mjs +246 -0
  44. package/dist/react/chunk-ELDXIXRE.mjs +5656 -0
  45. package/dist/{expo/chunk-MJJIYFAX.mjs → react/chunk-GYKJOWEO.mjs} +13 -1
  46. package/dist/react/chunk-TP6CJZ25.mjs +645 -0
  47. package/dist/react/clientConfig-VPOGQRNP.mjs +12 -0
  48. package/dist/react/consolidate-IJH7LD2T.mjs +131 -0
  49. package/dist/react/index.cjs +25683 -11143
  50. package/dist/react/index.d.mts +7399 -1393
  51. package/dist/react/index.d.ts +7399 -1393
  52. package/dist/react/index.mjs +16709 -9196
  53. package/dist/react/recall-CSOMIT4H.mjs +8 -0
  54. package/dist/server/chunk-FMGZTVXQ.mjs +62 -0
  55. package/dist/server/chunk-G3DC7QBH.mjs +19457 -0
  56. package/dist/server/chunk-PXN5Q3MD.mjs +643 -0
  57. package/dist/server/chunk-RCCKRTBI.mjs +230 -0
  58. package/dist/server/clientConfig-GMADOBPK.mjs +12 -0
  59. package/dist/server/consolidate-A2ZIX5WF.mjs +131 -0
  60. package/dist/server/index.cjs +32161 -0
  61. package/dist/server/index.d.mts +7319 -0
  62. package/dist/server/index.d.ts +7319 -0
  63. package/dist/server/index.mjs +11217 -0
  64. package/dist/server/recall-LFYTTIIY.mjs +8 -0
  65. package/dist/tools/index.cjs +12682 -34
  66. package/dist/tools/index.d.mts +2733 -18
  67. package/dist/tools/index.d.ts +2733 -18
  68. package/dist/tools/index.mjs +12565 -33
  69. package/dist/utils/index.cjs +165 -0
  70. package/dist/utils/index.d.mts +14 -0
  71. package/dist/utils/index.d.ts +14 -0
  72. package/dist/utils/index.mjs +145 -0
  73. package/dist/vercel/index.d.mts +0 -3
  74. package/dist/vercel/index.d.ts +0 -3
  75. package/package.json +127 -16
  76. package/dist/expo/chunk-LJYAMK62.mjs +0 -25
  77. package/dist/expo/chunk-PJCZO4BQ.mjs +0 -25
  78. package/dist/expo/clientConfig-2MI4KULF.mjs +0 -10
  79. package/dist/expo/clientConfig-QDAXFL3W.mjs +0 -10
  80. package/dist/expo/clientConfig-R4IOW7I2.mjs +0 -10
  81. package/dist/react/chunk-LJYAMK62.mjs +0 -25
  82. package/dist/react/chunk-PJCZO4BQ.mjs +0 -25
  83. package/dist/react/clientConfig-2MI4KULF.mjs +0 -10
  84. package/dist/react/clientConfig-QDAXFL3W.mjs +0 -10
  85. package/dist/react/clientConfig-R4IOW7I2.mjs +0 -10
  86. package/dist/tools/chunk-KDFGY4SK.mjs +0 -13
  87. package/dist/tools/clientConfig-RMDOT5YM.mjs +0 -10
@@ -1,24 +1,80 @@
1
+ type LlmapiChatCompletionTool = {
2
+ [key: string]: unknown;
3
+ };
4
+
5
+ type ToolExecutionErrorType = "parse" | "timeout" | "execution";
6
+ type ToolExecutionResult = {
7
+ result?: unknown;
8
+ error?: string;
9
+ /** Distinguishes parse errors, timeouts, and execution failures. */
10
+ errorType?: ToolExecutionErrorType;
11
+ };
12
+
1
13
  /**
2
- * Google Calendar tool definition for the chat system.
3
- * This tool allows the LLM to list events from the user's Google Calendar.
14
+ * Tool executor function type
4
15
  */
16
+ type ToolExecutor = (args: Record<string, unknown>) => Promise<unknown> | unknown;
5
17
  /**
6
- * Tool configuration type that extends the SDK's tool type with executor support.
7
- * The SDK runtime supports these properties even though the base types don't include them.
8
- *
9
- * Note: Client tools use "arguments" internally. The SDK converts this to "parameters"
10
- * when sending to the API (see serverTools.ts mergeTools function).
18
+ * Tool configuration with optional executor
11
19
  */
12
- interface ToolConfig {
13
- type: 'function';
14
- function: {
15
- name: string;
16
- description: string;
17
- arguments: Record<string, unknown>;
18
- };
19
- executor?: (args: Record<string, unknown>) => Promise<unknown> | unknown;
20
- autoExecute?: boolean;
21
- }
20
+ type ToolConfig = LlmapiChatCompletionTool & {
21
+ /**
22
+ * Function to execute when the tool is called.
23
+ * If provided, the tool will be executed automatically when the LLM calls it.
24
+ * If not provided, an onToolCall event will be emitted instead.
25
+ */
26
+ executor?: ToolExecutor;
27
+ /**
28
+ * Whether to skip sending the tool result back to the model as a continuation.
29
+ * When true, the tool is executed but its result is not fed back to the model.
30
+ * Useful for display-only tools (charts, weather cards) that render client-side
31
+ * and don't need a model follow-up.
32
+ */
33
+ skipContinuation?: boolean;
34
+ /**
35
+ * Whether to remove this tool from the tools list after it has been
36
+ * successfully executed. This prevents smaller models from calling
37
+ * the same tool repeatedly in continuation requests.
38
+ *
39
+ * Only applies when the tool has an executor. The tool is kept if
40
+ * execution fails, allowing the model to retry.
41
+ */
42
+ removeAfterExecution?: boolean;
43
+ /**
44
+ * Timeout in milliseconds for this tool's executor.
45
+ * Overrides the default 30-second timeout.
46
+ * Set to `Infinity` to disable the timeout (e.g. for interactive tools
47
+ * that wait for user input).
48
+ * @default 30000
49
+ */
50
+ executorTimeout?: number;
51
+ /**
52
+ * When PII redaction is active, de-anonymize this tool's call arguments
53
+ * (restore original values for any `[CATEGORY_n]` placeholders) BEFORE the
54
+ * executor runs, using the same redactor that redacted the request this turn.
55
+ * Opt in only for tools that act on data that stays on the device — e.g.
56
+ * `memory_vault_save`, which must persist the real value, not a placeholder.
57
+ * Leave off for tools that forward arguments off-device (connectors), so PII
58
+ * stays redacted on the wire.
59
+ * @default false
60
+ */
61
+ deAnonymizeArgs?: boolean;
62
+ /**
63
+ * Tool names that this tool depends on. When multiple tools are called in
64
+ * the same response, tools with `dependsOn` will wait for the named tools
65
+ * to finish executing before starting.
66
+ *
67
+ * Example: `display_slides` depends on `create_file` because it reads
68
+ * files from IndexedDB that `create_file` writes.
69
+ */
70
+ dependsOn?: string[];
71
+ };
72
+
73
+ /**
74
+ * Google Calendar tool definition for the chat system.
75
+ * This tool allows the LLM to list events from the user's Google Calendar.
76
+ */
77
+
22
78
  interface ListEventsArgs {
23
79
  timeMin?: string;
24
80
  timeMax?: string;
@@ -70,6 +126,377 @@ declare function createGoogleCalendarUpdateEventTool(getAccessToken: () => strin
70
126
  */
71
127
  declare function createChatTools(getAccessToken: () => string | null, requestCalendarAccess: () => Promise<string>): ToolConfig[];
72
128
 
129
+ /**
130
+ * Generic factories for creating client-side UI interaction tools.
131
+ *
132
+ * These factories handle the boilerplate of wiring tool executors to the
133
+ * UIInteractionProvider lifecycle (ID generation, context injection,
134
+ * promise resolution). Apps provide the tool schema and any custom logic.
135
+ */
136
+
137
+ /**
138
+ * Minimal context interface required by tool factories.
139
+ * Matches the methods provided by UIInteractionProvider.
140
+ */
141
+ type UIInteractionContext = {
142
+ createInteraction: (id: string, type: string, data: unknown) => Promise<unknown>;
143
+ createDisplayInteraction: (id: string, displayType: string, data: unknown, result: unknown, toolVersion?: number, replacesInteractionId?: string) => void;
144
+ };
145
+ /**
146
+ * Options for context injection, following the same getter pattern
147
+ * as createGoogleCalendarTool and createDriveTools.
148
+ */
149
+ type CreateUIToolsOptions = {
150
+ /** Returns the current UIInteractionContext (or null if unavailable) */
151
+ getContext: () => UIInteractionContext | null;
152
+ /** Returns the ID of the last message in the conversation (used for anchoring) */
153
+ getLastMessageId?: () => string | undefined;
154
+ };
155
+ /**
156
+ * Configuration for an interactive tool that waits for user input.
157
+ */
158
+ type InteractiveToolConfig = {
159
+ /** Tool name as called by the LLM (e.g. "prompt_user_choice") */
160
+ name: string;
161
+ /** Tool description shown to the LLM */
162
+ description: string;
163
+ /** JSON Schema for tool parameters */
164
+ parameters: Record<string, unknown>;
165
+ /** Interaction type string used by the provider (e.g. "choice", "form") */
166
+ interactionType: string;
167
+ /** Optional validation of LLM-provided args. Return false to cancel. */
168
+ validate?: (args: Record<string, unknown>) => boolean;
169
+ /** Transform args before storing as interaction data */
170
+ mapArgs?: (args: Record<string, unknown>) => Record<string, unknown>;
171
+ /** Transform the user's result before returning to the LLM */
172
+ mapResult?: (result: Record<string, unknown>, args: Record<string, unknown>) => unknown;
173
+ };
174
+ /**
175
+ * Migration map for a display tool.
176
+ * Keys are "fromVersion->toVersion" strings (e.g. "1->2").
177
+ * Each function receives the stored result at fromVersion and returns the
178
+ * result upgraded to toVersion.
179
+ *
180
+ * @example
181
+ * ```typescript
182
+ * migrations: {
183
+ * "1->2": (old) => ({ ...old, newField: old.legacyField ?? defaultValue }),
184
+ * }
185
+ * ```
186
+ */
187
+ type DisplayToolMigrations = {
188
+ [key: `${number}->${number}`]: (data: unknown) => unknown;
189
+ };
190
+ /**
191
+ * Configuration for a display-only tool that renders data without blocking.
192
+ */
193
+ type DisplayToolConfig<TArgs = Record<string, unknown>, TResult = unknown> = {
194
+ /** Tool name as called by the LLM (e.g. "display_weather") */
195
+ name: string;
196
+ /** Tool description shown to the LLM */
197
+ description: string;
198
+ /** JSON Schema for tool parameters */
199
+ parameters: Record<string, unknown>;
200
+ /** Display type string used to dispatch rendering (e.g. "weather") */
201
+ displayType: string;
202
+ /** Execute function that fetches/computes the display data */
203
+ execute: (args: TArgs) => Promise<TResult> | TResult;
204
+ /**
205
+ * Schema version for the result format. Increment when the result shape
206
+ * changes in a backward-incompatible way. Default: 1.
207
+ */
208
+ version?: number;
209
+ /**
210
+ * Migration functions keyed by "fromVersion->toVersion".
211
+ * Used to upgrade stored results to the current version on restore.
212
+ */
213
+ migrations?: DisplayToolMigrations;
214
+ /**
215
+ * Optional callback that returns the interaction ID this display should replace.
216
+ * When set, only the specified interaction is removed instead of all interactions
217
+ * of the same displayType. Useful for updating a specific app instance.
218
+ */
219
+ getReplacesInteractionId?: (args: TArgs) => string | undefined;
220
+ };
221
+ /**
222
+ * Migrate a stored display result from an older version to the current version.
223
+ *
224
+ * Runs the migration chain step-by-step: fromVersion → fromVersion+1 → … → toVersion.
225
+ * Steps with no registered migration function are skipped (result passes through unchanged).
226
+ * Returns the original result unchanged if fromVersion >= toVersion.
227
+ *
228
+ * @example
229
+ * ```typescript
230
+ * const migrated = migrateDisplayResult(storedResult, 1, 3, {
231
+ * "1->2": (v1) => ({ ...v1, added: v1.old ?? 0 }),
232
+ * "2->3": (v2) => ({ ...v2, renamed: v2.added }),
233
+ * });
234
+ * ```
235
+ */
236
+ declare function migrateDisplayResult(result: unknown, fromVersion: number, toVersion: number, migrations: DisplayToolMigrations): unknown;
237
+ /**
238
+ * Create an interactive tool that waits for user input.
239
+ *
240
+ * When the LLM calls this tool, it creates a pending interaction in
241
+ * the UIInteractionProvider and returns a Promise that resolves when
242
+ * the user responds via the UI.
243
+ *
244
+ * @example
245
+ * ```typescript
246
+ * const choiceTool = createInteractiveTool(
247
+ * { getContext: () => uiInteraction, getLastMessageId: () => lastMsgId },
248
+ * {
249
+ * name: "prompt_user_choice",
250
+ * description: "Present choices to the user",
251
+ * parameters: { type: "object", properties: { ... } },
252
+ * interactionType: "choice",
253
+ * validate: (args) => args.title && args.options?.length >= 2,
254
+ * }
255
+ * );
256
+ * ```
257
+ */
258
+ declare function createInteractiveTool(options: CreateUIToolsOptions, config: InteractiveToolConfig): ToolConfig;
259
+ /**
260
+ * Create a display-only tool that renders data without blocking.
261
+ *
262
+ * When the LLM calls this tool, it executes the provided function,
263
+ * stores the result as a resolved display interaction for rendering,
264
+ * and immediately returns the data to the LLM.
265
+ *
266
+ * @example
267
+ * ```typescript
268
+ * const weatherTool = createDisplayTool(
269
+ * { getContext: () => uiInteraction, getLastMessageId: () => lastMsgId },
270
+ * {
271
+ * name: "display_weather",
272
+ * description: "Show weather for a location",
273
+ * parameters: { type: "object", properties: { location: { type: "string" } } },
274
+ * displayType: "weather",
275
+ * execute: async (args) => fetchWeatherData(args.location),
276
+ * }
277
+ * );
278
+ * ```
279
+ */
280
+ declare function createDisplayTool<TArgs = Record<string, unknown>, TResult = unknown>(options: CreateUIToolsOptions, config: DisplayToolConfig<TArgs, TResult>): ToolConfig;
281
+
282
+ /**
283
+ * Chart display tool factory.
284
+ *
285
+ * Creates a client-side tool that renders bar, line, area, and pie charts
286
+ * inline in the chat. The tool validates the LLM-provided data and passes
287
+ * it through for rendering by the ChartCard component.
288
+ */
289
+
290
+ type ChartDataPoint = Record<string, string | number>;
291
+ type DisplayChartResult = {
292
+ chartType: "bar" | "line" | "area" | "pie";
293
+ title?: string;
294
+ data: ChartDataPoint[];
295
+ dataKeys: string[];
296
+ xAxisKey?: string;
297
+ colors?: Record<string, string>;
298
+ } | {
299
+ error: string;
300
+ };
301
+ /**
302
+ * Create a display_chart tool that renders charts inline in the chat.
303
+ *
304
+ * @example
305
+ * ```typescript
306
+ * import { createChartTool } from "@anuma/sdk/tools";
307
+ *
308
+ * const chartTool = createChartTool({
309
+ * getContext: () => uiInteraction,
310
+ * getLastMessageId: () => lastMsgId,
311
+ * });
312
+ * ```
313
+ */
314
+ declare function createChartTool(options: CreateUIToolsOptions): ToolConfig;
315
+
316
+ type DisplayPhoneCallOfferResult = {
317
+ recipientName: string;
318
+ phoneNumber: string;
319
+ objective: string;
320
+ suggestedQuestions?: string[];
321
+ contextSummary?: string;
322
+ } | {
323
+ error: string;
324
+ };
325
+ declare function createPhoneCallOfferTool(options: CreateUIToolsOptions): ToolConfig;
326
+
327
+ type DisplayConnectorResult = {
328
+ connectorId: string;
329
+ reason?: string;
330
+ } | {
331
+ error: string;
332
+ };
333
+ declare function createConnectorOfferTool(options: CreateUIToolsOptions): ToolConfig;
334
+
335
+ /**
336
+ * Choice interaction tool factory.
337
+ *
338
+ * Creates a client-side tool that presents an interactive choice picker
339
+ * inline in the chat. The tool blocks until the user selects an option.
340
+ */
341
+
342
+ type ChoiceOption = {
343
+ /** Unique identifier for this option */
344
+ value: string;
345
+ /** Display text for this option */
346
+ label: string;
347
+ /** Optional description or additional info */
348
+ description?: string;
349
+ };
350
+ /**
351
+ * Create a prompt_user_choice tool that renders an interactive choice picker.
352
+ *
353
+ * When the LLM calls this tool, it presents a set of options to the user
354
+ * inline in the chat. Execution blocks until the user makes a selection,
355
+ * then the result is returned to the LLM.
356
+ *
357
+ * @example
358
+ * ```typescript
359
+ * import { createChoiceTool } from "@anuma/sdk/tools";
360
+ *
361
+ * const choiceTool = createChoiceTool({
362
+ * getContext: () => uiInteraction,
363
+ * getLastMessageId: () => lastMsgId,
364
+ * });
365
+ * ```
366
+ */
367
+ declare function createChoiceTool(options: CreateUIToolsOptions): ToolConfig;
368
+
369
+ /**
370
+ * Form interaction tool factory.
371
+ *
372
+ * Creates a client-side tool that renders an interactive form inline in
373
+ * the chat. The tool blocks until the user fills out and submits the form.
374
+ */
375
+
376
+ type FormFieldType = "text" | "textarea" | "select" | "toggle" | "date" | "slider";
377
+ type FormFieldOption = {
378
+ value: string;
379
+ label: string;
380
+ };
381
+ type FormField = {
382
+ /** Unique field identifier (used as key in result) */
383
+ name: string;
384
+ /** Display label for the field */
385
+ label: string;
386
+ /** Field type */
387
+ type: FormFieldType;
388
+ /** Optional help text shown below the label */
389
+ description?: string;
390
+ /** Placeholder text for text/textarea/select fields */
391
+ placeholder?: string;
392
+ /** Options for select fields */
393
+ options?: FormFieldOption[];
394
+ /** Default value */
395
+ defaultValue?: string | boolean | number;
396
+ /** Minimum value for slider fields */
397
+ min?: number;
398
+ /** Maximum value for slider fields */
399
+ max?: number;
400
+ /** Step increment for slider fields */
401
+ step?: number;
402
+ };
403
+ /**
404
+ * Create a prompt_user_form tool that renders an interactive form.
405
+ *
406
+ * When the LLM calls this tool, it presents a form with various field types
407
+ * to the user inline in the chat. Execution blocks until the user submits,
408
+ * then all field values are returned to the LLM at once.
409
+ *
410
+ * @example
411
+ * ```typescript
412
+ * import { createFormTool } from "@anuma/sdk/tools";
413
+ *
414
+ * const formTool = createFormTool({
415
+ * getContext: () => uiInteraction,
416
+ * getLastMessageId: () => lastMsgId,
417
+ * });
418
+ * ```
419
+ */
420
+ declare function createFormTool(options: CreateUIToolsOptions): ToolConfig;
421
+
422
+ /**
423
+ * Weather display tool factory.
424
+ *
425
+ * Creates a client-side tool that fetches weather data from the Open-Meteo
426
+ * API and renders it as a visual card inline in the chat.
427
+ */
428
+
429
+ type ForecastDay = {
430
+ date: string;
431
+ weatherCode: number;
432
+ temperatureMax: number;
433
+ temperatureMin: number;
434
+ };
435
+ type DisplayWeatherResult = {
436
+ location: string;
437
+ country?: string;
438
+ temperature: number;
439
+ apparentTemperature: number;
440
+ humidity: number;
441
+ windSpeed: number;
442
+ weatherCode: number;
443
+ isDay: boolean;
444
+ useFahrenheit: boolean;
445
+ forecast?: ForecastDay[];
446
+ _meta?: {
447
+ location: string;
448
+ };
449
+ } | {
450
+ error: string;
451
+ _meta?: {
452
+ location: string;
453
+ };
454
+ };
455
+ interface CreateWeatherToolOptions {
456
+ /**
457
+ * Detect whether to fetch Fahrenheit/mph instead of Celsius/km/h.
458
+ * Defaults to checking `navigator.language` for US/LR/MM regions.
459
+ */
460
+ detectUseFahrenheit?: () => boolean;
461
+ /** Called when a fetch error occurs (after retry). */
462
+ onError?: (error: Error, ctx: {
463
+ location: string;
464
+ }) => void;
465
+ /** Timeout in milliseconds for fetch requests. Defaults to 10000. */
466
+ timeoutMs?: number;
467
+ /**
468
+ * Override the Open-Meteo geocoding API base URL.
469
+ * Defaults to `https://geocoding-api.open-meteo.com/v1`.
470
+ */
471
+ geocodingBaseUrl?: string;
472
+ /**
473
+ * Override the Open-Meteo forecast API base URL.
474
+ * Defaults to `https://api.open-meteo.com/v1`.
475
+ */
476
+ forecastBaseUrl?: string;
477
+ }
478
+ /**
479
+ * Create a display_weather tool that fetches and renders weather data.
480
+ *
481
+ * When the LLM calls this tool, it geocodes the location, fetches current
482
+ * weather and a 7-day forecast from Open-Meteo, and renders it as a card.
483
+ * The result is not sent back to the LLM (`skipContinuation: true`).
484
+ *
485
+ * Units are auto-detected from `navigator.language` (US/LR/MM → Fahrenheit
486
+ * + mph). Override with `weatherOptions.detectUseFahrenheit`.
487
+ *
488
+ * @example
489
+ * ```typescript
490
+ * import { createWeatherTool } from "@anuma/sdk/tools";
491
+ *
492
+ * const weatherTool = createWeatherTool(
493
+ * { getContext: () => uiInteraction, getLastMessageId: () => lastMsgId },
494
+ * { onError: (err, { location }) => logError("weather", err, { location }) }
495
+ * );
496
+ * ```
497
+ */
498
+ declare function createWeatherTool(options: CreateUIToolsOptions, weatherOptions?: CreateWeatherToolOptions): ToolConfig;
499
+
73
500
  /**
74
501
  * Google Drive tool definition for the chat system.
75
502
  * This tool allows the LLM to search files in the user's Google Drive.
@@ -113,9 +540,2297 @@ interface GetFileContentArgs {
113
540
  * Creates the Google Drive get file content tool
114
541
  */
115
542
  declare function createGoogleDriveGetContentTool(getAccessToken: () => string | null, requestDriveAccess: () => Promise<string>): ToolConfig;
543
+ interface CreateFileArgs {
544
+ name: string;
545
+ content: string;
546
+ mimeType?: string;
547
+ }
548
+ /**
549
+ * Creates the Google Drive create-file tool.
550
+ */
551
+ declare function createGoogleDriveCreateFileTool(getAccessToken: () => string | null, requestDriveAccess: () => Promise<string>): ToolConfig;
552
+ interface UpdateFileArgs {
553
+ fileId: string;
554
+ content: string;
555
+ mimeType?: string;
556
+ }
557
+ /**
558
+ * Creates the Google Drive update-file tool.
559
+ */
560
+ declare function createGoogleDriveUpdateFileTool(getAccessToken: () => string | null, requestDriveAccess: () => Promise<string>): ToolConfig;
116
561
  /**
117
562
  * Creates all Google Drive tools with the provided token context.
118
563
  */
119
564
  declare function createDriveTools(getAccessToken: () => string | null, requestDriveAccess: () => Promise<string>): ToolConfig[];
120
565
 
121
- export { type CalendarEvent, type CreateEventArgs, type DriveFile, type GetFileContentArgs, type ListEventsArgs, type ListRecentFilesArgs, type SearchFilesArgs, type ToolConfig, type UpdateEventArgs, createChatTools, createDriveTools, createGoogleCalendarCreateEventTool, createGoogleCalendarTool, createGoogleCalendarUpdateEventTool, createGoogleDriveGetContentTool, createGoogleDriveListRecentTool, createGoogleDriveSearchTool };
566
+ /**
567
+ * Notion MCP tool definitions for the chat system.
568
+ *
569
+ * These tools communicate with Notion's hosted MCP server using the
570
+ * Model Context Protocol (JSON-RPC 2.0). The MCP server handles all
571
+ * Notion API interactions - we just forward tool calls to it.
572
+ *
573
+ * MCP Server: https://mcp.notion.com/mcp (Streamable HTTP)
574
+ * Fallback: https://mcp.notion.com/sse (Server-Sent Events)
575
+ *
576
+ * @see https://developers.notion.com/guides/mcp/build-mcp-client
577
+ * @see https://modelcontextprotocol.io
578
+ */
579
+
580
+ interface NotionSearchArgs {
581
+ query: string;
582
+ query_type?: "internal" | "user";
583
+ filters?: Record<string, unknown>;
584
+ }
585
+ interface NotionFetchArgs {
586
+ id: string;
587
+ include_transcript?: boolean;
588
+ include_discussions?: boolean;
589
+ }
590
+ interface NotionCreatePagesArgs {
591
+ pages: Array<{
592
+ properties: Record<string, unknown>;
593
+ content: string;
594
+ }>;
595
+ parent?: Record<string, unknown>;
596
+ }
597
+ interface NotionUpdatePageArgs {
598
+ data: {
599
+ page_id: string;
600
+ command: string;
601
+ properties?: Record<string, unknown>;
602
+ new_str?: string;
603
+ selection_with_ellipsis?: string;
604
+ allow_deleting_content?: boolean;
605
+ };
606
+ }
607
+ interface NotionMovePagesArgs {
608
+ page_or_database_ids: string[];
609
+ new_parent: Record<string, unknown>;
610
+ }
611
+ /**
612
+ * MCP Tool: notion-search
613
+ * Semantic search over Notion workspace and connected sources, or user search
614
+ */
615
+ declare function createNotionSearchTool(getAccessToken: () => string | null, requestNotionAccess: () => Promise<string>): ToolConfig;
616
+ /**
617
+ * MCP Tool: notion-fetch
618
+ * Retrieves details about a Notion entity (page or database) by URL or ID
619
+ */
620
+ declare function createNotionFetchTool(getAccessToken: () => string | null, requestNotionAccess: () => Promise<string>): ToolConfig;
621
+ /**
622
+ * MCP Tool: notion-create-pages
623
+ * Creates one or more Notion pages with properties and content
624
+ */
625
+ declare function createNotionCreatePagesTool(getAccessToken: () => string | null, requestNotionAccess: () => Promise<string>): ToolConfig;
626
+ /**
627
+ * MCP Tool: notion-update-page
628
+ * Update a page's properties or content using command-based operations
629
+ */
630
+ declare function createNotionUpdatePageTool(getAccessToken: () => string | null, requestNotionAccess: () => Promise<string>): ToolConfig;
631
+ /**
632
+ * MCP Tool: notion-move-pages
633
+ * Move one or more pages/databases to a new parent
634
+ */
635
+ declare function createNotionMovePagesTool(getAccessToken: () => string | null, requestNotionAccess: () => Promise<string>): ToolConfig;
636
+ /**
637
+ * MCP Tool: notion-duplicate-page
638
+ * Duplicate a Notion page (completes asynchronously)
639
+ */
640
+ declare function createNotionDuplicatePageTool(getAccessToken: () => string | null, requestNotionAccess: () => Promise<string>): ToolConfig;
641
+ /**
642
+ * MCP Tool: notion-create-database
643
+ * Create a new Notion database with a properties schema
644
+ */
645
+ declare function createNotionCreateDatabaseTool(getAccessToken: () => string | null, requestNotionAccess: () => Promise<string>): ToolConfig;
646
+ /**
647
+ * MCP Tool: notion-update-data-source
648
+ * Update a data source's properties, name, or other attributes
649
+ */
650
+ declare function createNotionUpdateDataSourceTool(getAccessToken: () => string | null, requestNotionAccess: () => Promise<string>): ToolConfig;
651
+ /**
652
+ * MCP Tool: notion-create-comment
653
+ * Add a comment to a page, specific content, or reply to a discussion
654
+ */
655
+ declare function createNotionCreateCommentTool(getAccessToken: () => string | null, requestNotionAccess: () => Promise<string>): ToolConfig;
656
+ /**
657
+ * MCP Tool: notion-get-comments
658
+ * Get comments and discussions from a Notion page
659
+ */
660
+ declare function createNotionGetCommentsTool(getAccessToken: () => string | null, requestNotionAccess: () => Promise<string>): ToolConfig;
661
+ /**
662
+ * MCP Tool: notion-get-users
663
+ * List users in the workspace, get a specific user, or get self
664
+ */
665
+ declare function createNotionGetUsersTool(getAccessToken: () => string | null, requestNotionAccess: () => Promise<string>): ToolConfig;
666
+ /**
667
+ * MCP Tool: notion-get-teams
668
+ * Retrieve teams (teamspaces) in the workspace
669
+ */
670
+ declare function createNotionGetTeamsTool(getAccessToken: () => string | null, requestNotionAccess: () => Promise<string>): ToolConfig;
671
+ /**
672
+ * Create all Notion MCP tools
673
+ *
674
+ * Returns tools that communicate with Notion's hosted MCP server.
675
+ * The MCP server handles all Notion API interactions.
676
+ */
677
+ declare function createNotionTools(getAccessToken: () => string | null, requestNotionAccess: () => Promise<string>): ToolConfig[];
678
+ /**
679
+ * Get the MCP endpoints for direct access
680
+ */
681
+ declare function getMCPEndpoints(): {
682
+ http: string;
683
+ sse: string;
684
+ };
685
+ /**
686
+ * Call any MCP tool directly (for advanced use cases)
687
+ */
688
+ declare function callNotionMCPTool<T>(accessToken: string, toolName: string, args: Record<string, unknown>): Promise<T>;
689
+
690
+ /**
691
+ * GitHub tools for the chat system.
692
+ *
693
+ * Two tools:
694
+ * 1. github_get_authenticated_user — discover the user's identity and orgs
695
+ * 2. github_api — make any authenticated GitHub REST API call
696
+ *
697
+ * This lets the LLM use its training knowledge of the GitHub API directly,
698
+ * instead of us defining individual tools for every endpoint.
699
+ */
700
+
701
+ /**
702
+ * Create GitHub tools for the chat system.
703
+ *
704
+ * @param getAccessToken - Returns the current GitHub access token (or null)
705
+ * @param requestGitHubAccess - Triggers the OAuth flow and returns a token
706
+ */
707
+ declare function createGitHubTools(getAccessToken: () => string | null, requestGitHubAccess: () => Promise<string>): ToolConfig[];
708
+
709
+ /**
710
+ * Token-getter factory shared by every connector tool factory.
711
+ *
712
+ * Wraps a portal client's `mintConnectorToken(provider)` call in a
713
+ * per-instance access-token cache. The returned closure is the
714
+ * `getAccessToken: () => string | null` argument every tool factory takes.
715
+ *
716
+ * Cache rules (per-instance):
717
+ * - First call mints; subsequent calls return the cached token until
718
+ * `(expiresAt - refreshBeforeMs) <= now`, then re-mints.
719
+ * - On mint failure (`MintResult.ok === false`), the cache is left untouched;
720
+ * the failure is forwarded to `onNotConnected` (when applicable) or
721
+ * surfaced as `null` so the tool factory can emit a connector error.
722
+ * - A thrown mint call (portal unreachable, all retries exhausted) is
723
+ * swallowed and surfaced as `null` too — the closure's documented
724
+ * contract is `() => Promise<string | null>`, and letting a network
725
+ * error bubble would crash the tool executor instead of letting it
726
+ * emit the canonical `__anuma_connector_error_v1` result.
727
+ *
728
+ * Two tool calls in the same request reuse the same `PortalClient` instance,
729
+ * and so share this cache — that's where the "2 calls = 1 mint" caching
730
+ * guarantee in the runtime spec comes from.
731
+ */
732
+ /** Discriminated union returned by `mintConnectorToken`. */
733
+ type ConnectorMintResult = {
734
+ ok: true;
735
+ accessToken: string;
736
+ expiresAt: number;
737
+ } | {
738
+ ok: false;
739
+ error: ConnectorMintError;
740
+ };
741
+ /** Error variants from the mint endpoint, mirrored on `@anuma/agent-runtime`. */
742
+ type ConnectorMintError = {
743
+ code: "connector_not_connected";
744
+ provider: string;
745
+ connectUrl: string;
746
+ } | {
747
+ code: "scope_not_covered";
748
+ provider: string;
749
+ missingScopes: string[];
750
+ connectUrl: string;
751
+ } | {
752
+ code: "insufficient_scope";
753
+ required: string;
754
+ } | {
755
+ code: "upstream_unavailable";
756
+ retryAfterMs?: number;
757
+ } | {
758
+ code: "unknown";
759
+ message: string;
760
+ };
761
+ /**
762
+ * Minimal portal-client surface this helper depends on.
763
+ *
764
+ * `@anuma/agent-runtime` exposes a richer `PortalClient` that satisfies this
765
+ * shape. Keeping the SDK-side dependency structural avoids pulling the
766
+ * runtime package into every SDK consumer.
767
+ */
768
+ interface ConnectorTokenSource {
769
+ mintConnectorToken(provider: string): Promise<ConnectorMintResult>;
770
+ }
771
+ interface ConnectorTokenGetterOpts {
772
+ /** How early to refresh before expiry. @default 30_000 (30 s) */
773
+ refreshBeforeMs?: number;
774
+ /**
775
+ * Called when mint returns `connector_not_connected` or `scope_not_covered`.
776
+ * The default returns `null`, letting tool factories emit a structured
777
+ * connector error via `buildConnectorErrorResult`. Interactive surfaces
778
+ * (browser, mobile) can override to drive a connect UI and return a fresh
779
+ * token once the user finishes.
780
+ */
781
+ onNotConnected?: (err: ConnectorMintError) => Promise<string | null>;
782
+ /** Defaults to `Date.now`; injected for tests. */
783
+ now?: () => number;
784
+ }
785
+ /**
786
+ * Build a `() => Promise<string | null>` closure that mints + caches an
787
+ * upstream access token for the given logical provider.
788
+ *
789
+ * The closure is consumed verbatim by tool factories such as `createGmailTools`
790
+ * and `createDriveTools` — those factories expect a synchronous getter today,
791
+ * but Promise-returning getters are tolerated because tool executors are
792
+ * already async. (PR 7 in the plan shrinks the existing factories to call
793
+ * `await getToken()` internally.)
794
+ */
795
+ declare function createConnectorTokenGetter(client: ConnectorTokenSource, provider: string, opts?: ConnectorTokenGetterOpts): () => Promise<string | null>;
796
+
797
+ /**
798
+ * Canonical connector-error contract emitted by every connector tool factory.
799
+ *
800
+ * When the portal mint endpoint returns a 412 or 403, the tool factory does
801
+ * NOT throw — it returns the JSON string produced by `buildConnectorErrorResult`
802
+ * as the tool result. The post-loop parser in `@anuma/agent-runtime` walks the
803
+ * resulting tool-role messages and lifts any payload bearing the
804
+ * `__anuma_connector_error_v1` marker into a structured `ToolError[]` the
805
+ * caller can render alongside the LLM reply.
806
+ *
807
+ * The double-underscore + version suffix in the marker name keeps it
808
+ * collision-resistant against any tool that legitimately returns JSON
809
+ * containing the word "error".
810
+ */
811
+ /** Recognized connector error codes. */
812
+ type ConnectorErrorCode = "connector_not_connected" | "scope_not_covered" | "insufficient_scope" | "upstream_unavailable";
813
+ /** Marker key that distinguishes a connector error from any other tool JSON. */
814
+ declare const CONNECTOR_ERROR_MARKER: "__anuma_connector_error_v1";
815
+ /**
816
+ * Optional extras emitted alongside the core fields. Kept loose so the
817
+ * helper signature stays small and tool factories can opt into the
818
+ * fields they have available.
819
+ */
820
+ interface ConnectorErrorExtras {
821
+ /** For `scope_not_covered`: scopes still missing from the user's grant. */
822
+ missingScopes?: string[];
823
+ /** For `insufficient_scope`: the required scope identifier. */
824
+ required?: string;
825
+ }
826
+ /**
827
+ * Build the canonical connector-error JSON string. Every connector tool
828
+ * factory uses this helper so the marker convention is impossible to forget.
829
+ *
830
+ * Note: omitted optional fields are emitted as `undefined`, which
831
+ * `JSON.stringify` drops, leaving the key absent — matching the shape the
832
+ * post-loop parser expects.
833
+ *
834
+ * @param code One of the recognized {@link ConnectorErrorCode} values.
835
+ * @param provider Logical provider name (`"gmail"`, `"gdrive"`, etc.).
836
+ * @param connectUrl Optional connect URL surfaced to the user via the LLM reply.
837
+ * @param extras Optional `missingScopes` / `required` fields lifted by the
838
+ * runtime parser into `ToolErrorInfo`.
839
+ */
840
+ declare function buildConnectorErrorResult(code: ConnectorErrorCode, provider: string, connectUrl?: string, extras?: ConnectorErrorExtras): string;
841
+
842
+ /**
843
+ * Gmail tool factory for the chat system.
844
+ *
845
+ * Mirrors the shape of {@link "./googleDrive".createDriveTools}: the caller
846
+ * supplies a token getter and an interactive `requestAccess` fallback,
847
+ * and the factory returns a record of `ToolConfig` entries the chat loop
848
+ * can run. The token getter is expected to be one returned by
849
+ * `createConnectorTokenGetter`, but any `() => Promise<string | null>`
850
+ * compatible function works.
851
+ *
852
+ * Tool catalogue (matches portal scope `connector:gmail:*`):
853
+ * - `gmail_search_messages` — list message IDs + snippets matching a query
854
+ * - `gmail_get_message` — fetch headers + plaintext body for a single message
855
+ * - `gmail_create_draft` — create a draft message in the user's mailbox (does not send)
856
+ * - `gmail_send_message` — send a message via the user's mailbox
857
+ *
858
+ * Error contract: when the upstream Gmail API or the portal mint endpoint
859
+ * signals a connector-level failure (412 missing connector, 412 scope not
860
+ * covered, 401 token unusable), the tool returns the canonical
861
+ * `__anuma_connector_error_v1` JSON shape produced by
862
+ * `buildConnectorErrorResult`. The agent-runtime post-loop parser lifts
863
+ * these into `AgentResponse.toolErrors`. Tool executors never throw on
864
+ * these paths — the LLM is expected to surface the connect URL to the user.
865
+ */
866
+
867
+ /** Async getter returned by `createConnectorTokenGetter`. */
868
+ type GmailTokenGetter = () => Promise<string | null>;
869
+ /**
870
+ * Fallback used when no token is currently available. Server agents pass an
871
+ * implementation that throws (no interactive OAuth is possible). Browser
872
+ * surfaces pass a function that drives the connect flow and resolves with a
873
+ * fresh token (or `null` if the user declined).
874
+ */
875
+ type GmailRequestAccess = () => Promise<string | null>;
876
+ interface GmailSearchArgs {
877
+ query: string;
878
+ maxResults?: number;
879
+ }
880
+ interface GmailGetMessageArgs {
881
+ messageId: string;
882
+ format?: "metadata" | "full";
883
+ }
884
+ interface GmailSendMessageArgs {
885
+ to: string;
886
+ subject: string;
887
+ body: string;
888
+ cc?: string;
889
+ bcc?: string;
890
+ }
891
+ interface GmailMessageSummary {
892
+ id: string;
893
+ threadId: string;
894
+ snippet?: string;
895
+ }
896
+ interface GmailMessageDetail {
897
+ id: string;
898
+ threadId: string;
899
+ from?: string;
900
+ to?: string;
901
+ subject?: string;
902
+ date?: string;
903
+ snippet?: string;
904
+ body?: string;
905
+ }
906
+ /**
907
+ * Pull the structured error out of a `ConnectorMintError`. Unused in v1 — the
908
+ * `onNotConnected` opt is the place to translate `ConnectorMintError`. Exported
909
+ * so future tool factories can share the helper without duplicating the
910
+ * code → error-string mapping.
911
+ *
912
+ * @param err The mint error returned by the portal client.
913
+ * @param provider Logical provider name (`"gmail"`, `"gdrive"`, ...) used for
914
+ * error variants that don't carry their own provider field
915
+ * (`insufficient_scope`, `upstream_unavailable`, `unknown`).
916
+ * Required so non-Gmail tool factories don't emit
917
+ * `provider: "gmail"` by accident.
918
+ */
919
+ declare function connectorMintErrorToToolResult(err: ConnectorMintError, provider: string): string;
920
+ /**
921
+ * Build the Gmail tool set wired to the supplied token getter.
922
+ *
923
+ * The returned record keys (`gmail_search_messages`, `gmail_get_message`,
924
+ * `gmail_create_draft`, `gmail_send_message`) match the underlying tool names
925
+ * so callers can forward a subset by destructuring.
926
+ *
927
+ * @param getToken Async getter — typically `createConnectorTokenGetter(portal, "gmail")`.
928
+ * @param requestAccess Fallback called when `getToken()` returns null.
929
+ * Server agents pass `async () => { throw ... }`; browser
930
+ * surfaces drive a connect flow and resolve with the
931
+ * fresh token (or `null` on user cancel).
932
+ */
933
+ declare function createGmailTools(getToken: GmailTokenGetter, requestAccess: GmailRequestAccess): Record<string, ToolConfig>;
934
+
935
+ /**
936
+ * X (Twitter) tool factory for the chat system.
937
+ *
938
+ * Unlike the other connector tools, the X tools never call api.x.com directly:
939
+ * api.x.com returns no CORS headers, so a browser can't fetch it. Instead the
940
+ * caller supplies an {@link XProxyCaller} that POSTs to the portal's
941
+ * `POST {portalBaseUrl}/api/v1/connectors/x/proxy` endpoint (authed with the
942
+ * user's Privy bearer, NOT an X token). The portal mints the X token
943
+ * server-side and returns the upstream status + JSON verbatim. The SDK stays
944
+ * transport-agnostic: it builds the same upstream paths + query objects it
945
+ * always has and hands them to the injected caller.
946
+ *
947
+ * Tool catalogue (matches portal scope `connector:x:*`):
948
+ * - `x_get_me` -- fetch the authenticated user's profile
949
+ * - `x_get_my_posts` -- fetch the authenticated user's recent posts
950
+ *
951
+ * Error contract: when the proxy reports an auth failure (401, 403) the tool
952
+ * returns the canonical `__anuma_connector_error_v1` JSON shape produced by
953
+ * `buildConnectorErrorResult`. Tool executors never throw on these paths.
954
+ */
955
+
956
+ /**
957
+ * Calls the portal X proxy with an upstream X API path and optional query, and
958
+ * resolves to the upstream status + parsed JSON. Consumers wire this to
959
+ * `POST {portalBaseUrl}/api/v1/connectors/x/proxy` with the user's Privy bearer
960
+ * and a JSON body `{ path, query }`; the portal mints the X token server-side.
961
+ */
962
+ type XProxyCaller = (path: string, query?: Record<string, string | number>) => Promise<{
963
+ status: number;
964
+ json: unknown;
965
+ }>;
966
+ interface XGetMeArgs {
967
+ includePublicMetrics?: boolean;
968
+ }
969
+ interface XGetMyPostsArgs {
970
+ maxResults?: number;
971
+ }
972
+ /**
973
+ * Build the X tool set wired to the supplied proxy caller.
974
+ *
975
+ * The returned record keys (`x_get_me`, `x_get_my_posts`) match the underlying
976
+ * tool names so callers can forward a subset by destructuring.
977
+ *
978
+ * @param callProxy POSTs an upstream X path + query through the portal X proxy
979
+ * (`/api/v1/connectors/x/proxy`) and resolves to `{ status, json }`. The X
980
+ * token never reaches the browser -- the portal mints it server-side.
981
+ */
982
+ declare function createXTools(callProxy: XProxyCaller): Record<string, ToolConfig>;
983
+
984
+ /**
985
+ * Dropbox tool factory for the chat system.
986
+ *
987
+ * Mirrors the shape of {@link "./gmail".createGmailTools}: the caller supplies a
988
+ * token getter and an interactive `requestAccess` fallback, and the factory
989
+ * returns a record of `ToolConfig` entries the chat loop can run. The token
990
+ * getter is expected to be one returned by `createConnectorTokenGetter`, but any
991
+ * `() => Promise<string | null>` compatible function works.
992
+ *
993
+ * Dropbox API v2 sends CORS headers, so — unlike X or Slack — these tools call
994
+ * the API DIRECTLY with the minted token; no portal proxy caller is needed.
995
+ *
996
+ * Tool catalogue (all read-only, matches portal scope `connector:dropbox:read`):
997
+ * - `dropbox_list_folders` — list entries under a folder path
998
+ * - `dropbox_get_file_content` — download a file's content as text
999
+ * - `dropbox_search` — search files/folders by name/content
1000
+ *
1001
+ * Error contract: on a connector-level auth failure (401/403, or no usable
1002
+ * token) the tool returns the canonical `__anuma_connector_error_v1` JSON shape
1003
+ * produced by `buildConnectorErrorResult`. Tool executors never throw on these
1004
+ * paths — the LLM is expected to surface the connect URL to the user.
1005
+ */
1006
+
1007
+ /** Async getter returned by `createConnectorTokenGetter`. */
1008
+ type DropboxTokenGetter = () => Promise<string | null>;
1009
+ /**
1010
+ * Fallback used when no token is currently available. Server agents pass an
1011
+ * implementation that throws (no interactive OAuth is possible). Browser
1012
+ * surfaces pass a function that drives the connect flow and resolves with a
1013
+ * fresh token (or `null` if the user declined).
1014
+ */
1015
+ type DropboxRequestAccess = () => Promise<string | null>;
1016
+ /**
1017
+ * Build the Dropbox tool set wired to the supplied token getter.
1018
+ *
1019
+ * The returned record keys (`dropbox_list_folders`, `dropbox_get_file_content`,
1020
+ * `dropbox_search`) match the underlying tool names so callers can forward a
1021
+ * subset by destructuring.
1022
+ *
1023
+ * @param getToken Async getter — typically `createConnectorTokenGetter(portal, "dropbox")`.
1024
+ * @param requestAccess Fallback called when `getToken()` returns null.
1025
+ * Server agents pass `async () => { throw ... }`; browser
1026
+ * surfaces drive a connect flow and resolve with the fresh
1027
+ * token (or `null` on user cancel).
1028
+ */
1029
+ declare function createDropboxTools(getToken: DropboxTokenGetter, requestAccess: DropboxRequestAccess): Record<string, ToolConfig>;
1030
+
1031
+ /**
1032
+ * Slack tool factory for the chat system.
1033
+ *
1034
+ * Like the X tools, the Slack tools never call slack.com directly: the Web API
1035
+ * returns no CORS headers, so a browser can't fetch it. The caller supplies a
1036
+ * {@link SlackProxyCaller} that GETs the portal's Slack proxy (authed with the
1037
+ * user's Privy bearer, NOT a Slack token). The portal mints the Slack token
1038
+ * server-side and returns the upstream status + JSON verbatim. The SDK stays
1039
+ * transport-agnostic: it builds the same upstream method paths + query objects
1040
+ * and hands them to the injected caller.
1041
+ *
1042
+ * Slack auth quirk: the Web API answers HTTP 200 even on auth failures, putting
1043
+ * the real outcome in `{ ok: false, error: "invalid_auth" | ... }`. So we map
1044
+ * the auth-shaped `error` codes (and the usual 401/403 statuses) to the
1045
+ * canonical connector error, not just the HTTP status.
1046
+ *
1047
+ * Tool catalogue. The Slack Marketplace forbids the message-search scope, so
1048
+ * there's no server-side message-search API to call: `slack_search_messages`
1049
+ * scans recent history via `conversations.history` instead. Slack throttles
1050
+ * conversations.history/.replies to ~1 req/min for distributed apps, so the read
1051
+ * tools stay bounded — search fans out across only a handful of channels and
1052
+ * reads only recent messages.
1053
+ * - `slack_get_me` -- the authenticated user's profile
1054
+ * - `slack_list_channels` -- channels in the workspace
1055
+ * - `slack_search_messages` -- text-search recent messages across channels + DMs
1056
+ * - `slack_list_users` -- workspace members
1057
+ * - `slack_get_channel_history` -- recent messages in a channel (rate-limited)
1058
+ * - `slack_get_thread_replies` -- replies in a thread (rate-limited)
1059
+ * - `slack_post_message` -- post a message (WRITE)
1060
+ *
1061
+ * Error contract: on an auth failure the tool returns the canonical
1062
+ * `__anuma_connector_error_v1` JSON shape from `buildConnectorErrorResult`.
1063
+ * Tool executors never throw on these paths.
1064
+ */
1065
+
1066
+ /**
1067
+ * Calls the portal Slack proxy with an upstream Slack Web API method path,
1068
+ * optional query, and optional body, and resolves to the upstream status +
1069
+ * parsed JSON. Consumers wire this to the portal's Slack proxy with the user's
1070
+ * Privy bearer; the portal mints the Slack token server-side. Paths are method
1071
+ * names (e.g. `/conversations.list`); read params go in `query`. Write methods
1072
+ * (e.g. `/chat.postMessage`) pass a `body` — the portal classifies the path as a
1073
+ * write and POSTs the body upstream.
1074
+ */
1075
+ type SlackProxyCaller = (path: string, query?: Record<string, string | number>, body?: unknown) => Promise<{
1076
+ status: number;
1077
+ json: unknown;
1078
+ }>;
1079
+ interface SlackListChannelsArgs {
1080
+ limit?: number;
1081
+ }
1082
+ interface SlackSearchMessagesArgs {
1083
+ query: string;
1084
+ count?: number;
1085
+ /** Restrict the search to a single channel (id or name). Omit to fan out across channels. */
1086
+ channel?: string;
1087
+ }
1088
+ interface SlackListUsersArgs {
1089
+ limit?: number;
1090
+ }
1091
+ interface SlackGetChannelHistoryArgs {
1092
+ channel: string;
1093
+ limit?: number;
1094
+ }
1095
+ interface SlackGetThreadRepliesArgs {
1096
+ channel: string;
1097
+ ts: string;
1098
+ limit?: number;
1099
+ }
1100
+ interface SlackPostMessageArgs {
1101
+ channel: string;
1102
+ text: string;
1103
+ thread_ts?: string;
1104
+ }
1105
+ /**
1106
+ * Build the Slack tool set wired to the supplied proxy caller.
1107
+ *
1108
+ * The returned record keys match the underlying tool names so callers can
1109
+ * forward a subset by destructuring.
1110
+ *
1111
+ * @param callProxy GETs an upstream Slack method path + query through the portal
1112
+ * Slack proxy and resolves to `{ status, json }`. The Slack token never
1113
+ * reaches the browser -- the portal mints it server-side.
1114
+ */
1115
+ declare function createSlackTools(callProxy: SlackProxyCaller): Record<string, ToolConfig>;
1116
+
1117
+ /**
1118
+ * Design audit for app-generation output.
1119
+ *
1120
+ * Inspects App.js + App.css and reports inconsistencies in the design
1121
+ * system: raw hex colors outside the :root token block, CSS variables
1122
+ * declared but never used, missing :focus-visible rules on interactive
1123
+ * elements, icon-only buttons without aria-labels, images without alt
1124
+ * text, heading order anti-patterns, inline styles with hardcoded
1125
+ * colors. The audit does not prescribe what design choices to make —
1126
+ * it only checks that whatever the model picked is applied coherently.
1127
+ *
1128
+ * Returned as a structured AuditResult so the model (or a host) can
1129
+ * read it programmatically: each issue has a path, line, severity,
1130
+ * type, and human-readable message. The model is expected to call this
1131
+ * after substantial changes and patch the actionable issues.
1132
+ */
1133
+ type AuditSeverity = "error" | "warn" | "info";
1134
+ type AuditIssueType = "no-design-tokens" | "raw-color" | "inline-style-with-color" | "unused-token" | "missing-focus-state" | "focus-not-keyed" | "low-contrast" | "off-scale-spacing" | "orphaned-class" | "missing-aria-label" | "missing-alt" | "heading-order";
1135
+ interface AuditIssue {
1136
+ /** Where the issue surfaced — error / warn / info. */
1137
+ severity: AuditSeverity;
1138
+ /** Filename (e.g. "App.js" or "App.css"). */
1139
+ path: string;
1140
+ /** 1-based line number. Absent for whole-file issues. */
1141
+ line?: number;
1142
+ /** Machine-readable issue type for grouping or filtering. */
1143
+ type: AuditIssueType;
1144
+ /** Human-readable explanation suitable for showing to an LLM or a developer. */
1145
+ message: string;
1146
+ }
1147
+ interface AuditTokens {
1148
+ /** CSS variable names classified as color tokens (e.g. ["--bg", "--accent"]). */
1149
+ colors: string[];
1150
+ /** Typography tokens (e.g. ["--font-display"]). */
1151
+ fonts: string[];
1152
+ /** All other tokens (spacing, radii, shadows, etc.). */
1153
+ other: string[];
1154
+ }
1155
+ interface AuditResult {
1156
+ /** 0-100 score: 100 = clean, 0 = severe issues. Errors -10 each, warns -5, infos -1, floored at 0. */
1157
+ score: number;
1158
+ /** Design tokens discovered in :root. */
1159
+ tokens: AuditTokens;
1160
+ /** Issues found, sorted: errors first, then warns, then infos; within each, by path + line. */
1161
+ issues: AuditIssue[];
1162
+ }
1163
+ /**
1164
+ * Audit a generated app's design coherence. Pure function — no I/O.
1165
+ *
1166
+ * @param files Map of filename → content. Looks at "App.js" (or "App.jsx")
1167
+ * and "App.css". Other files are ignored.
1168
+ */
1169
+ declare function auditDesign(files: Record<string, string>): AuditResult;
1170
+
1171
+ /**
1172
+ * Parent ↔ iframe bridge for `window.app.complete`.
1173
+ *
1174
+ * Generated apps (built via `createAppGenerationTools`) call
1175
+ * `window.app.complete(prompt)` to reach an LLM at runtime — see the
1176
+ * AI CAPABILITIES section of `buildAppSystemPrompt`. The transport is
1177
+ * a host concern: hosts decide which model, which auth, which endpoint.
1178
+ * This module ships the standard postMessage protocol so hosts don't
1179
+ * have to design their own:
1180
+ *
1181
+ * PARENT (host's chat app):
1182
+ * installs `createAppCompleteBridge({ complete })`, which listens
1183
+ * for `anuma:app:complete:connect` announcements from preview
1184
+ * iframes and answers each one with a private {@link MessagePort}.
1185
+ * Prompts then arrive only over that port and the host's async
1186
+ * `complete(prompt)` is called per request.
1187
+ *
1188
+ * IFRAME (preview HTML where the generated app runs):
1189
+ * installs the shim via `installAppCompleteIframeShim()` (or by
1190
+ * inlining `APP_COMPLETE_IFRAME_SHIM_SCRIPT`). The shim sets
1191
+ * `window.app.complete` to a function that — on first use —
1192
+ * handshakes with the host to obtain a port, then sends each
1193
+ * prompt over that port and resolves with the response.
1194
+ *
1195
+ * Why a port handshake rather than posting the prompt straight up the
1196
+ * frame tree: the preview can't read its ancestors' origins
1197
+ * (cross-origin reads throw), so to *find* the host bridge it broadcasts
1198
+ * a connect announcement to every ancestor with targetOrigin "*". That
1199
+ * announcement carries no prompt — just a correlation id — so a frame
1200
+ * sitting between the preview and the host (a bundler/CDN frame, an
1201
+ * analytics or ad wrapper) sees only a content-free ping. The host
1202
+ * answers by transferring one end of a {@link MessageChannel} back to
1203
+ * the requesting frame; all prompt traffic flows over that entangled
1204
+ * port, which no other frame can observe. The prompt is therefore never
1205
+ * broadcast, even in nested-iframe topologies.
1206
+ *
1207
+ * The protocol is intentionally minimal — a connect announcement, a
1208
+ * connect-ack that carries the port, then request/response messages
1209
+ * (each with an id to correlate them) over the port. Sandbox-friendly:
1210
+ * the iframe never reaches outside its origin, the parent never lets the
1211
+ * iframe pick the backend, and `MessageChannel` works in sandboxed /
1212
+ * srcdoc previews whose origin is opaque ("null").
1213
+ *
1214
+ * @example Parent-side wiring:
1215
+ * ```ts
1216
+ * import { createAppCompleteBridge } from "@anuma/sdk/tools";
1217
+ *
1218
+ * const bridge = createAppCompleteBridge({
1219
+ * // Restrict who can drive complete() — required (or pass `source`).
1220
+ * allowedOrigins: ["https://preview.example"],
1221
+ * complete: async (prompt) => {
1222
+ * const res = await fetch("/api/complete", {
1223
+ * method: "POST",
1224
+ * headers: { "Content-Type": "application/json" },
1225
+ * body: JSON.stringify({ prompt }),
1226
+ * });
1227
+ * return await res.text();
1228
+ * },
1229
+ * });
1230
+ * // when the preview is unmounted:
1231
+ * bridge.dispose();
1232
+ * ```
1233
+ *
1234
+ * @example Iframe-side wiring (preview HTML):
1235
+ * ```html
1236
+ * <script>${APP_COMPLETE_IFRAME_SHIM_SCRIPT}</script>
1237
+ * <!-- now window.app.complete is available to the React app -->
1238
+ * ```
1239
+ */
1240
+ /** Announcement broadcast from iframe → ancestors so the host bridge can
1241
+ * find the preview frame. Carries only a correlation id (no prompt), so
1242
+ * posting it up the ancestor chain with targetOrigin "*" discloses
1243
+ * nothing sensitive to intermediate frames. */
1244
+ declare const APP_COMPLETE_CONNECT_TYPE = "anuma:app:complete:connect";
1245
+ /** Reply from parent → iframe acknowledging a connect. The transferred
1246
+ * {@link MessagePort} (in the message's `ports`) is the private channel
1247
+ * the iframe uses for all subsequent prompt traffic. */
1248
+ declare const APP_COMPLETE_CONNECT_ACK_TYPE = "anuma:app:complete:connect-ack";
1249
+ /** Tagged request from iframe → parent, sent over the entangled
1250
+ * {@link MessagePort} (never via window postMessage). Carries the prompt
1251
+ * and a correlation id so the parent can route the reply to the right
1252
+ * pending promise. */
1253
+ declare const APP_COMPLETE_REQUEST_TYPE = "anuma:app:complete:request";
1254
+ /** Tagged response from parent → iframe, sent back over the same
1255
+ * {@link MessagePort}. Either `result` (string) or `error` (string) is
1256
+ * populated. */
1257
+ declare const APP_COMPLETE_RESPONSE_TYPE = "anuma:app:complete:response";
1258
+ interface AppCompleteBridgeOptions {
1259
+ /** Host's implementation. Receives the prompt string and resolves
1260
+ * with the response. May throw / reject; the error message is
1261
+ * forwarded to the iframe. */
1262
+ complete: (prompt: string) => Promise<string>;
1263
+ /** Origin to send the connect-ack (and its transferred port) to. When
1264
+ * unset, the ack is targeted at the requesting frame's own
1265
+ * `event.origin` so a document that later replaces the source window
1266
+ * can't capture the channel — falling back to "*" only for opaque
1267
+ * origins (sandboxed / srcdoc iframes, which serialize to "null" and
1268
+ * can't be targeted by string). Note the "*" fallback cannot protect a
1269
+ * null-origin preview against a same-window navigation race; pin a known
1270
+ * origin here (or front the preview with a real origin) when that matters.
1271
+ * Set explicitly to pin a known origin in production. */
1272
+ targetOrigin?: string;
1273
+ /** Filter connect announcements by source window. When set, connects
1274
+ * from any other window are ignored. Useful when the parent embeds
1275
+ * multiple iframes and wants to route them differently. */
1276
+ source?: Window;
1277
+ /** Restrict accepted `event.origin` values for connect announcements.
1278
+ * Connects from any other origin are silently dropped — so a cross-origin
1279
+ * script that gains a window handle can't open a channel and burn quota.
1280
+ * Use the host's own origin (or the preview's, for cross-origin previews);
1281
+ * for srcdoc/sandboxed previews whose origin is "null", prefer `source`.
1282
+ * The single-element sentinel `["*"]` opts into accepting every origin
1283
+ * (logs a one-time warning). Omitting both this and `source` throws — the
1284
+ * bridge will not silently default to wide-open. */
1285
+ allowedOrigins?: readonly string[];
1286
+ }
1287
+ interface AppCompleteBridge {
1288
+ /** Remove the message listener and close every open channel. Call when
1289
+ * the preview is unmounted. */
1290
+ dispose(): void;
1291
+ }
1292
+ /**
1293
+ * Install a parent-side bridge that answers `window.app.complete`
1294
+ * calls from one or more preview iframes. Returns a handle whose
1295
+ * `dispose()` removes the listener and closes open channels.
1296
+ *
1297
+ * @throws if neither `allowedOrigins` nor `source` is provided. The bridge
1298
+ * runs the host's `complete()` (which holds the API key/quota) for callers, so
1299
+ * it refuses to default to accepting every origin — pass `allowedOrigins`
1300
+ * and/or `source`, or opt in explicitly with `allowedOrigins: ["*"]`.
1301
+ */
1302
+ declare function createAppCompleteBridge(options: AppCompleteBridgeOptions): AppCompleteBridge;
1303
+ /** Default timeout (ms) for `window.app.complete` calls inside the
1304
+ * iframe shim. Covers the connect handshake plus the LLM completion —
1305
+ * long enough for slow completions, short enough to surface a hung or
1306
+ * absent parent bridge (e.g. unmounted preview). Hosts can override by
1307
+ * setting `window.APP_COMPLETE_TIMEOUT_MS` in the iframe before the
1308
+ * first call. */
1309
+ declare const APP_COMPLETE_DEFAULT_TIMEOUT_MS = 60000;
1310
+ /**
1311
+ * Inline JavaScript source that installs `window.app.complete` in an
1312
+ * iframe. On first use it handshakes with the host bridge to obtain a
1313
+ * private {@link MessagePort}, then sends each prompt over that port.
1314
+ * Drop into a `<script>` tag of a preview HTML to enable in-app LLM
1315
+ * calls.
1316
+ *
1317
+ * Confidentiality: prompts are sent only over the entangled port, never
1318
+ * broadcast up the frame tree. The only thing posted to ancestor frames is
1319
+ * a content-free connect announcement (used to locate the host bridge), so
1320
+ * an intermediate frame that merely *reads* messages can't see prompts —
1321
+ * the passive-disclosure leak this transport was designed to close.
1322
+ *
1323
+ * What the default does NOT defend against: an actively-malicious ancestor
1324
+ * frame. The connect announcement (and its correlation id) is broadcast to
1325
+ * every ancestor, so such a frame can race a forged connect-ack carrying its
1326
+ * own port; if it wins, that port becomes the channel and it then reads
1327
+ * every prompt. The iframe cannot read an ancestor's origin to tell the real
1328
+ * host from an impostor, so closing this active path needs a trust anchor:
1329
+ * set `window.APP_COMPLETE_PARENT_ORIGIN` to the host's origin before the
1330
+ * first call and the shim accepts a connect-ack only from that origin (pair
1331
+ * it with `allowedOrigins` on the bridge). Strongly recommended whenever an
1332
+ * untrusted cross-origin frame can sit between the preview and the host.
1333
+ *
1334
+ * No-ops when there's no parent window (e.g. when the HTML is opened
1335
+ * directly via `file://` rather than embedded in an iframe). In that
1336
+ * case, hosts can provide their own `window.app.complete` stub or
1337
+ * leave it unset.
1338
+ *
1339
+ * Each call has a {@link APP_COMPLETE_DEFAULT_TIMEOUT_MS} ms timeout
1340
+ * (overridable via `window.APP_COMPLETE_TIMEOUT_MS`); on timeout the promise
1341
+ * rejects and its pending state is cleaned up, so a disposed or absent parent
1342
+ * bridge can't leave the app frozen. A timeout on the established channel also
1343
+ * drops it, so the next call re-handshakes — recovering if the bridge was torn
1344
+ * down and a new one has since mounted.
1345
+ */
1346
+ declare const APP_COMPLETE_IFRAME_SHIM_SCRIPT: string;
1347
+ /**
1348
+ * Programmatic variant of `APP_COMPLETE_IFRAME_SHIM_SCRIPT` — call once
1349
+ * from inside an iframe (typically the first thing the preview HTML
1350
+ * executes) to install `window.app.complete`. Equivalent to inlining
1351
+ * the script string in a `<script>` tag.
1352
+ */
1353
+ declare function installAppCompleteIframeShim(): void;
1354
+
1355
+ /**
1356
+ * Standalone HTML export for app-generation outputs.
1357
+ *
1358
+ * The app-generation tools produce three files — App.js, App.css, and
1359
+ * package.json — that together describe a React app. `exportAppToHtml`
1360
+ * compiles them into a single self-contained HTML file that runs
1361
+ * anywhere via `file://`: it inlines App.css, strips local imports
1362
+ * from App.js, maps package.json deps to esm.sh CDN URLs, and wires
1363
+ * up React UMD + Babel standalone for in-browser JSX compilation.
1364
+ *
1365
+ * Useful for letting end users download what the model built, or for
1366
+ * embedding generated apps in iframes without a bundler. Hosts that
1367
+ * provide their own preview infrastructure (e.g. a hosted iframe with
1368
+ * a custom shim for `window.app.complete`) can pass `windowAppShim`
1369
+ * to inject their bridge in place of the default offline stub.
1370
+ *
1371
+ * @example Single-file download for an end-user:
1372
+ * ```ts
1373
+ * import { exportAppToHtml } from "@anuma/sdk/tools";
1374
+ *
1375
+ * const html = exportAppToHtml({
1376
+ * files: {
1377
+ * "App.js": await storage.getFile(convId, "App.js"),
1378
+ * "App.css": await storage.getFile(convId, "App.css"),
1379
+ * "package.json": await storage.getFile(convId, "package.json"),
1380
+ * },
1381
+ * title: "My Generated App",
1382
+ * });
1383
+ * // Browser:
1384
+ * const blob = new Blob([html], { type: "text/html" });
1385
+ * const url = URL.createObjectURL(blob);
1386
+ * Object.assign(document.createElement("a"), { href: url, download: "app.html" }).click();
1387
+ * ```
1388
+ *
1389
+ * @example iframe preview with a parent-side LLM bridge:
1390
+ * ```ts
1391
+ * import { exportAppToHtml, APP_COMPLETE_IFRAME_SHIM_SCRIPT } from "@anuma/sdk/tools";
1392
+ *
1393
+ * const html = exportAppToHtml({
1394
+ * files: { ... },
1395
+ * windowAppShim: APP_COMPLETE_IFRAME_SHIM_SCRIPT,
1396
+ * });
1397
+ * previewIframe.srcdoc = html;
1398
+ * ```
1399
+ */
1400
+ /**
1401
+ * Inline JS that buffers any `error` / `unhandledrejection` event on the
1402
+ * window and, if `#root` is still empty after the React app has had time
1403
+ * to mount, paints a visible error banner into the page. Without this,
1404
+ * an unhandled import or render error leaves the user staring at the
1405
+ * body background color with no indication of what went wrong (the
1406
+ * exact failure mode we just shipped: ESM `LayoutKanban` did not
1407
+ * exist; React never mounted; preview was a blank navy rectangle).
1408
+ *
1409
+ * Buffer-then-check pattern: errors are recorded as they fire, but the
1410
+ * overlay only paints when `#root.childNodes.length === 0` after a
1411
+ * load+grace window. That means errors thrown by a successfully-
1412
+ * mounted app (e.g. user clicks a button that throws) never trigger
1413
+ * the overlay — they belong in the console and the app handles its
1414
+ * own UI. The grace covers Babel-standalone compilation time + ESM
1415
+ * resolver round-trips, which empirically takes 1-3s.
1416
+ *
1417
+ * Written in ES5 with no fancy syntax — runs in a plain `<script>`
1418
+ * (not module mode), so even very old browsers parse it.
1419
+ */
1420
+ declare const RUNTIME_ERROR_OVERLAY_SCRIPT = "// Runtime error overlay \u2014 surfaces a visible banner when the app fails\n// to mount. See RUNTIME_ERROR_OVERLAY_SCRIPT docstring for design.\n(function () {\n var errors = [];\n function record(label, message) {\n errors.push(label + \": \" + message);\n }\n window.addEventListener(\"error\", function (e) {\n record(\"Error\", (e.error && e.error.message) || e.message || String(e));\n });\n window.addEventListener(\"unhandledrejection\", function (e) {\n var msg = (e.reason && (e.reason.message || String(e.reason))) || \"unknown\";\n record(\"Unhandled rejection\", msg);\n });\n function paint() {\n var root = document.getElementById(\"root\");\n if (!root) return;\n if (root.childNodes.length > 0) return;\n if (errors.length === 0) return;\n var box = document.createElement(\"div\");\n box.setAttribute(\"data-anuma-error-overlay\", \"\");\n box.style.cssText = \"position:fixed;inset:0;background:#1a0c0c;color:#ffb4b4;font:14px/1.5 ui-monospace,Menlo,monospace;padding:24px;overflow:auto;z-index:99999\";\n var h = document.createElement(\"h2\");\n h.textContent = \"Runtime error \u2014 the React app did not mount\";\n h.style.cssText = \"color:#ff8080;margin:0 0 16px;font-size:16px;font-weight:600\";\n box.appendChild(h);\n for (var i = 0; i < errors.length; i++) {\n var p = document.createElement(\"p\");\n p.textContent = errors[i];\n p.style.cssText = \"margin:0 0 10px;white-space:pre-wrap;font-family:inherit\";\n box.appendChild(p);\n }\n var hint = document.createElement(\"p\");\n hint.textContent = \"Common causes: an imported export name that doesn't exist in the package, a syntax error in App.js, a missing dependency in package.json, or a network failure loading a CDN module.\";\n hint.style.cssText = \"margin:18px 0 0;color:#a08080;font-size:12px;line-height:1.55\";\n box.appendChild(hint);\n root.appendChild(box);\n }\n function schedule() {\n setTimeout(paint, 3000);\n }\n if (document.readyState === \"complete\") {\n schedule();\n } else {\n window.addEventListener(\"load\", schedule);\n }\n})();";
1421
+ /**
1422
+ * Baseline CSS that defines what the model can assume about the preview
1423
+ * environment — applied before `App.css` in both `exportAppToHtml`'s
1424
+ * standalone output and the chat host's Sandpack iframe template.
1425
+ *
1426
+ * Why this exists: the model writes CSS targeting component classes
1427
+ * (`.app-shell`, `.card`) but rarely resets the surrounding `html` /
1428
+ * `body` chrome. In environments without a preflight (Sandpack's bare
1429
+ * iframe), browser defaults leak through — the visible symptom is the
1430
+ * 8 px `body` margin everyone notices, but the same root cause produces
1431
+ * `content-box` sizing surprises, Times-serif fallback on stray text
1432
+ * outside styled wrappers, and `<img>` baseline gaps. Tailwind's
1433
+ * preflight solves it at scale in environments that bundle Tailwind;
1434
+ * we provide the minimal equivalent for environments that don't.
1435
+ *
1436
+ * Design rules:
1437
+ *
1438
+ * - Only fix actual leaks (rules that surface as visible bugs).
1439
+ * Don't ship a maximal reset like normalize.css — every entry here
1440
+ * should be defensible as "the model would have to fight this
1441
+ * default anyway."
1442
+ * - Loaded BEFORE the model's `App.css`. CSS specificity ties go to
1443
+ * the later-loaded rule, so the model's CSS still wins on every
1444
+ * property it sets. The baseline only provides defaults for what
1445
+ * the model didn't set.
1446
+ * - Stays in sync between `exportAppToHtml` (standalone download)
1447
+ * and the host's Sandpack template (chat preview). Single source
1448
+ * of truth so the two environments render identically.
1449
+ */
1450
+ declare const APP_PREVIEW_BASELINE_CSS = "*, *::before, *::after { box-sizing: border-box; }\nhtml, body, #root { margin: 0; padding: 0; min-height: 100%; }\nbody {\n font-family: system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif;\n line-height: 1.5;\n -webkit-text-size-adjust: 100%;\n}\nimg, picture, video, canvas, svg { display: block; max-width: 100%; }\nbutton, input, select, textarea { font: inherit; }";
1451
+ /** Stub for `window.app.complete` that returns canned responses based on
1452
+ * keyword detection. Used when no real LLM backend is available — keeps
1453
+ * the visual preview interactive enough to demo. The default value of
1454
+ * `ExportAppOptions.windowAppShim`. */
1455
+ declare const APP_COMPLETE_STUB_SCRIPT = "// Offline stub for window.app.complete \u2014 returns canned responses so the\n// preview is interactive without a real backend. Replace by passing\n// windowAppShim to exportAppToHtml (e.g. APP_COMPLETE_IFRAME_SHIM_SCRIPT\n// for parent-iframe postMessage bridging) or by overwriting at runtime.\nwindow.app = window.app || {};\nwindow.app.complete = async function (prompt) {\n console.info(\"[stub] window.app.complete called:\", String(prompt).slice(0, 120));\n await new Promise((r) => setTimeout(r, 400));\n var p = String(prompt || \"\").toLowerCase();\n if (p.includes(\"correct\") || p.includes(\"grade\") || p.includes(\"evaluate\")) {\n return \"\u2713 Correct \u2014 that's the right answer. (stub response)\";\n }\n if (p.includes(\"explain\") || p.includes(\"why\") || p.includes(\"how\")) {\n return \"Here's an explanation: this is a stub response. Wire window.app.complete to a real LLM to see actual reasoning.\";\n }\n if (p.includes(\"summarize\") || p.includes(\"summary\")) {\n return \"Summary: (stub response \u2014 connect to a real backend for real summaries.)\";\n }\n return \"Stub AI response. Replace window.app.complete with a real backend bridge to enable real generations.\";\n};";
1456
+ interface ExportAppOptions {
1457
+ /** Map of file path → file content. Must include `App.js` (or `App.jsx`).
1458
+ * Optional: `App.css`, `package.json`. Other files are ignored — the
1459
+ * output is single-file. */
1460
+ files: Record<string, string>;
1461
+ /** `<title>` for the page. Defaults to `"App"`. */
1462
+ title?: string;
1463
+ /** Include the Tailwind Play CDN `<script>`. Defaults to `true`. Set
1464
+ * to `false` if the generated app uses only the inlined App.css. */
1465
+ tailwind?: boolean;
1466
+ /** JS source installed before the React app boots — typically sets up
1467
+ * `window.app.complete` (and any related helpers). Defaults to
1468
+ * {@link APP_COMPLETE_STUB_SCRIPT}, an offline canned-response stub.
1469
+ * Pass {@link APP_COMPLETE_IFRAME_SHIM_SCRIPT} for an iframe embed
1470
+ * with a parent-side bridge, or `""` to omit entirely. */
1471
+ windowAppShim?: string;
1472
+ }
1473
+ /**
1474
+ * Compile an app-generation output (App.js + App.css + package.json) into
1475
+ * a single self-contained HTML file. See module docstring for examples.
1476
+ *
1477
+ * Loader strategy: native ESM via `<script type="importmap">` resolves
1478
+ * every package name (react, react-dom/client, plus whatever's in
1479
+ * package.json) to an esm.sh URL. Babel-standalone runs the App.js
1480
+ * source as `<script type="text/babel" data-type="module">` so JSX is
1481
+ * compiled to JS but imports stay as ES-module imports — the browser
1482
+ * resolves them through the importmap. No window globals, no UMD
1483
+ * sequencing, no per-package compatibility wrappers.
1484
+ */
1485
+ declare function exportAppToHtml(options: ExportAppOptions): string;
1486
+
1487
+ /**
1488
+ * App Builder agent persona prompt — canonical source for the SDK.
1489
+ *
1490
+ * Mirrors the `SENTINEL_PROMPT` / `HAVEN_PROMPT` pattern: the prompt lives in
1491
+ * code and the SDK owns it. Imported as the App Builder persona by hosts that
1492
+ * wire `createAppGenerationTools` directly, and attached to the
1493
+ * `app-generation` tool set's `systemPrompt` so it rides in with semantic tool
1494
+ * selection.
1495
+ *
1496
+ * Lives in its own dependency-free module (no `@babel/parser` etc.) so the
1497
+ * lib/server layer can import the string without pulling in the heavy
1498
+ * app-generation runtime — see `BUILT_IN_TOOL_SETS` in
1499
+ * `src/lib/tools/serverTools.ts`.
1500
+ *
1501
+ * Pair with the tools returned by `createAppGenerationTools` — `create_file`,
1502
+ * `patch_file`, `read_file`, `delete_file`, `list_files`, `audit_design`,
1503
+ * `critique_design`, `verify_app`.
1504
+ *
1505
+ * The workflow this prompt mandates (create → critique → patch → audit →
1506
+ * patch → verify) needs 7–8 tool rounds on a substantial turn. Hosts capping
1507
+ * `maxToolRounds` below 8 starve the trailing `verify_app` step.
1508
+ */
1509
+ declare const APP_BUILDER_PROMPT = "App Builder tools (create_file, patch_file, and the rest described below) are available this turn, but they are OPTIONAL \u2014 having them does not mean you should build anything.\n\nBuild ONLY when the user gives an explicit instruction to build, create, generate, or change an app, tool, or UI \u2014 e.g. \"build a todo app\", \"make a snake game\", \"add a footer to the app\", \"change the background to blue\". A bare app name or feature reference is NOT a build instruction: if the user just says \"todo app\", \"a snake game\", or \"qr code\", or asks a question about an app, do NOT generate anything \u2014 reply normally, or ask what they'd like you to build. The same holds for greetings, questions, stories, explanations, advice, or code that isn't a runnable app: respond normally and do not create files. When it's unclear whether the user actually wants an app built, ask instead of building.\n\nOnce the user HAS given an explicit build instruction, you are in App Builder mode: you produce polished, production-quality React apps that feel designed \u2014 not generic.\n\nDESIGN: on the initial build, before writing code, briefly state (2-3 sentences) what you want this app to feel like \u2014 name the specific fonts, the palette in hex, and one signature detail. Be concrete: \"Fraunces display + Inter body + JetBrains Mono metadata, terracotta #b75432 accent on bone #f3efe6, paper-grain texture via inline SVG noise\" is the kind of specificity that anchors the build. Default to nothing: don't pick \"safe modern productivity\" unless the brief literally asks for utilitarian. On later turns, state a design brief only when the change is visual (a restyle, new theme, or new UI); for a logic, data, or behavior-only change, skip the brief and just make the change.\n\nWORKFLOW:\n1. NEVER output code as text or markdown. ALWAYS use tools.\n2. Initial build: state your design brief, then create_file with a \"files\" array writing ALL files in one call. The preview renders automatically.\n3. patch_file requires read_file for that path earlier in the conversation (or create_file in the same conversation, which counts). read_file output is numbered \"42: <text>\" \u2014 the prefix is display-only; never copy it into \"find\". Each \"find\" must match exactly one location \u2014 include 2-3 lines of surrounding context. Pass multiple patches per call to change distinct locations.\n4. Prefer patch_file for changes to existing files \u2014 surgical, reviewable, preserves surrounding code. Reserve create_file overwrites for substantial restructuring. Inserts: find the line before, replace with that line + the new line. Deletes: empty replace.\n5. After substantial changes, call critique_design first (taste, hierarchy, distinctiveness, weakest decision) and then audit_design (mechanical: tokens, focus states, aria-labels, alt, heading order). Patch what each surfaces. critique catches taste issues no checklist would; audit catches mechanics you'd miss. Run both; act on both.\n6. Before declaring done, call verify_app. The host's preview runtime reports whether your app actually mounted and surfaces any errors it captured \u2014 hallucinated imports, syntax the bundler rejected, undefined components, runtime crashes. If `rendered` is false or `errors` is non-empty, fix every error before the next message. If the response includes a `note` that verify_app isn't wired in this host, skip \u2014 audit + critique are your feedback in that case.\n7. Keep responses to a sentence or two. Exception: when responding to critique_design, write a substantive paragraph per rubric question.\n\nSTRUCTURE:\n- App.js: default-export React component. For non-trivial apps, define small co-located helpers (Header, CardList, Card, EditDialog, \u2026) above the default export rather than one giant App.\n- App.css: all styles, imported in App.js. No inline style objects.\n- package.json: list ALL imported packages including react. No CDN script tags. Versions are auto-pinned.\n- Do NOT create index.js or index.html (auto-generated).\n\nSTATE & PERSISTENCE:\n- The code runs in the user's browser \u2014 standard browser APIs available (localStorage, URL hash, Date, fetch).\n- Persist user-created data (todos, cards, notes, settings) with localStorage so it survives refresh. Use a versioned key (\"kanban.v1\") and JSON.parse inside try/catch on read.\n- Treat persisted data as untrusted and possibly stale: a saved record may predate a field you just added (a timestamp, id, or setting), so default-fill or skip missing fields on read \u2014 never assume a loaded record has every field the current code expects. Bump the key version when the shape changes.\n- Lift state to App when multiple children need it; keep it local otherwise. Extract non-trivial logic into custom hooks (useTodos, useTimer).\n- useReducer for state shapes with several interrelated fields (cards + columns + filters + drag state).\n\nFILE & IMAGE UPLOAD:\n- Picker: `<input type=\"file\" accept=\"image/*\" multiple>` (vary accept by use case: `.csv,text/csv`, `.txt,.md,text/plain`, etc.).\n- Drag-and-drop: call `e.preventDefault()` in onDragOver (otherwise drop never fires); read files from `event.dataTransfer.files`. Drop and picker should coexist.\n- Previews: `<img src={URL.createObjectURL(file)} />`. Call `URL.revokeObjectURL` on unmount or before replacing \u2014 otherwise the browser leaks memory.\n- localStorage cannot hold large binary. Persist only metadata for big files; re-upload on next visit (or use IndexedDB if you really need durable binary storage).\n- Validate size and type before processing; reject files larger than the app can hold in memory.\n\nAI CAPABILITIES \u2014 `window.app.complete(prompt: string): Promise<string>` is available in this environment. Build for the real call.\n- When the app's core purpose is conversation, generation, grading, recommendation, or any natural-language task, this IS the implementation \u2014 call it. NEVER fake it: no hardcoded or canned replies, no random or `setTimeout` \"thinking\", no demo-mode stub. A chat app, tutor, or assistant that doesn't call `window.app.complete` is wrong, not a placeholder.\n- Don't force it where it isn't needed \u2014 a calculator, todo list, or timer has no reason to call it. Match the implementation to what the app actually does.\n- Async: wrap in try/catch, show a loading state, and disable dependent inputs while waiting. Surface failures gracefully (\"Couldn't reach the AI \u2014 try again\").\n- Structure prompts with clear sections (Role / Context / Task / Format) and pass the relevant state (e.g. the running conversation) as context. Render the response as untrusted text: `white-space: pre-wrap`, never dangerouslySetInnerHTML.\n- Skip the call only when the answer is genuinely static \u2014 no language or reasoning needed. Cache repeated prompts in component state when sensible.\n\nCODE QUALITY \u2014 write as a senior engineer would:\n- Handle edge cases (empty inputs, division by zero, invalid data). Show helpful error states, not crashes.\n- Never let formatting throw during render: guard `new Date(x)` / `Intl` / `toLocaleString` / `.toFixed` against missing or invalid values (e.g. `Number.isNaN(d.getTime())`) and return a fallback \u2014 one bad value from storage or state must not blank the whole app.\n- Semantic HTML: real headings, labels, buttons (not divs with onClick), form elements.\n- Accessible: aria-labels on icon buttons, focus-visible styles, keyboard navigable.\n- Extract into custom hooks or helpers when a component grows past ~80 lines.\n\nSTYLING \u2014 Tailwind is the CHASSIS, custom CSS carries the IDENTITY:\n- Tailwind (via Play CDN) for layout, spacing, state (hover/focus), responsive prefixes \u2014 uniform structure.\n- App.css carries the identity: @import the Google Fonts, declare CSS custom properties (--bg, --panel, --card, --ink, --muted, --accent, --hairline, --radius, --radius-sm, --shadow-card, --shadow-card-hi) in :root, gradients/textures/signature animations Tailwind can't express. Reference tokens from Tailwind via arbitrary values (`className=\"bg-[var(--bg)]\"`) when needed. A well-designed app has 60-200 lines of App.css \u2014 not zero.\n- Modern CSS is how apps stop reading as generic-AI-output: `color-mix(in oklab, var(--accent) 14%, transparent)` for tinting, `oklch()` / `oklab()` for perceptually-uniform color, `:has()`, `conic-gradient` / `radial-gradient`, `backdrop-filter`, `@container` queries, inline SVG `feTurbulence` for texture.\n- Don't pull additional CSS frameworks. Tailwind + custom CSS is enough.\n\nVISUAL DESIGN \u2014 every app should look like a real product, not a wireframe:\n- Let the chosen aesthetic drive every decision. \"Editorial\" wants italic display type and restrained color; \"retro arcade\" wants chunky shadows and saturated gradients. No safe-modern defaults.\n- Atmospheric depth: subtle radial wash, paper grain (inline SVG noise), warm/cool tint \u2014 every canvas should feel like a designed space, not a #fff rectangle.\n- Three shadow tiers (rest / hover / active). Pair drop shadows with inset highlights (`box-shadow: 0 1px 0 rgba(255,255,255,.9) inset, 0 6px 14px -10px ...`) for tactile feel.\n- Typography hierarchy: display font for headlines (italic accents on key words via `<em>`), body font for content, mono for metadata/labels \u2014 different families telegraph different information types.\n- Interactive polish: hover/active/focus on every clickable element, 200-300ms cubic-bezier transitions, keyboard shortcuts where natural (Esc to cancel, Cmd/Ctrl+Enter to commit), focus-visible rings keyed to the accent.\n- Light/dark mode if reasonable: a single CSS variable inversion under `[data-theme=\"dark\"]`.\n- Customization affordance for multi-session apps: small \"Tweaks\" panel with theme / density / accent. Skip for throwaway demos.\n- Responsive: Tailwind responsive prefixes (`sm:`, `md:`, `lg:`) and `@media` in App.css. Mobile-first.";
1510
+
1511
+ /** Strip leading slash and collapse traversal segments so paths are always clean relative paths. */
1512
+ declare function normalizePath(p: string): string;
1513
+
1514
+ /**
1515
+ * App generation tools for multi-file app development.
1516
+ *
1517
+ * Provides create_file, patch_file, delete_file, read_file, and list_files
1518
+ * tool configurations, plus the system prompt that drives LLM behavior.
1519
+ * The tools operate on a pluggable storage backend so consumers can wire
1520
+ * them to IndexedDB, an in-memory Map, a database, or any other
1521
+ * persistence layer.
1522
+ *
1523
+ * App preview rendering is automatic: every successful create_file /
1524
+ * patch_file / delete_file invokes the host's optional `displayApp`
1525
+ * callback so the chat UI refreshes the same preview card in place. There
1526
+ * is no standalone `display_app` tool — mirrors the slide pipeline, where
1527
+ * `add_slide` and `patch_slides` self-render via `displaySlides`.
1528
+ *
1529
+ * @example
1530
+ * ```typescript
1531
+ * import { createAppGenerationTools, buildAppSystemPrompt } from "@anuma/sdk/tools";
1532
+ *
1533
+ * const tools = createAppGenerationTools({
1534
+ * getConversationId: () => currentConversationId,
1535
+ * storage: myStorageAdapter,
1536
+ * });
1537
+ * ```
1538
+ */
1539
+
1540
+ /** Minimal file record returned by storage operations. */
1541
+ interface AppFileRecord {
1542
+ path: string;
1543
+ content: string;
1544
+ }
1545
+ /**
1546
+ * Pluggable storage backend for app files.
1547
+ * Consumers provide an implementation backed by IndexedDB, a Map, etc.
1548
+ */
1549
+ interface AppFileStorage {
1550
+ getFile: (conversationId: string, path: string) => Promise<AppFileRecord | null>;
1551
+ getFiles: (conversationId: string) => Promise<AppFileRecord[]>;
1552
+ putFile: (conversationId: string, path: string, content: string) => Promise<void>;
1553
+ putFiles: (conversationId: string, files: Array<{
1554
+ path: string;
1555
+ content: string;
1556
+ }>) => Promise<void>;
1557
+ deleteFile: (conversationId: string, path: string) => Promise<void>;
1558
+ }
1559
+
1560
+ /**
1561
+ * Truncate file content for inclusion in tool results.
1562
+ * Keeps the first and last portions so the LLM has both the beginning
1563
+ * (imports, declarations) and end (exports, closing tags) of the file.
1564
+ */
1565
+ declare function truncateContent(content: string): string;
1566
+ /** Numbered slice of file content surrounding a point of interest. */
1567
+ interface FileSnippet {
1568
+ startLine: number;
1569
+ endLine: number;
1570
+ content: string;
1571
+ }
1572
+ /** Result of fuzzy-locating where a failed `find` was probably targeting.
1573
+ * `high` confidence means a non-noisy line matched; `low` means only a
1574
+ * generic markup line (e.g. SVG header) matched and the snippet would
1575
+ * likely mislead the model. */
1576
+ interface AnchorMatch {
1577
+ line: number;
1578
+ confidence: "high" | "low";
1579
+ }
1580
+ /** Reason a single patch did not apply. */
1581
+ type PatchFailureReason = "empty_find" | "not_found" | "ambiguous";
1582
+ /** Details for a patch that did not apply, indexed against the input array.
1583
+ * `matchLines` is populated only for `reason: "ambiguous"` — the 1-based
1584
+ * line numbers where the find string starts in the file. */
1585
+ interface PatchFailure {
1586
+ index: number;
1587
+ find: string;
1588
+ reason: PatchFailureReason;
1589
+ matchLines?: number[];
1590
+ }
1591
+ /**
1592
+ * Apply find/replace patches to a string atomically.
1593
+ *
1594
+ * Either all patches apply, or none do — when any patch fails (no match,
1595
+ * empty find, or ambiguous match), the returned `content` equals the input
1596
+ * and `appliedCount` is 0. This prevents committing partial edits to disk
1597
+ * (e.g. a rename that succeeds in some call sites but not others).
1598
+ *
1599
+ * A `find` must match the file exactly once. Multiple matches surface as
1600
+ * `reason: "ambiguous"` so the model can disambiguate by adding context
1601
+ * rather than silently editing the first occurrence.
1602
+ *
1603
+ * Before declaring a `find` unmatched, the function tries two fallbacks:
1604
+ * 1. unescaping JSON whitespace literals (`\\n`, `\\t`, `\\r` as
1605
+ * two-character backslash sequences → real newline/tab/CR);
1606
+ * 2. stripping leading "42: " line-number prefixes when every line of
1607
+ * the find carries one (the model may have copied numbered output
1608
+ * from read_file or a failure snippet verbatim).
1609
+ * Ambiguity is checked against whichever variant matches.
1610
+ *
1611
+ * Returned `failed` includes the full `find` string and its index in the
1612
+ * input array so the caller can map failures back to the original patches
1613
+ * without ambiguity.
1614
+ */
1615
+ declare function applyPatches(content: string, patches: Array<{
1616
+ find: string;
1617
+ replace: string;
1618
+ }>): {
1619
+ content: string;
1620
+ appliedCount: number;
1621
+ failed: PatchFailure[];
1622
+ };
1623
+ /** Tool names for app file operations (create, patch, delete, read, list,
1624
+ * audit, critique, verify). */
1625
+ declare const APP_FILE_TOOL_NAMES: ReadonlySet<string>;
1626
+ /**
1627
+ * In-memory `AppFileStorage` backed by a `Map<string, string>`.
1628
+ *
1629
+ * Useful for server-side consumers (Cloudflare Workers, Node.js) that manage
1630
+ * their own persistence. The `conversationId` parameter is ignored — all
1631
+ * operations target the single internal map.
1632
+ *
1633
+ * @example
1634
+ * ```typescript
1635
+ * const storage = new MapFileStorage();
1636
+ * const tools = createAppGenerationTools({
1637
+ * getConversationId: () => "task-1",
1638
+ * storage,
1639
+ * });
1640
+ * // After tool execution, read the files:
1641
+ * const files = storage.getAll(); // Map<string, string>
1642
+ * ```
1643
+ */
1644
+ declare class MapFileStorage implements AppFileStorage {
1645
+ private files;
1646
+ constructor(initial?: Map<string, string>);
1647
+ getFile(_conversationId: string, path: string): Promise<AppFileRecord | null>;
1648
+ getFiles(_conversationId: string): Promise<AppFileRecord[]>;
1649
+ putFile(_conversationId: string, path: string, content: string): Promise<void>;
1650
+ putFiles(_conversationId: string, files: Array<{
1651
+ path: string;
1652
+ content: string;
1653
+ }>): Promise<void>;
1654
+ deleteFile(_conversationId: string, path: string): Promise<void>;
1655
+ /** Direct access to the underlying map. */
1656
+ getAll(): Map<string, string>;
1657
+ /** Number of files stored. */
1658
+ get size(): number;
1659
+ /** Serialize to JSON for persistence (e.g. Durable Object storage). */
1660
+ serialize(): string;
1661
+ /** Restore from a previously serialized JSON string. */
1662
+ static deserialize(json: string | undefined | null): MapFileStorage;
1663
+ }
1664
+ declare const CREATE_FILE_SCHEMA: {
1665
+ readonly name: "create_file";
1666
+ readonly description: "Build a complete, standalone interactive app from scratch — a calculator, a game, a todo list, a dashboard, a simulation, or another self-contained interactive tool. Writes files into the in-chat app project; the live preview renders automatically. Pass every file (App.js, App.css, package.json, ...) in a single call.\n\ncreate_file creates new files and can also overwrite existing ones for substantial restructures. For overwrites, you must have called read_file for the path earlier in this conversation (or created it via create_file in the same conversation) — the tool refuses otherwise. For incremental changes, prefer patch_file: it's easier to review and preserves surrounding code unchanged.";
1667
+ readonly arguments: {
1668
+ readonly type: "object";
1669
+ readonly properties: {
1670
+ readonly files: {
1671
+ readonly type: "array";
1672
+ readonly description: "Files to write. Each item: {\"path\": \"App.js\", \"content\": \"...\"}";
1673
+ readonly items: {
1674
+ readonly type: "object";
1675
+ readonly properties: {
1676
+ readonly path: {
1677
+ readonly type: "string";
1678
+ };
1679
+ readonly content: {
1680
+ readonly type: "string";
1681
+ };
1682
+ };
1683
+ readonly required: readonly ["path", "content"];
1684
+ };
1685
+ };
1686
+ };
1687
+ readonly required: readonly ["files"];
1688
+ };
1689
+ };
1690
+ declare const PATCH_FILE_SCHEMA: {
1691
+ readonly name: "patch_file";
1692
+ readonly description: "Edit, modify, or update an existing file in the app project via find-and-replace. Use this instead of create_file when changing a few lines of an existing app — small edits, fixes, or component updates.\n\nREQUIRED: you must call read_file (or create_file) for this path earlier in the conversation before calling patch_file. The tool refuses otherwise — you cannot patch a file whose current content you have not seen.\n\nPass a \"patches\" array of {find, replace} objects. Each \"find\" string must:\n - match the file exactly, character for character;\n - match EXACTLY ONE location — if your find appears multiple times, the patch is rejected as ambiguous. Include 2-3 lines of surrounding context to make it unique;\n - NOT include the \"<n>: \" line-number prefix from read_file output.";
1693
+ readonly arguments: {
1694
+ readonly type: "object";
1695
+ readonly properties: {
1696
+ readonly path: {
1697
+ readonly type: "string";
1698
+ readonly description: "File path to patch";
1699
+ };
1700
+ readonly patches: {
1701
+ readonly type: "array";
1702
+ readonly description: "Array of find/replace operations";
1703
+ readonly items: {
1704
+ readonly type: "object";
1705
+ readonly properties: {
1706
+ readonly find: {
1707
+ readonly type: "string";
1708
+ readonly description: "Exact string to find in the file";
1709
+ };
1710
+ readonly replace: {
1711
+ readonly type: "string";
1712
+ readonly description: "Replacement string";
1713
+ };
1714
+ };
1715
+ readonly required: readonly ["find", "replace"];
1716
+ };
1717
+ };
1718
+ };
1719
+ readonly required: readonly ["path", "patches"];
1720
+ };
1721
+ };
1722
+ declare const DELETE_FILE_SCHEMA: {
1723
+ readonly name: "delete_file";
1724
+ readonly description: "Deletes a file from the app project.";
1725
+ readonly arguments: {
1726
+ readonly type: "object";
1727
+ readonly properties: {
1728
+ readonly path: {
1729
+ readonly type: "string";
1730
+ readonly description: "File path to delete";
1731
+ };
1732
+ };
1733
+ readonly required: readonly ["path"];
1734
+ };
1735
+ };
1736
+ declare const READ_FILE_SCHEMA: {
1737
+ readonly name: "read_file";
1738
+ readonly description: "Reads the content of a file from the app project. Required before patch_file on any file whose content you have not just written via create_file. Returned content has \"<lineNumber>: \" prefixes for navigation — do not copy those prefixes into a patch_file \"find\" string.";
1739
+ readonly arguments: {
1740
+ readonly type: "object";
1741
+ readonly properties: {
1742
+ readonly path: {
1743
+ readonly type: "string";
1744
+ readonly description: "File path to read";
1745
+ };
1746
+ };
1747
+ readonly required: readonly ["path"];
1748
+ };
1749
+ };
1750
+ declare const LIST_FILES_SCHEMA: {
1751
+ readonly name: "list_files";
1752
+ readonly description: "Lists all files in the app project. Returns file paths and sizes. Use this to understand the current project structure.";
1753
+ readonly arguments: {
1754
+ readonly type: "object";
1755
+ readonly properties: {};
1756
+ readonly required: readonly [];
1757
+ };
1758
+ };
1759
+ declare const AUDIT_DESIGN_SCHEMA: {
1760
+ readonly name: "audit_design";
1761
+ readonly description: "Inspect the current app's design coherence. Reports raw hex colors outside :root (should be tokens), CSS variables declared but unused, missing :focus-visible rules on interactive elements, icon-only buttons without aria-label, images without alt, heading-level skips, and inline styles with hardcoded colors. Returns a 0-100 score and a list of issues with line numbers. Call this after substantial changes to verify the design system is intact, then patch the actionable issues.";
1762
+ readonly arguments: {
1763
+ readonly type: "object";
1764
+ readonly properties: {};
1765
+ readonly required: readonly [];
1766
+ };
1767
+ };
1768
+ declare const CRITIQUE_DESIGN_SCHEMA: {
1769
+ readonly name: "critique_design";
1770
+ readonly description: "Step back and look at what you just made. Returns App.js + App.css plus a rubric of open-ended questions for you to answer honestly in your next response — about intent, hierarchy, distinctiveness, coherence, and what's weakest. This is not a checklist of rules; it's an occasion to apply taste. After answering, patch the 2-3 weakest items you identified. Call this after the initial build and after substantial design changes — complementary to audit_design (which catches mechanical/accessibility issues), where critique_design catches taste and composition.";
1771
+ readonly arguments: {
1772
+ readonly type: "object";
1773
+ readonly properties: {};
1774
+ readonly required: readonly [];
1775
+ };
1776
+ };
1777
+ declare const VERIFY_APP_SCHEMA: {
1778
+ readonly name: "verify_app";
1779
+ readonly description: "Ask the host runtime whether the current app actually mounts. The host's bundler / preview iframe (e.g. Sandpack in a chat UI) is already compiling and running your code; this tool collects whatever errors that runtime captured — hallucinated imports, syntax the bundler rejects, undefined components, runtime crashes — and returns them. Use this after substantial changes and before declaring done. If `rendered` is false or `errors` is non-empty, fix every error before doing anything else. If the host did not wire a runtime verifier, the tool returns `{ rendered: true, errors: [] }` with a `note` explaining — degrade to audit_design + critique_design in that case.";
1780
+ readonly arguments: {
1781
+ readonly type: "object";
1782
+ readonly properties: {};
1783
+ readonly required: readonly [];
1784
+ };
1785
+ };
1786
+ /** Default open-ended questions for `critique_design`. Each is designed to
1787
+ * extract the model's own judgment rather than enforce a property — the
1788
+ * model brings the taste, the rubric only supplies the occasion. */
1789
+ declare const DEFAULT_DESIGN_CRITIQUE_RUBRIC: readonly string[];
1790
+ /**
1791
+ * Result returned by the host's `verifyApp` hook (and surfaced as the
1792
+ * `verify_app` tool's response to the model). Same shape regardless of
1793
+ * how the host implements verification — Sandpack error state, an
1794
+ * iframe error listener, a headless browser in a benchmark, etc.
1795
+ *
1796
+ * `rendered`: did the React app actually mount in the host's runtime?
1797
+ * True when the preview is showing content; false when the runtime
1798
+ * reported a fatal failure or never mounted within the host's wait
1799
+ * window.
1800
+ *
1801
+ * `errors`: human-readable error messages captured by the runtime. One
1802
+ * per distinct error. Order is host-defined; usually chronological.
1803
+ *
1804
+ * `note`: optional explanation when the result is degenerate — host
1805
+ * didn't wire the hook, hook is wired but the preview isn't mounted
1806
+ * yet, etc. Lets the model interpret the result correctly without
1807
+ * silently treating "no errors" as success.
1808
+ */
1809
+ interface VerifyAppResult {
1810
+ rendered: boolean;
1811
+ errors: string[];
1812
+ note?: string;
1813
+ }
1814
+ /**
1815
+ * Event emitted after a successful file mutation. Hosts subscribe via
1816
+ * the `onFileChange` option to {@link createAppGenerationTools} and
1817
+ * attach versioning (isomorphic-git, sqlite-history, etc.), audit
1818
+ * logs, analytics, or whatever else benefits from a per-mutation hook.
1819
+ *
1820
+ * Read operations (`read_file`, `list_files`) do not emit — they
1821
+ * don't mutate state.
1822
+ *
1823
+ * Batch `create_file` calls fan out to one event per file, with each
1824
+ * file getting `type: "created"` or `type: "modified"` depending on
1825
+ * whether it existed before the write.
1826
+ */
1827
+ type FileChangeEvent = {
1828
+ type: "created";
1829
+ conversationId: string;
1830
+ path: string;
1831
+ content: string;
1832
+ tool: "create_file";
1833
+ } | {
1834
+ type: "modified";
1835
+ conversationId: string;
1836
+ path: string;
1837
+ before: string;
1838
+ after: string;
1839
+ tool: "create_file" | "patch_file";
1840
+ } | {
1841
+ type: "deleted";
1842
+ conversationId: string;
1843
+ path: string;
1844
+ before: string;
1845
+ tool: "delete_file";
1846
+ };
1847
+ interface CreateAppGenerationToolsOptions {
1848
+ /** Returns the current conversation ID (may be null before first message). */
1849
+ getConversationId: () => string | null;
1850
+ /** Storage backend for file operations. */
1851
+ storage: AppFileStorage;
1852
+ /** Optional error logger. Falls back to console.error. */
1853
+ logError?: (message: string, error?: Error) => void;
1854
+ /**
1855
+ * Optional host callback that renders / refreshes the app preview in
1856
+ * the chat UI. When provided, it's invoked automatically after every
1857
+ * successful create_file / patch_file / delete_file — the model
1858
+ * never needs to call a separate display tool. Mirrors the
1859
+ * `displaySlides` pattern in `createSlideTools`.
1860
+ *
1861
+ * The callback receives `{ title?, replaces_interaction_id? }` and
1862
+ * should return `{ interaction_id, title? }` so subsequent calls can
1863
+ * thread `replaces_interaction_id` and fold updates onto the same card.
1864
+ */
1865
+ displayApp?: (args: Record<string, unknown>) => Promise<unknown> | unknown;
1866
+ /**
1867
+ * Optional host callback that runs the generated app in whatever
1868
+ * runtime the host has available (Sandpack iframe in a chat UI, a
1869
+ * headless browser in a benchmark, etc.) and reports back whether
1870
+ * the app mounted plus any errors the runtime captured.
1871
+ *
1872
+ * When wired, the SDK exposes a `verify_app` tool the model can call
1873
+ * after substantial changes; the tool's executor invokes this hook
1874
+ * and returns the result to the model. When NOT wired, the tool
1875
+ * still exists but returns `{ rendered: true, errors: [], note: … }`
1876
+ * so the model degrades gracefully to audit/critique-only feedback.
1877
+ *
1878
+ * Same host-hook pattern as `displayApp` and `onFileChange` — the
1879
+ * SDK never executes anything itself.
1880
+ */
1881
+ verifyApp?: () => Promise<VerifyAppResult>;
1882
+ /**
1883
+ * Optional host callback fired after every successful file mutation
1884
+ * (`create_file`, `patch_file`, `delete_file`). See
1885
+ * {@link FileChangeEvent} for the event shape.
1886
+ *
1887
+ * Use this to attach versioning, audit logs, or analytics from the
1888
+ * host side without wrapping the storage adapter. The callback is
1889
+ * `await`ed but a thrown error is logged and swallowed — a host
1890
+ * subsystem failure should not fail the underlying tool.
1891
+ *
1892
+ * Read tools (`read_file`, `list_files`) and failed mutations
1893
+ * (validation errors, patch ambiguity, syntax errors) do not emit.
1894
+ */
1895
+ onFileChange?: (event: FileChangeEvent) => Promise<void> | void;
1896
+ /**
1897
+ * Override the open-ended rubric returned by `critique_design`. Each
1898
+ * string is a question the model is expected to answer in its next
1899
+ * response about the current App.js + App.css. Defaults to
1900
+ * {@link DEFAULT_DESIGN_CRITIQUE_RUBRIC} — six questions covering
1901
+ * intent, hierarchy, distinctiveness, coherence, the weakest decision,
1902
+ * and what to cut. Pass a custom rubric to steer the kind of critique
1903
+ * the model performs (e.g. brand-specific concerns).
1904
+ */
1905
+ designCritiqueRubric?: readonly string[];
1906
+ /**
1907
+ * Cap on the number of distinct conversations whose per-conversation
1908
+ * state (read-before-write tracking, patch-failure tallies, display
1909
+ * cache) is retained in memory. When a new conversation pushes the
1910
+ * count over the cap, the oldest entry is evicted (FIFO by first
1911
+ * touch).
1912
+ *
1913
+ * Defaults to {@link DEFAULT_MAX_CONVERSATIONS}. The factory is
1914
+ * typically long-lived in a server process, so this prevents the
1915
+ * maps from growing unboundedly as conversations churn. An evicted
1916
+ * conversation that comes back will need to call `read_file` again
1917
+ * before `patch_file` — same as a cold start.
1918
+ *
1919
+ * Set to a positive integer; non-positive values fall back to the
1920
+ * default.
1921
+ */
1922
+ maxConversations?: number;
1923
+ }
1924
+ /** Default cap for {@link CreateAppGenerationToolsOptions.maxConversations}. */
1925
+ declare const DEFAULT_MAX_CONVERSATIONS = 1000;
1926
+ /**
1927
+ * Creates the suite of app generation tools (create_file, patch_file, delete_file,
1928
+ * read_file, list_files, audit_design, critique_design, verify_app) backed by
1929
+ * the provided storage adapter.
1930
+ *
1931
+ * Give the tool loop running these at least 8 tool rounds per turn
1932
+ * (`maxToolRounds` in `runToolLoop` / `useChat`). The workflow the App Builder
1933
+ * prompt mandates — re-orient, create, critique, patch, audit, patch, verify —
1934
+ * takes 7–8 rounds on a substantial turn; a smaller budget silently starves
1935
+ * the trailing `verify_app` call, so the host's runtime verification never runs.
1936
+ */
1937
+ declare function createAppGenerationTools({ getConversationId, storage, logError, displayApp, verifyApp, onFileChange, designCritiqueRubric, maxConversations, }: CreateAppGenerationToolsOptions): ToolConfig[];
1938
+
1939
+ /**
1940
+ * Returns the {@link APP_BUILDER_PROMPT} constant. Thin function wrapper
1941
+ * kept for callers that import a builder rather than a string constant
1942
+ * (e.g. existing test setups). The prompt itself is now a static
1943
+ * constant — there is no per-call assembly.
1944
+ */
1945
+ declare function buildAppSystemPrompt(): string;
1946
+ /**
1947
+ * Build a compact manifest of the conversation's current app files, for
1948
+ * injection into each turn's context as a system message.
1949
+ *
1950
+ * This is the anchor of the *turn envelope* pattern, which keeps per-request
1951
+ * token cost flat across arbitrarily long edit sessions. The files in
1952
+ * storage — not the conversation — are the durable state: every prior change
1953
+ * is already encoded in them, so old tool traffic adds cost without adding
1954
+ * information. Hosts that resend tool history across turns pay per-turn cost
1955
+ * that grows linearly with turn count (quadratic total); hosts using the
1956
+ * envelope pay a cost bounded by app size, the same at request 5 and
1957
+ * request 100.
1958
+ *
1959
+ * The envelope recipe, per change request:
1960
+ * 1. system: `APP_BUILDER_PROMPT`
1961
+ * 2. system: this manifest (rebuilt from storage each turn)
1962
+ * 3. the last few user/assistant TEXT exchanges (drop all tool messages)
1963
+ * 4. the new user request
1964
+ *
1965
+ * In-turn tool traffic stays within the turn; `maxToolRounds` bounds the
1966
+ * rounds and `maxTurnTokens` (on `runToolLoop`) gives a hard ceiling. The
1967
+ * model re-reads the files it wants to edit (`read_file` results are
1968
+ * truncation-bounded), so per-turn cost is a function of app size and the
1969
+ * text window — never of how many turns came before.
1970
+ *
1971
+ * The manifest deliberately lists paths and sizes only, no content: the
1972
+ * read-before-write contract tracks what the model has actually seen via
1973
+ * `read_file`/`create_file`, and inlined-but-unread content would invite
1974
+ * patches against text the gate can't vouch for.
1975
+ */
1976
+ declare function buildAppFileManifest(opts: {
1977
+ storage: AppFileStorage;
1978
+ conversationId: string;
1979
+ }): Promise<string>;
1980
+
1981
+ /**
1982
+ * Typed catalog of Google Fonts that SlideElement.fontFamily can reference.
1983
+ *
1984
+ * The model sees this library in the plan_deck tool result and can reach for
1985
+ * any entry as a per-element `fontFamily` override — e.g. pair the deck's
1986
+ * theme body (Inter) with a dramatic Abril Fatface hero title on the cover,
1987
+ * or a short Caveat script accent under a pull quote.
1988
+ *
1989
+ * Invariants:
1990
+ * - Every font in FONT_PRESETS (heading or body) is also in FONT_LIBRARY.
1991
+ * Tests enforce this so the theme default pairings always resolve.
1992
+ * - The renderer loads only the fonts actually used by a given deck via
1993
+ * buildFontsUrl(), so per-element overrides are zero-cost when unused.
1994
+ *
1995
+ * Why typed data and not strings: as with layouts + palettes, a structured
1996
+ * catalog lets us (a) validate fontFamily at write time (reject typos
1997
+ * before they hit the renderer), (b) render a compact, role-grouped prompt
1998
+ * block, and (c) derive the Google Fonts URL deterministically.
1999
+ */
2000
+ type FontCategory = "display" | "serif-body" | "sans-body" | "mono" | "accent";
2001
+ interface FontSpec {
2002
+ /** Exact Google Fonts family name — used verbatim as TextElement.fontFamily. */
2003
+ name: string;
2004
+ /** Role bucket — display (hero), body (reading), mono (chrome), accent (sparingly). */
2005
+ category: FontCategory;
2006
+ /** One-line feel/usage hint shown to the model next to the name. */
2007
+ feel: string;
2008
+ /**
2009
+ * Semicolon-separated weight list for the Google Fonts CSS2 URL, e.g.
2010
+ * "400;500;700". Empty string means the family is single-weight (no
2011
+ * :wght@ segment in the slug).
2012
+ */
2013
+ weights: string;
2014
+ }
2015
+ declare const FONT_LIBRARY: FontSpec[];
2016
+ /** Look up a font by exact Google Fonts family name. Returns null if unknown. */
2017
+ declare function getFontByName(name: string): FontSpec | null;
2018
+ /** Validate a fontFamily value against the library. Returns true if accepted. */
2019
+ declare function isKnownFont(name: string): boolean;
2020
+ /**
2021
+ * Build the Google Fonts CSS2 URL for a set of font families. Takes known
2022
+ * library names (resolved with each font's own weight list) and optional
2023
+ * extra slugs (for Material Symbols etc.). Returns a single URL with
2024
+ * display=swap. Unknown names are silently skipped — callers that care
2025
+ * should validate with `isKnownFont` first.
2026
+ */
2027
+ declare function buildFontsUrl(names: string[], extraSlugs?: string[]): string;
2028
+
2029
+ /**
2030
+ * Anuma JSX AST — the in-memory model for slides and app mockups.
2031
+ *
2032
+ * Every artifact is a tree of `AnumaNode` values rooted at an `<Anuma.Deck>`
2033
+ * (slides) or `<Anuma.Screen>` (apps — Stage 6+). There are no typed
2034
+ * per-tag interfaces; the tree is tag-agnostic. Consumers walk the tree
2035
+ * and dispatch on `node.tag`.
2036
+ *
2037
+ * Vocabulary (checked at parse time):
2038
+ * - Containers: `<Anuma.Deck>`, `<Anuma.Slide>`, `<Anuma.Group>`.
2039
+ * - Leaves: `<Anuma.Text>`, `<Anuma.Image>`, `<Anuma.Rect>`, `<Anuma.Circle>`,
2040
+ * `<Anuma.Line>`, `<Anuma.Icon>`.
2041
+ *
2042
+ * All coordinates are container-relative pixels — the slide canvas is
2043
+ * 960×540 px, any child's `x`/`y`/`w`/`h` are pixels measured from the
2044
+ * parent's top-left. Containers may opt into a flex layout with
2045
+ * `layout="row" | "column"` plus `gap` / `padding` / `justify` / `align`;
2046
+ * children inside a flex container skip `x`/`y` and flow instead.
2047
+ *
2048
+ * Numeric attrs are JSX expressions (`x={96}`), strings are quoted
2049
+ * literals, booleans use `{true}`/`{false}` (or bare `hidden` for true).
2050
+ * Dynamic expressions (`{someVar}`) are rejected in this version.
2051
+ *
2052
+ * @module tools/slides/jsx
2053
+ */
2054
+ /** Scalar value an attribute can hold. */
2055
+ type AttrScalar$1 = string | number | boolean;
2056
+ /**
2057
+ * An object-valued attribute — used primarily for `style={{}}`. Keys are
2058
+ * CSS property names in camelCase (`fontSize`, `borderRadius`, …); values
2059
+ * are scalars.
2060
+ */
2061
+ type AttrObject$1 = Record<string, AttrScalar$1>;
2062
+ type AttrValue = AttrScalar$1 | AttrObject$1;
2063
+ type AnumaChild = AnumaNode | string;
2064
+ /**
2065
+ * A node in the Anuma tree. `tag` is the local name after `Anuma.` (e.g.
2066
+ * `"Text"`, `"Slide"`). Children are other nodes for containers, or a
2067
+ * single string (the body text) for `<Anuma.Text>`.
2068
+ */
2069
+ interface AnumaNode {
2070
+ tag: string;
2071
+ attrs: Record<string, AttrValue>;
2072
+ children: AnumaChild[];
2073
+ }
2074
+ declare class AnumaJsxError extends Error {
2075
+ readonly line?: number;
2076
+ readonly column?: number;
2077
+ constructor(message: string, loc?: {
2078
+ line: number;
2079
+ column: number;
2080
+ } | null);
2081
+ }
2082
+ /**
2083
+ * `Anuma.*` primitives — things that aren't native HTML concepts. Kept
2084
+ * deliberately small; general content uses raw HTML.
2085
+ *
2086
+ * - Deck / Slide / Screen: layout containers with canvas semantics.
2087
+ * - Text / Image / Icon / Group: preserved for slide templates and LLM
2088
+ * fluency (with theme-token bindings on Text). Safe to mix with HTML.
2089
+ * - Rect / Circle / Line: SVG shape abstractions.
2090
+ */
2091
+ declare const ANUMA_TAGS: readonly ["Deck", "Slide", "Screen", "Group", "Text", "Span", "Image", "Rect", "Circle", "Line", "Icon"];
2092
+ type KnownTag = (typeof ANUMA_TAGS)[number];
2093
+ /**
2094
+ * Parse a JSX source string into an AnumaNode tree.
2095
+ *
2096
+ * `strict` mode (opt-in, defaults to `false`) enables checks that catch
2097
+ * model-emitted JSX with the wrong convention before it lands in the deck:
2098
+ * top-level visual-styling props on text elements (which the renderer
2099
+ * silently ignores → invisible output). Stored decks load with strict off
2100
+ * — any deck previously built before the strict check existed might carry
2101
+ * non-conforming JSX, and we don't want a tightened validator to retro-
2102
+ * actively break every tool call on that deck. Callers that parse
2103
+ * model-submitted JSX (`add_slide`, `insert_slide`, `replace_slide`,
2104
+ * `replace_element`, `insert_element`) pass `strict: true`.
2105
+ */
2106
+ declare function parseJsx(source: string, options?: {
2107
+ strict?: boolean;
2108
+ }): AnumaNode;
2109
+ /** True when a tag is an Anuma primitive (capitalized local name). */
2110
+ declare function isAnumaTag(tag: string): boolean;
2111
+ /** True when a tag is a plain HTML element from the allowlist. */
2112
+ declare function isHtmlTag(tag: string): boolean;
2113
+ interface SerializeOptions {
2114
+ /** Indent per level (default two spaces). */
2115
+ indent?: string;
2116
+ /** Max single-line width before attrs break onto multiple lines (default 100). */
2117
+ maxLineWidth?: number;
2118
+ }
2119
+ declare function serializeJsx(node: AnumaNode, options?: SerializeOptions): string;
2120
+ /** Return `attrs.id` as a string, or undefined. */
2121
+ declare function getId(node: AnumaNode): string | undefined;
2122
+ /**
2123
+ * Walk the tree depth-first. Visitor sees `(node, parent)` — parent is
2124
+ * `null` for the root. Return `false` from the visitor to skip descending
2125
+ * into a node's children.
2126
+ */
2127
+ declare function walk(root: AnumaNode, visitor: (node: AnumaNode, parent: AnumaNode | null) => void | false): void;
2128
+ /** Find the first node whose `attrs.id` matches `id`. */
2129
+ declare function findById(root: AnumaNode, id: string): AnumaNode | null;
2130
+ /** Find the parent of the node with matching id (null if root or missing). */
2131
+ declare function findParentOfId(root: AnumaNode, id: string): AnumaNode | null;
2132
+ /**
2133
+ * Replace the first node with matching id in the tree. Mutates `root` in
2134
+ * place. Returns true on success.
2135
+ */
2136
+ declare function replaceById(root: AnumaNode, id: string, next: AnumaNode): boolean;
2137
+ /**
2138
+ * Insert `node` into `parent.children`. If `afterId` is provided, the new
2139
+ * node is inserted immediately after the matched sibling; otherwise it is
2140
+ * appended to the end.
2141
+ */
2142
+ declare function insertChild(parent: AnumaNode, node: AnumaNode, afterId?: string): void;
2143
+ /**
2144
+ * Insert `node` immediately after the node with matching id anywhere in
2145
+ * the tree. Returns true on success.
2146
+ */
2147
+ declare function insertAfterId(root: AnumaNode, afterId: string, node: AnumaNode): boolean;
2148
+ /** Remove the node with matching id. Mutates `root`. Returns true on success. */
2149
+ declare function removeById(root: AnumaNode, id: string): boolean;
2150
+ /** Shallow-merge attrs onto `node`. Ignores prototype-pollution keys. */
2151
+ declare function updateAttrs(node: AnumaNode, patch: Record<string, unknown>): void;
2152
+ /** Read a string attr, returning undefined if absent or wrong type. */
2153
+ declare function getStringAttr(node: AnumaNode, name: string): string | undefined;
2154
+ /** Read a number attr, returning undefined if absent or wrong type. */
2155
+ declare function getNumberAttr(node: AnumaNode, name: string): number | undefined;
2156
+
2157
+ /**
2158
+ * Backwards-compat for slide decks generated before the JSX migration.
2159
+ *
2160
+ * Older decks were persisted as JSON with a typed `SlideDeck` shape:
2161
+ *
2162
+ * {
2163
+ * version: 2,
2164
+ * theme: { fontPreset, colors: { background, slideBg, ... } },
2165
+ * slides: [{
2166
+ * id, background?,
2167
+ * elements: [{ kind: 'text'|'image'|'shape'|'icon', id, x, y, w, h, ... }],
2168
+ * }],
2169
+ * }
2170
+ *
2171
+ * Coordinates were percent-of-canvas (0..100). The new runtime stores
2172
+ * decks as `<Anuma.Deck>` JSX with pixel coordinates on a 960×540 slide.
2173
+ *
2174
+ * This module converts legacy JSON → an `AnumaNode` tree so consumers
2175
+ * can load old decks transparently and (if they want) re-serialise as
2176
+ * JSX to migrate the persisted form forward.
2177
+ */
2178
+
2179
+ interface LegacyTheme {
2180
+ fontPreset?: string;
2181
+ colors?: {
2182
+ background?: string;
2183
+ slideBg?: string;
2184
+ surfaceSecondary?: string;
2185
+ textPrimary?: string;
2186
+ textSecondary?: string;
2187
+ textMuted?: string;
2188
+ accent?: string;
2189
+ card?: string;
2190
+ border?: string;
2191
+ };
2192
+ }
2193
+ interface LegacyBaseElement {
2194
+ id?: string;
2195
+ x?: number;
2196
+ y?: number;
2197
+ w?: number;
2198
+ h?: number;
2199
+ rotation?: number;
2200
+ }
2201
+ interface LegacyTextElement extends LegacyBaseElement {
2202
+ kind: "text";
2203
+ text?: string;
2204
+ fontSize?: number;
2205
+ fontRole?: "heading" | "body";
2206
+ fontWeight?: number;
2207
+ color?: string;
2208
+ align?: "left" | "center" | "right";
2209
+ lineHeight?: number;
2210
+ letterSpacing?: number;
2211
+ fontStyle?: "italic" | "normal";
2212
+ textTransform?: "uppercase" | "none";
2213
+ fontFamily?: string;
2214
+ }
2215
+ interface LegacyImageElement extends LegacyBaseElement {
2216
+ kind: "image";
2217
+ src?: string;
2218
+ cornerRadius?: number;
2219
+ }
2220
+ interface LegacyShapeElement extends LegacyBaseElement {
2221
+ kind: "shape";
2222
+ shape?: "rect" | "circle" | "line";
2223
+ fill?: string;
2224
+ stroke?: string;
2225
+ strokeWidth?: number;
2226
+ cornerRadius?: number;
2227
+ }
2228
+ interface LegacyIconElement extends LegacyBaseElement {
2229
+ kind: "icon";
2230
+ name?: string;
2231
+ color?: string;
2232
+ fontSize?: number;
2233
+ }
2234
+ type LegacyElement = LegacyTextElement | LegacyImageElement | LegacyShapeElement | LegacyIconElement;
2235
+ interface LegacySlide {
2236
+ id?: string;
2237
+ background?: string;
2238
+ elements?: LegacyElement[];
2239
+ }
2240
+ /** Legacy persisted deck shape. `version: 2` was the only version that ever shipped. */
2241
+ interface LegacyDeckJson {
2242
+ version?: number;
2243
+ theme?: LegacyTheme;
2244
+ slides?: LegacySlide[];
2245
+ }
2246
+ /**
2247
+ * Detect whether `raw` looks like a legacy JSON deck. Cheap structural
2248
+ * check — used by callers that want to branch between `parseJsx` and
2249
+ * the legacy converter without throwing on every JSX parse attempt.
2250
+ *
2251
+ * Accepts a string (JSON-encoded) or a parsed object.
2252
+ */
2253
+ declare function isLegacyDeckJson(raw: unknown): raw is LegacyDeckJson;
2254
+ /**
2255
+ * Convert a legacy JSON deck (string or already-parsed object) into a
2256
+ * fresh `<Anuma.Deck>` AnumaNode tree. Returns null if the input
2257
+ * doesn't parse as JSON or doesn't look like the legacy shape.
2258
+ *
2259
+ * Defensive defaults match the constants in the deleted
2260
+ * `legacy-slide-types.ts`: undefined colors fall back to the original
2261
+ * dark theme, undefined elements get sensible numeric defaults so
2262
+ * partially-corrupted decks still render.
2263
+ */
2264
+ declare function convertLegacyDeckJson(raw: unknown): AnumaNode | null;
2265
+
2266
+ /**
2267
+ * Slide deck tools for LLM-driven presentation generation.
2268
+ *
2269
+ * Provides plan_deck, add_slide, read_slides, and patch_slides tool
2270
+ * configurations, along with the type system, coordinate helpers, font
2271
+ * presets, and system prompt that drive the slide generation flow.
2272
+ *
2273
+ * Slide decks are stored as a JSX document (rooted at `<Anuma.Deck>`) in
2274
+ * a file named `slides.jsx` via the shared `AppFileStorage` interface. The
2275
+ * LLM initializes an empty deck with `plan_deck`, appends slides one at a
2276
+ * time via `add_slide`, and applies incremental edits with `patch_slides`.
2277
+ * All positions use container-relative pixels on a 960×540 slide canvas.
2278
+ *
2279
+ * When a `displaySlides` callback is supplied to `createSlideTools`, the
2280
+ * deck-display UI interaction is emitted automatically from inside
2281
+ * `plan_deck`, `add_slide`, and `patch_slides` — no separate display
2282
+ * tool needed. The SDK tracks the interaction_id internally so per-slide
2283
+ * appends update the same viewer in-place.
2284
+ *
2285
+ * @example
2286
+ * ```typescript
2287
+ * import { createSlideTools, buildSlideSystemPrompt } from "@anuma/sdk/tools";
2288
+ *
2289
+ * const slideTools = createSlideTools({
2290
+ * getConversationId,
2291
+ * storage,
2292
+ * displaySlides: async (args) => ({ ...args, displayType: "slides" }),
2293
+ * });
2294
+ *
2295
+ * await sendMessage({
2296
+ * messages: [...],
2297
+ * tools: slideTools,
2298
+ * systemPrompt: buildSlideSystemPrompt(),
2299
+ * });
2300
+ * ```
2301
+ *
2302
+ * @module tools/slides
2303
+ */
2304
+
2305
+ declare const SLIDE_CANVAS_WIDTH = 960;
2306
+ declare const SLIDE_CANVAS_HEIGHT = 540;
2307
+ declare const THEME_ATTRS: readonly ["background", "slideBg", "surfaceSecondary", "textPrimary", "textSecondary", "textMuted", "accent", "card", "border"];
2308
+ type ThemeAttr = (typeof THEME_ATTRS)[number];
2309
+ interface FontPreset {
2310
+ heading: string;
2311
+ body: string;
2312
+ /** Semicolon-separated weights, e.g. "400;500;700". */
2313
+ weights: string;
2314
+ /** Google Fonts family slug (for building a stylesheet URL). */
2315
+ slug: string;
2316
+ }
2317
+ declare const FONT_PRESETS: Record<string, FontPreset>;
2318
+ /** Layout modes a container (Deck/Slide/Group) may opt into. */
2319
+ declare const LAYOUT_MODES: readonly ["absolute", "row", "column"];
2320
+ type LayoutMode = (typeof LAYOUT_MODES)[number];
2321
+ declare const READ_SLIDES_SCHEMA: {
2322
+ readonly name: "read_slides";
2323
+ readonly description: "Get the current slide deck. Defaults to a compact summary — slide ids, layout names, element ids per slide, and short text previews — typically 5-10× smaller than the full deck JSX. Use the summary to LOCATE the slide(s) you want to edit, then call patch_slides with the ids you found. When you need to see a slide's full JSX (e.g. to copy an existing style block, or to inspect element geometry before patching), pass slideIds: [\"s4\"] and read_slides will include the full JSX of just those slides alongside the summary. Do NOT request slideIds you don't need to read — every slide you list pays full token cost.";
2324
+ readonly arguments: {
2325
+ readonly type: "object";
2326
+ readonly properties: {
2327
+ readonly slideIds: {
2328
+ readonly type: "array";
2329
+ readonly items: {
2330
+ readonly type: "string";
2331
+ };
2332
+ readonly description: "Optional list of slide ids to return in full JSX form (alongside the summary). Use sparingly — only ask for slides you must inspect to write a patch. Omit entirely for the cheap summary-only response.";
2333
+ };
2334
+ };
2335
+ };
2336
+ };
2337
+ declare const PATCH_SLIDES_SCHEMA: {
2338
+ readonly name: "patch_slides";
2339
+ readonly description: "Apply patch operations to an existing slide deck — change a slide's text, swap a layout, update the theme, or add and remove slides. Pair with read_slides when the user wants to change something in their deck.\n\nThe deck re-renders inline automatically after each patch; pass replaces_interaction_id from a previous add_slide/patch_slides result to update the same deck viewer in-place (otherwise a new viewer is created).\n\nOperations (set 'action' and the fields noted):\n- update_element: { slideId, elementId, attrs?, text? } — surgically patch ONE element WITHOUT rewriting it. Use 'attrs' for x/y/w/h moves and style tweaks (deep-merges; passing { style: { fontSize: 24 } } keeps every other style key intact). Use 'text' to rename or rewrite the element's text body — only the child string changes, every attr and style key is preserved exactly. Either field works alone or together. PREFER THIS for any edit that doesn't change the element's tag. Renames, recolors, moves, resizes all belong here. ~10× fewer output tokens than replace_element and you can't accidentally degrade the original styling.\n- replace_element: { slideId, elementId, jsx } — replace a matched element with a new content-element JSX fragment. Use ONLY when the element's TAG changes (e.g. swapping a <Anuma.Text> for an <Anuma.Image>). For text rename / styling changes / position changes use update_element instead — replace_element forces you to rewrite every attr from scratch and is the easiest place to silently lose design-system styling.\n- insert_element: { slideId, jsx, afterElementId? } — insert a new content-element JSX fragment. Appends to the end when afterElementId is omitted.\n- remove_element: { slideId, elementId }.\n- replace_slide: { slideId, jsx } — replace an entire <Anuma.Slide> with a new JSX fragment (root must be <Anuma.Slide>).\n- insert_slide: { jsx, layout, afterSlideId? } — insert a new <Anuma.Slide>. 'layout' is REQUIRED: pass the compound \"<composition>--<system>\" name pattern used by the existing slides. The executor validates the slide's slot content against the recipe and REJECTS the op if layout is missing — same contract as add_slide. To match the deck visually: call read_slides with slideIds:[an existing slide id] to copy its element structure, then keep the same \"--<system>\" suffix in the layout name. Appends to the end when afterSlideId is omitted.\n- remove_slide: { slideId }.\n- update_theme: { set: { fontPreset?, colors? } } — structured partial patch for theme metadata (no tree shape to rewrite).";
2340
+ readonly arguments: {
2341
+ readonly type: "object";
2342
+ readonly properties: {
2343
+ readonly replaces_interaction_id: {
2344
+ readonly type: "string";
2345
+ readonly description: "interaction_id from a previous add_slide or patch_slides result. Pass this to update the existing deck viewer in-place instead of creating a new one.";
2346
+ };
2347
+ readonly operations: {
2348
+ readonly type: "array";
2349
+ readonly description: "Array of operations to apply in order";
2350
+ readonly items: {
2351
+ readonly type: "object";
2352
+ readonly properties: {
2353
+ readonly action: {
2354
+ readonly type: "string";
2355
+ readonly enum: readonly ["update_element", "replace_element", "insert_element", "remove_element", "replace_slide", "insert_slide", "remove_slide", "update_theme"];
2356
+ readonly description: "The operation to perform";
2357
+ };
2358
+ readonly slideId: {
2359
+ readonly type: "string";
2360
+ readonly description: "Target slide id";
2361
+ };
2362
+ readonly elementId: {
2363
+ readonly type: "string";
2364
+ readonly description: "Target element id (for update_element, replace_element, and remove_element)";
2365
+ };
2366
+ readonly attrs: {
2367
+ readonly type: "object";
2368
+ readonly properties: {};
2369
+ readonly additionalProperties: true;
2370
+ readonly description: "For update_element: partial attrs to merge onto the existing element (e.g. { x: 100, y: 60 } moves it). When 'style' is an object, its keys merge with the existing style — other style keys are preserved.";
2371
+ };
2372
+ readonly text: {
2373
+ readonly type: "string";
2374
+ readonly description: "For update_element: replace the element's text body. Only the child string changes; every attr and style key is preserved. Use this for renames and copy-rewrites — replace_element rewrites the whole element from scratch and is the easiest way to drop the recipe's design-system styling. Pass alongside attrs to combine a text edit with a position/style nudge.";
2375
+ };
2376
+ readonly jsx: {
2377
+ readonly type: "string";
2378
+ readonly description: "JSX fragment: an <Anuma.Slide> for slide ops, or a single content element (<Anuma.Text>, <Anuma.Image>, <Anuma.Rect>, <Anuma.Circle>, <Anuma.Line>, <Anuma.Icon>, <Anuma.Group>) for element ops.";
2379
+ };
2380
+ readonly afterSlideId: {
2381
+ readonly type: "string";
2382
+ readonly description: "Insert new slide after this id. Omit to append at the end.";
2383
+ };
2384
+ readonly layout: {
2385
+ readonly type: "string";
2386
+ readonly description: "For insert_slide: the compound \"<composition>--<system>\" layout name (same form as add_slide). Always pass this so insert_slide can validate the new slide's slot structure against the recipe and reject off-template JSX.";
2387
+ };
2388
+ readonly afterElementId: {
2389
+ readonly type: "string";
2390
+ readonly description: "Insert new element after this id within the target slide. Omit to append at the end.";
2391
+ };
2392
+ readonly set: {
2393
+ readonly type: "object";
2394
+ readonly properties: {};
2395
+ readonly additionalProperties: true;
2396
+ readonly description: "For update_theme: partial theme fields to merge (fontPreset, colors).";
2397
+ };
2398
+ };
2399
+ readonly required: readonly ["action"];
2400
+ };
2401
+ };
2402
+ };
2403
+ readonly required: readonly ["operations"];
2404
+ };
2405
+ };
2406
+ declare const PLAN_DECK_SCHEMA: {
2407
+ readonly name: "plan_deck";
2408
+ readonly description: "Create a new slide deck or presentation — use when the user asks to make, build, generate, or start a new slide deck, slideshow, PowerPoint, or Keynote presentation.\n\nCall this FIRST — once — to set up the theme, title, slide count, and layout pool. You pick:\n - title: presentation title (shown above the deck in the UI)\n - fontPreset: one of the font-preset keys\n - paletteName: the register name from the palette table (e.g. \"warm editorial\", \"techno dark\")\n - slideCount: how many slides the deck will have (commit to a concrete number)\n - layouts: the subset of compound \"<composition>--<system>\" layout names from the LAYOUT CATALOG you intend to use across the deck. Each subsequent add_slide call must pick from this list, and every name must share the same \"--<system>\" suffix.\n - accent: a 6-digit hex — the brand accent color for the deck. Set it to a hex that fits this deck's specific brand or topic. Skip ONLY for genuinely brand-ambiguous topics.\n\nThe result contains the full <Anuma.*> element recipes ONLY for the layouts you named in `layouts`, plus <Anuma.*> tag schemas, coordinate-system notes, and the palette hex values — everything you need for the add_slide calls that follow. Naming a tight subset keeps the context small and keeps the deck visually coherent.\n\nAfter plan_deck, call add_slide ONCE PER SLIDE, in order, passing the slide as a <Anuma.Slide> JSX fragment, until you've appended slideCount slides. Each add_slide call reports how many slides remain, so you can track progress.\n\nFor editing an existing deck (read_slides + patch_slides flow), do NOT call plan_deck.";
2409
+ readonly arguments: {
2410
+ readonly type: "object";
2411
+ readonly properties: {
2412
+ readonly title: {
2413
+ readonly type: "string";
2414
+ readonly description: "Title of the presentation (shown above the deck in the UI).";
2415
+ };
2416
+ readonly fontPreset: {
2417
+ readonly type: "string";
2418
+ readonly description: "Font preset key (default, editorial, geometric, humanist, bold, elegant, clean, techno)";
2419
+ };
2420
+ readonly paletteName: {
2421
+ readonly type: "string";
2422
+ readonly description: "Palette register name from the CHOOSING A PALETTE table. Copy exactly (e.g. 'warm editorial').";
2423
+ };
2424
+ readonly slideCount: {
2425
+ readonly type: "integer";
2426
+ readonly minimum: 3;
2427
+ readonly maximum: 19;
2428
+ readonly description: "How many slides this deck will have. Pick a number between 3 and 19 based on the user's ask and the topic's depth. You will then call add_slide exactly this many times.";
2429
+ };
2430
+ readonly layouts: {
2431
+ readonly type: "array";
2432
+ readonly items: {
2433
+ readonly type: "string";
2434
+ };
2435
+ readonly minItems: 1;
2436
+ readonly description: "Compound layout names (from the LAYOUT CATALOG) you plan to use across this deck. Pick a small, well-matched subset — typically 3–8 layouts for a deck of 10 slides. Each name must be of the form '<composition>--<system>' (e.g. 'cover-split-portrait--editorial-warm'); every entry must share the same '--<system>' suffix so the deck stays in one visual identity. Each add_slide call must use a layout from this list; picking a layout outside it is rejected.";
2437
+ };
2438
+ readonly accent: {
2439
+ readonly type: "string";
2440
+ readonly pattern: "^#[0-9A-Fa-f]{6}$";
2441
+ readonly description: "Brand accent color as a 6-digit hex. Almost every deck should set this. Pick a hex that fits this deck's specific brand or topic — what a designer would pick for THIS deck, not a generic industry default. Omit only when the topic is genuinely brand-ambiguous (internal status update, generic example).";
2442
+ };
2443
+ };
2444
+ readonly required: readonly ["title", "fontPreset", "paletteName", "slideCount", "layouts"];
2445
+ };
2446
+ };
2447
+ declare const ADD_SLIDE_SCHEMA: {
2448
+ readonly name: "add_slide";
2449
+ readonly description: "Append ONE slide to the deck as a <Anuma.Slide> JSX fragment. Call this once per slide after plan_deck, in order, until you've added slideCount slides. The deck re-renders inline after each add_slide so the viewer updates in-place.\n\nThe response reports: (a) how many slides remain to reach the planned slideCount, (b) which layouts you've used so far and how many times.\n\nBATCHING: Prefer to emit ALL add_slide calls in a single assistant turn (as parallel tool calls) once plan_deck has returned. The deck plan already tells you the layouts and slideCount, so you can design every slide up-front and call add_slide N times in one turn. This cuts the number of LLM round-trips from N+1 to 2. Parallel calls don't see each other's per-call layout-usage feedback inside the same turn — that's fine because the layout subset is fixed at plan_deck time. Only fall back to serial calls if you genuinely need to react to each result before designing the next slide.\n\nExample slideJsx:\n <Anuma.Slide id=\"cover\">\n <Anuma.Text id=\"title\" x={96} y={216} w={768} h={108} fontSize={58} fontRole=\"heading\" fontWeight={700} color=\"textPrimary\" align=\"center\">Welcome</Anuma.Text>\n </Anuma.Slide>\n\nEach composition recipe already defines its own chrome (header/footer slots) — copy the recipe verbatim and fill the role-tagged slots; do not invent a separate \"header pattern\".";
2450
+ readonly arguments: {
2451
+ readonly type: "object";
2452
+ readonly properties: {
2453
+ readonly slideIndex: {
2454
+ readonly type: "integer";
2455
+ readonly description: "1-based position of this slide in the deck (1 = cover, slideCount = last). REQUIRED when emitting multiple add_slide calls in parallel in one turn (the recommended pattern) — without it the parallel calls land in non-deterministic order. Resubmitting with the same slideIndex overwrites the slide at that position; omit only for serial single-slide flows.";
2456
+ readonly minimum: 1;
2457
+ };
2458
+ readonly layout: {
2459
+ readonly type: "string";
2460
+ readonly description: "Name of the layout you're using for this slide — must be an exact compound '<composition>--<system>' name from the LAYOUT CATALOG (e.g. 'cover-split-portrait--editorial-warm'). Rejected with a helpful error if unknown.";
2461
+ };
2462
+ readonly slideJsx: {
2463
+ readonly type: "string";
2464
+ readonly description: "JSX fragment whose root is <Anuma.Slide id=\"...\">…</Anuma.Slide>. Numeric attributes use JSX expressions ({10}), string attrs are quoted.";
2465
+ };
2466
+ };
2467
+ readonly required: readonly ["layout", "slideJsx"];
2468
+ };
2469
+ };
2470
+ /** Canonical storage path for the slide deck, relative to a conversation. */
2471
+ declare const SLIDES_FILE_PATH = "slides.jsx";
2472
+ interface CreateSlideToolsOptions {
2473
+ /** Returns the current conversation ID (may be null before first message). */
2474
+ getConversationId: () => string | null;
2475
+ /** Storage backend for slides.jsx — only getFile/putFile are required. */
2476
+ storage: Pick<AppFileStorage, "getFile" | "putFile">;
2477
+ /** Optional error logger. Defaults to a no-op. */
2478
+ logError?: (message: string, error?: Error) => void;
2479
+ /**
2480
+ * Set to true when the host has bound an image-generation tool (e.g.
2481
+ * AnumaMediaMCP-anuma_create_image) to the same loop as these slide
2482
+ * tools. plan_deck's recipe text adapts: when false (the default),
2483
+ * the recipe tells the model the only valid path is attached:N or
2484
+ * removing the element; when true, anuma_create_image is advertised as
2485
+ * an option. Avoids leaking a tool name the model can't actually call.
2486
+ */
2487
+ hasImageGenerator?: boolean;
2488
+ /**
2489
+ * Host-supplied hook that emits a deck-display UI interaction. Called
2490
+ * automatically from inside `plan_deck`, `add_slide`, and `patch_slides`;
2491
+ * its return value is merged into the tool result so the model (and the
2492
+ * chat UI) see the `interaction_id`. Receives `{ title, replaces_interaction_id }`.
2493
+ *
2494
+ * The SDK tracks the interaction_id internally per conversation so
2495
+ * per-slide appends update the same viewer — the model does not need
2496
+ * to thread the id through its tool arguments.
2497
+ *
2498
+ * Omit in headless/test environments that don't need a UI anchor — in
2499
+ * that case the tool results just omit `interaction_id`.
2500
+ */
2501
+ displaySlides?: (args: Record<string, unknown>) => Promise<unknown> | unknown;
2502
+ }
2503
+ /**
2504
+ * Create the slide deck tool suite: `plan_deck`, `add_slide`, `read_slides`,
2505
+ * and `patch_slides`.
2506
+ *
2507
+ * New decks flow: plan_deck (once) → add_slide (once per slide, in order).
2508
+ * Existing deck edits: read_slides → patch_slides.
2509
+ *
2510
+ * When `displaySlides` is provided, `plan_deck`, `add_slide`, and
2511
+ * `patch_slides` all emit the deck-display UI interaction as part of
2512
+ * their result. No standalone `display_slides` tool — the write and the
2513
+ * display anchor are a single step, and the SDK tracks the interaction
2514
+ * id per conversation so per-slide appends update the same viewer
2515
+ * in-place.
2516
+ *
2517
+ * Backed by the provided storage adapter. No dependency on the
2518
+ * app-generation `create_file` tool — `plan_deck` initializes slides.jsx
2519
+ * and `add_slide` appends to it directly.
2520
+ */
2521
+ /**
2522
+ * Return shape of {@link createSlideTools}: the tool array plus a
2523
+ * `clearConversation(id)` method to release per-conversation state. Call
2524
+ * the method when a conversation ends (session close, timeout, etc.) —
2525
+ * otherwise `deckStateByConv` and `writeLockByConv` grow unbounded over
2526
+ * the server process lifetime.
2527
+ *
2528
+ * The return is still array-iterable, so existing `tools: slideTools`
2529
+ * spreads keep working without destructuring.
2530
+ */
2531
+ type SlideToolSet = ToolConfig[] & {
2532
+ clearConversation: (conversationId: string) => void;
2533
+ };
2534
+ declare function createSlideTools({ getConversationId, storage, logError, displaySlides, hasImageGenerator, }: CreateSlideToolsOptions): SlideToolSet;
2535
+ /**
2536
+ * Build the slide mode system prompt — slim initialize + per-slide flow.
2537
+ *
2538
+ * Instructs the LLM to call plan_deck once (picks title + palette +
2539
+ * fontPreset, initializes an empty slides.jsx, returns the full layout
2540
+ * recipes + element-type schemas + palette hex values),
2541
+ * then call add_slide once per slide to append. Per-slide rounds keep
2542
+ * each tool-call stream small so slow / reasoning-heavy models can finish
2543
+ * within portal per-provider timeout ceilings.
2544
+ */
2545
+ interface BuildSlideSystemPromptOptions {
2546
+ /**
2547
+ * Set to true when the host has bound an image-generation tool (e.g.
2548
+ * AnumaMediaMCP-anuma_create_image) to the same loop. The IMAGES
2549
+ * section adapts: when false (the default) it tells the model the
2550
+ * only valid path is attached:N or removing the element; when true,
2551
+ * anuma_create_image is advertised as an option. Mirrors the same flag
2552
+ * on `CreateSlideToolsOptions` — host should pass the same value to both.
2553
+ */
2554
+ hasImageGenerator?: boolean;
2555
+ }
2556
+ declare function buildSlideSystemPrompt(options?: BuildSlideSystemPromptOptions): string;
2557
+
2558
+ /**
2559
+ * Document Builder persona prompt — canonical source for the SDK.
2560
+ *
2561
+ * Mirrors {@link APP_BUILDER_PROMPT}: the prompt lives in code, the SDK owns
2562
+ * it, and it is attached to the `documents` tool set's `systemPrompt` so it
2563
+ * rides in with semantic tool selection (and is otherwise absent, keeping
2564
+ * borderline turns unbiased).
2565
+ *
2566
+ * Lives in its own dependency-free module (no `@babel/parser` etc.) so the
2567
+ * lib/server layer can import the string without pulling in the document
2568
+ * runtime — see `BUILT_IN_TOOL_SETS` in `src/lib/tools/serverTools.ts`.
2569
+ *
2570
+ * Pair with the tools returned by {@link createDocumentTools} —
2571
+ * `create_document`, `read_document`, `patch_document`.
2572
+ *
2573
+ * @module tools/document/documentBuilderPrompt
2574
+ */
2575
+ declare const DOCUMENT_BUILDER_PROMPT = "Document tools (create_document, read_document, patch_document) are available this turn, but they are OPTIONAL \u2014 having them does not mean you should produce a document.\n\nUse them ONLY when the user explicitly asks you to draft, write, compose, or generate a document \u2014 a contract, agreement, NDA, letter, cover letter, memo, essay, report, brief, proposal, or resume \u2014 e.g. \"draft me a service agreement\", \"write a cover letter for this job\", \"make a one-page memo about X\". A bare topic, a question, a request for an explanation, or a request for a slide deck or an interactive app is NOT a document instruction: respond normally and do not call these tools. When it's unclear whether the user wants a finished document, ask instead.\n\nWhen you DO author a document, the document body goes entirely into the tool \u2014 NEVER write the document text as chat prose. The tool renders your source to a real, paginated PDF and attaches it to your reply automatically. AFTER the tool returns, briefly confirm in ONE short sentence what you made (e.g. \"Drafted a cover letter for the product manager role \u2014 see the attached PDF.\"). Do not restate the document's contents.\n\nAUTHORING \u2014 write the \"source\" as a single @react-pdf/renderer JSX expression (this is react-pdf, NOT HTML and NOT a slide deck):\n- Root is exactly one <Document> with at least one <Page>. Its only children are <Page> elements. Add more <Page> elements for longer documents \u2014 react-pdf also paginates overflow automatically.\n- Inside a <Page>: use <View> as flex containers and <Text> for ALL text. Raw text is only allowed inside <Text> (and <Link>); a <View> or <Page> may not contain bare text.\n- <Text> may nest <Text>, <Link>, or <Note> for inline styling, hyperlinks, and (rarely) a PDF comment annotation. A <Link> src must be an ordinary web link \u2014 http(s), mailto, tel, or a relative/anchor link; javascript:/data:/file: links are rejected. <Image> is self-closing and its src MUST be an inline \"data:\" URI \u2014 remote image URLs are rejected for privacy. Vector primitives (<Rect>/<Circle>/<Path>/etc.) must live inside an <Svg>.\n- Styling is react-pdf style objects only: style={{ marginTop: 12, fontSize: 11, fontFamily: \"Times-Roman\", flexDirection: \"column\" }}. Flexbox only \u2014 there is NO CSS grid, float, or box-shadow. Use camelCase react-pdf keys (margin/padding, flex*, width/height, color/backgroundColor, border*, text*, etc.).\n- fontFamily must be a built-in PDF font: Helvetica, Times-Roman, or Courier (with -Bold / -Oblique / -Italic / -BoldOblique variants), or Symbol / ZapfDingbats. Custom fonts are not supported.\n- LITERAL VALUES ONLY: no variables, no JS expressions, no .map(), no event handlers (onClick, \u2026), no spreads. Every attribute is a literal string, number, boolean, or a style object of literals.\n\nA minimal example:\n\n<Document>\n <Page size=\"A4\" style={{ paddingVertical: 56, paddingHorizontal: 64, fontFamily: \"Times-Roman\", fontSize: 11, lineHeight: 1.5 }}>\n <Text style={{ fontSize: 18, fontFamily: \"Times-Bold\", marginBottom: 16, textAlign: \"center\" }}>MUTUAL NON-DISCLOSURE AGREEMENT</Text>\n <Text style={{ marginBottom: 10 }}>This Mutual Non-Disclosure Agreement (the \"Agreement\") is entered into as of the date of last signature below.</Text>\n <View style={{ marginTop: 14 }}>\n <Text style={{ fontFamily: \"Times-Bold\", marginBottom: 4 }}>1. Confidential Information</Text>\n <Text>Each party may disclose certain confidential and proprietary information to the other party.</Text>\n </View>\n </Page>\n</Document>\n\nWORKFLOW:\n1. To create or fully restructure a document, call create_document with the complete source.\n2. To revise an existing document, call read_document first (its output is line-numbered \"42: <text>\" \u2014 the prefix is display-only, never copy it into a \"find\"), then patch_document with small find/replace edits. Each \"find\" must match exactly one location; add 2-3 lines of surrounding context if needed. Prefer patching over rewriting.\n3. To attach several separate documents in one reply, call create_document multiple times with distinct documentId slugs (e.g. \"nda\", \"cover-letter\"). Each renders to its own PDF.\n4. If a tool reports a DSL error (with line:col) or a render failure, fix the source and retry \u2014 do not give up or fall back to writing the document as chat text.";
2576
+ /**
2577
+ * The Document Builder system prompt. A function (not just the constant) to
2578
+ * match {@link buildAppSystemPrompt} and leave room for future
2579
+ * host-configurable variants.
2580
+ *
2581
+ * @category Document Tools
2582
+ */
2583
+ declare function buildDocumentSystemPrompt(): string;
2584
+
2585
+ /**
2586
+ * Document DSL — the validated, literal-only `@react-pdf/renderer` subset the
2587
+ * model authors when drafting a document artifact (contract, letter, memo,
2588
+ * essay, report, …).
2589
+ *
2590
+ * The model emits a single JSX expression rooted at `<Document>`, using the
2591
+ * react-pdf primitive vocabulary (`<Page>`, `<View>`, `<Text>`, `<Image>`, …)
2592
+ * plus inline `style={{}}` objects. We parse it into a tag-agnostic
2593
+ * {@link DocNode} tree with `@babel/parser` and enforce a strict allowlist:
2594
+ * only react-pdf render tags, only react-pdf style keys, only literal scalar
2595
+ * attribute values. No executable code ever runs — the web renderer maps the
2596
+ * validated tree onto real react-pdf components (see the web `render.tsx`),
2597
+ * which is what makes this safe on the main thread.
2598
+ *
2599
+ * This is a deliberate sibling of `tools/slides/jsx.ts`: same parse strategy
2600
+ * and literal-only attribute discipline, but the tag reader accepts the
2601
+ * react-pdf bare-capitalized vocabulary (`<Document>`, `<Page>`) instead of
2602
+ * the slides `<Anuma.*>` namespace, and the allowlists/structural rules are
2603
+ * document/react-pdf specific.
2604
+ *
2605
+ * @module tools/document/dsl
2606
+ */
2607
+ /** Scalar value an attribute can hold. */
2608
+ type AttrScalar = string | number | boolean;
2609
+ /** An object-valued attribute — used primarily for `style={{}}`. */
2610
+ type AttrObject = Record<string, AttrScalar>;
2611
+ /** @category Document DSL */
2612
+ type DocAttrValue = AttrScalar | AttrObject;
2613
+ /** @category Document DSL */
2614
+ type DocChild = DocNode | string;
2615
+ /**
2616
+ * A node in the document tree. `tag` is the react-pdf component name (e.g.
2617
+ * `"Document"`, `"Page"`, `"Text"`). Children are nested nodes for containers,
2618
+ * or strings (the displayed text) for text-bearing tags.
2619
+ *
2620
+ * @category Document DSL
2621
+ */
2622
+ interface DocNode {
2623
+ tag: string;
2624
+ attrs: Record<string, DocAttrValue>;
2625
+ children: DocChild[];
2626
+ }
2627
+ /**
2628
+ * Thrown when the document DSL violates a parse or structural rule. Carries
2629
+ * the source line/column when available so the model can self-correct.
2630
+ *
2631
+ * @category Document DSL
2632
+ */
2633
+ declare class DocDslError extends Error {
2634
+ readonly line?: number;
2635
+ readonly column?: number;
2636
+ constructor(message: string, loc?: {
2637
+ line: number;
2638
+ column: number;
2639
+ } | null);
2640
+ }
2641
+ /**
2642
+ * Every `@react-pdf/renderer` component the document DSL permits. Bare
2643
+ * capitalized identifiers (unlike slides, which namespaces under `Anuma.*`).
2644
+ *
2645
+ * Deliberately excluded: `Font` (imperative `Font.register` API, not a render
2646
+ * tag) and `Canvas` (requires a `paint` function prop, which the literal-only
2647
+ * DSL cannot express).
2648
+ */
2649
+ declare const PDF_TAGS: readonly ["Document", "Page", "View", "Text", "Link", "Note", "Image", "Svg", "G", "Line", "Rect", "Circle", "Ellipse", "Polygon", "Polyline", "Path", "Tspan", "Defs", "ClipPath", "Stop", "LinearGradient", "RadialGradient"];
2650
+ /** @category Document DSL */
2651
+ type PdfTag = (typeof PDF_TAGS)[number];
2652
+ /**
2653
+ * Parse and validate a document DSL source string into a {@link DocNode} tree.
2654
+ *
2655
+ * Throws {@link DocDslError} on any violation: invalid JSX, a non-`<Document>`
2656
+ * root, an unknown tag, a forbidden/dynamic attribute, an unknown style key,
2657
+ * or a structural rule break (e.g. `<Document>` containing a non-`<Page>`,
2658
+ * raw text outside `<Text>`, or a leaf tag with children).
2659
+ *
2660
+ * @category Document DSL
2661
+ */
2662
+ declare function parseDocumentDsl(source: string): DocNode;
2663
+ /** True iff `tag` is a permitted react-pdf document tag. */
2664
+ declare function isPdfTag(tag: string): boolean;
2665
+ /** The full list of permitted react-pdf document tags (for prompts/tests). */
2666
+ declare function pdfTags(): readonly string[];
2667
+ /** The full set of permitted react-pdf style keys (for prompts/tests). */
2668
+ declare function pdfStyleKeys(): readonly string[];
2669
+
2670
+ /**
2671
+ * Document generation tools — `create_document`, `read_document`,
2672
+ * `patch_document`.
2673
+ *
2674
+ * The model authors a document as a single `@react-pdf/renderer` DSL source
2675
+ * file (see {@link module:tools/document/dsl}). Each document is stored as one
2676
+ * file per conversation in the shared {@link AppFileStorage}; the host's
2677
+ * `displayDocument` callback renders it to a vector PDF and attaches it to the
2678
+ * assistant message (the web app does this on the main thread — see the web
2679
+ * `useDocumentPdfExport`). The callback is mandatory: the PDF is the tools'
2680
+ * entire user-visible output, so hosts that cannot render (e.g. React Native,
2681
+ * which has no `@react-pdf/renderer`) get no document tools at all rather
2682
+ * than a tool that silently claims success (#659). To revise, the model reads
2683
+ * the current source and applies find/replace patches.
2684
+ *
2685
+ * This is a single-file sibling of {@link createAppGenerationTools}: it reuses
2686
+ * that module's pure patch engine (`applyPatches`, `snippetForFailedPatch`,
2687
+ * `snippetAroundLine`) and the read-before-write contract, but validates
2688
+ * content with {@link parseDocumentDsl} (react-pdf
2689
+ * DSL) rather than the JS/JSX syntax checker, and has no multi-file batch.
2690
+ *
2691
+ * Multiple distinct documents per conversation are supported via an optional
2692
+ * `documentId` (default `"document"`), each backed by its own `<id>.jsx` file
2693
+ * and rendered to its own PDF chip — so a single response can attach one or
2694
+ * several documents.
2695
+ *
2696
+ * @module tools/document
2697
+ */
2698
+
2699
+ /** Default document identifier when the model omits `documentId`. */
2700
+ declare const DEFAULT_DOCUMENT_ID = "document";
2701
+ /** Tool names this module owns — for host filtering / set membership. */
2702
+ declare const DOCUMENT_TOOL_NAMES: ReadonlySet<string>;
2703
+ /**
2704
+ * Map a `documentId` to its storage path. The id is run through
2705
+ * {@link normalizeDocumentId}, which slug-validates it (throwing on anything
2706
+ * outside `[a-z0-9][a-z0-9-]{0,63}`), so the returned storage key stays
2707
+ * filesystem-safe and cannot traverse outside the conversation directory —
2708
+ * including when a host calls this public helper directly on untrusted input.
2709
+ */
2710
+ declare function documentPath(documentId: string): string;
2711
+ declare const CREATE_DOCUMENT_SCHEMA: {
2712
+ readonly name: "create_document";
2713
+ readonly description: "Author a formatted, paginated document the user can read and download as a PDF — a contract, agreement, letter, cover letter, memo, essay, report, brief, proposal, resume, or similar. Use this whenever the user asks to draft, write, compose, or generate a document. The document renders to a real PDF and attaches to your reply automatically — never write the document body as chat prose.\n\nThis is NOT for slide decks/presentations (use the deck tools) or interactive apps (use the app tools).\n\nWrite the \"source\" as a single @react-pdf/renderer JSX expression: a <Document> root containing one or more <Page> elements, with <View>/<Text> for layout and prose, using react-pdf style objects (flexbox only — no CSS grid/float). To revise a document later, prefer read_document + patch_document over rewriting the whole thing with create_document.";
2714
+ readonly arguments: {
2715
+ readonly type: "object";
2716
+ readonly properties: {
2717
+ readonly title: {
2718
+ readonly type: "string";
2719
+ readonly description: "Short human-readable document title (used for the PDF filename / attachment label).";
2720
+ };
2721
+ readonly source: {
2722
+ readonly type: "string";
2723
+ readonly description: "The complete @react-pdf/renderer DSL: one <Document> root with <Page> children. Literal values only — no variables, expressions, or event handlers.";
2724
+ };
2725
+ readonly documentId: {
2726
+ readonly type: "string";
2727
+ readonly description: "Optional slug identifying which document to operate on (e.g. \"nda\", \"offer-letter\"). Omit for the default document. Use distinct ids to attach several separate documents in one response.";
2728
+ };
2729
+ };
2730
+ readonly required: readonly ["source"];
2731
+ };
2732
+ };
2733
+ declare const READ_DOCUMENT_SCHEMA: {
2734
+ readonly name: "read_document";
2735
+ readonly description: "Read the current document source (the @react-pdf/renderer DSL) with \"<n>: \" line-number prefixes, to plan an edit. Required before patch_document. Do not copy the line-number prefixes into a patch \"find\" string.";
2736
+ readonly arguments: {
2737
+ readonly type: "object";
2738
+ readonly properties: {
2739
+ readonly documentId: {
2740
+ readonly type: "string";
2741
+ readonly description: "Optional slug identifying which document to operate on (e.g. \"nda\", \"offer-letter\"). Omit for the default document. Use distinct ids to attach several separate documents in one response.";
2742
+ };
2743
+ };
2744
+ readonly required: readonly [];
2745
+ };
2746
+ };
2747
+ declare const PATCH_DOCUMENT_SCHEMA: {
2748
+ readonly name: "patch_document";
2749
+ readonly description: "Edit, revise, amend, or reword an existing document via find-and-replace on its source, then re-render the PDF. Use this for incremental changes (\"make it more formal\", \"add a confidentiality clause\") instead of rewriting with create_document.\n\nREQUIRED: call read_document for this document earlier in the conversation first — the tool refuses otherwise.\n\nPass a \"patches\" array of {find, replace}. Each \"find\" must match the source exactly, character for character, at EXACTLY ONE location (add 2-3 lines of surrounding context if it would otherwise be ambiguous), and must NOT include the \"<n>: \" line-number prefix from read_document.";
2750
+ readonly arguments: {
2751
+ readonly type: "object";
2752
+ readonly properties: {
2753
+ readonly patches: {
2754
+ readonly type: "array";
2755
+ readonly description: "Array of find/replace operations applied atomically.";
2756
+ readonly items: {
2757
+ readonly type: "object";
2758
+ readonly properties: {
2759
+ readonly find: {
2760
+ readonly type: "string";
2761
+ readonly description: "Exact string to find in the document source";
2762
+ };
2763
+ readonly replace: {
2764
+ readonly type: "string";
2765
+ readonly description: "Replacement string";
2766
+ };
2767
+ };
2768
+ readonly required: readonly ["find", "replace"];
2769
+ };
2770
+ };
2771
+ readonly documentId: {
2772
+ readonly type: "string";
2773
+ readonly description: "Optional slug identifying which document to operate on (e.g. \"nda\", \"offer-letter\"). Omit for the default document. Use distinct ids to attach several separate documents in one response.";
2774
+ };
2775
+ };
2776
+ readonly required: readonly ["patches"];
2777
+ };
2778
+ };
2779
+ interface CreateDocumentToolsOptions {
2780
+ /** Returns the current conversation ID (may be null before the first message). */
2781
+ getConversationId: () => string | null;
2782
+ /** Storage backend for the document source file. */
2783
+ storage: Pick<AppFileStorage, "getFile" | "putFile">;
2784
+ /** Optional error logger. Falls back to console.error. */
2785
+ logError?: (message: string, error?: Error) => void;
2786
+ /**
2787
+ * Host callback that renders the just-written document to a PDF and
2788
+ * attaches it to the assistant message (web: render on the main thread via
2789
+ * `@react-pdf/renderer`, write to encrypted OPFS, append the media id to the
2790
+ * message's `fileIds`). Invoked automatically after every successful
2791
+ * `create_document` / `patch_document` — the model never calls a separate
2792
+ * display tool.
2793
+ *
2794
+ * REQUIRED: the rendered PDF is the tools' entire deliverable, so a host
2795
+ * that cannot render must not offer them. Without this callback the tools
2796
+ * used to persist the source and report `success: true` anyway — the model
2797
+ * told the user the PDF was attached, nothing rendered, no error surfaced,
2798
+ * and the turn was still billed (#659). If a plain-JS host omits it at
2799
+ * runtime, {@link createDocumentTools} returns no tools (and logs) so the
2800
+ * model honestly reports it cannot produce a PDF instead.
2801
+ *
2802
+ * Receives `{ documentId, path, title? }`. Unlike `displayApp`/`displaySlides`,
2803
+ * a THROW here is NOT swallowed: a render failure becomes a tool-error result
2804
+ * so the model can fix an unrenderable document and retry. Any object it
2805
+ * returns is spread into the tool result to ADD fields (e.g. a media id);
2806
+ * the tool's own status fields (`success`, `documentId`, `applied`) always
2807
+ * take precedence and cannot be overwritten by the callback.
2808
+ */
2809
+ displayDocument: (args: Record<string, unknown>) => Promise<Record<string, unknown> | void> | Record<string, unknown> | void;
2810
+ /** Cap on per-conversation in-memory state (read-tracking, patch tallies). */
2811
+ maxConversations?: number;
2812
+ }
2813
+ /** Default cap for {@link CreateDocumentToolsOptions.maxConversations}. */
2814
+ declare const DEFAULT_MAX_DOCUMENT_CONVERSATIONS = 1000;
2815
+ /**
2816
+ * Create the document tool set (`create_document`, `read_document`,
2817
+ * `patch_document`) backed by the provided storage adapter and host render
2818
+ * callback. The write tools do NOT skip continuation: their results are small
2819
+ * (`{ success, documentId }` — never the source), so feeding them back is cheap
2820
+ * and lets the model add a brief confirmation ("Drafted your cover letter — see
2821
+ * the attached PDF.") after the chip renders. Render/validation failures also
2822
+ * feed back as tool errors, so the model can recover.
2823
+ *
2824
+ * Returns an EMPTY array (and logs via `logError`) if `displayDocument` is
2825
+ * missing at runtime: with no renderer there is nothing a user can ever see
2826
+ * or download, so offering the tools would only let the model burn a billed
2827
+ * turn on a `create_document` call that reports `success: true` while
2828
+ * producing nothing (#659). With no tools offered, the model tells the user
2829
+ * it cannot produce a PDF on this platform — before any document turn is
2830
+ * billed.
2831
+ *
2832
+ * @category Document Tools
2833
+ */
2834
+ declare function createDocumentTools({ getConversationId, storage, logError, displayDocument, maxConversations, }: CreateDocumentToolsOptions): ToolConfig[];
2835
+
2836
+ export { ADD_SLIDE_SCHEMA, APP_BUILDER_PROMPT, APP_COMPLETE_CONNECT_ACK_TYPE, APP_COMPLETE_CONNECT_TYPE, APP_COMPLETE_DEFAULT_TIMEOUT_MS, APP_COMPLETE_IFRAME_SHIM_SCRIPT, APP_COMPLETE_REQUEST_TYPE, APP_COMPLETE_RESPONSE_TYPE, APP_COMPLETE_STUB_SCRIPT, APP_FILE_TOOL_NAMES, APP_PREVIEW_BASELINE_CSS, AUDIT_DESIGN_SCHEMA, type AnchorMatch, type AnumaChild, AnumaJsxError, type AnumaNode, type AppCompleteBridge, type AppCompleteBridgeOptions, type AppFileRecord, type AppFileStorage, type AttrValue, type AuditIssue, type AuditIssueType, type AuditResult, type AuditSeverity, type AuditTokens, type BuildSlideSystemPromptOptions, CONNECTOR_ERROR_MARKER, CREATE_DOCUMENT_SCHEMA, CREATE_FILE_SCHEMA, CRITIQUE_DESIGN_SCHEMA, type CalendarEvent, type ChartDataPoint, type ChoiceOption, type ConnectorErrorCode, type ConnectorMintError, type ConnectorMintResult, type ConnectorTokenGetterOpts, type ConnectorTokenSource, type CreateAppGenerationToolsOptions, type CreateDocumentToolsOptions, type CreateEventArgs, type CreateFileArgs, type CreateSlideToolsOptions, type CreateUIToolsOptions, type CreateWeatherToolOptions, DEFAULT_DESIGN_CRITIQUE_RUBRIC, DEFAULT_DOCUMENT_ID, DEFAULT_MAX_CONVERSATIONS, DEFAULT_MAX_DOCUMENT_CONVERSATIONS, DELETE_FILE_SCHEMA, DOCUMENT_BUILDER_PROMPT, DOCUMENT_TOOL_NAMES, type DisplayChartResult, type DisplayConnectorResult, type DisplayPhoneCallOfferResult, type DisplayToolConfig, type DisplayToolMigrations, type DisplayWeatherResult, type DocAttrValue, type DocChild, DocDslError, type DocNode, type DriveFile, type DropboxRequestAccess, type DropboxTokenGetter, type ExportAppOptions, FONT_LIBRARY, FONT_PRESETS, type FileChangeEvent, type FileSnippet, type FontCategory, type FontPreset, type FontSpec, type ForecastDay, type FormField, type FormFieldOption, type FormFieldType, type GetFileContentArgs, type GmailGetMessageArgs, type GmailMessageDetail, type GmailMessageSummary, type GmailRequestAccess, type GmailSearchArgs, type GmailSendMessageArgs, type GmailTokenGetter, type InteractiveToolConfig, type KnownTag, LAYOUT_MODES, LIST_FILES_SCHEMA, type LayoutMode, type LegacyDeckJson, type ListEventsArgs, type ListRecentFilesArgs, MapFileStorage, type NotionCreatePagesArgs, type NotionFetchArgs, type NotionMovePagesArgs, type NotionSearchArgs, type NotionUpdatePageArgs, PATCH_DOCUMENT_SCHEMA, PATCH_FILE_SCHEMA, PATCH_SLIDES_SCHEMA, PLAN_DECK_SCHEMA, type PatchFailure, type PatchFailureReason, type PdfTag, READ_DOCUMENT_SCHEMA, READ_FILE_SCHEMA, READ_SLIDES_SCHEMA, RUNTIME_ERROR_OVERLAY_SCRIPT, SLIDES_FILE_PATH, SLIDE_CANVAS_HEIGHT, SLIDE_CANVAS_WIDTH, type SearchFilesArgs, type SlackGetChannelHistoryArgs, type SlackGetThreadRepliesArgs, type SlackListChannelsArgs, type SlackListUsersArgs, type SlackPostMessageArgs, type SlackProxyCaller, type SlackSearchMessagesArgs, type SlideToolSet, THEME_ATTRS, type ThemeAttr, type ToolConfig, type ToolExecutionErrorType, type ToolExecutionResult, type UIInteractionContext, type UpdateEventArgs, type UpdateFileArgs, VERIFY_APP_SCHEMA, type VerifyAppResult, type XGetMeArgs, type XGetMyPostsArgs, type XProxyCaller, applyPatches, auditDesign, buildAppFileManifest, buildAppSystemPrompt, buildConnectorErrorResult, buildDocumentSystemPrompt, buildFontsUrl, buildSlideSystemPrompt, callNotionMCPTool, connectorMintErrorToToolResult, convertLegacyDeckJson, createAppCompleteBridge, createAppGenerationTools, createChartTool, createChatTools, createChoiceTool, createConnectorOfferTool, createConnectorTokenGetter, createDisplayTool, createDocumentTools, createDriveTools, createDropboxTools, createFormTool, createGitHubTools, createGmailTools, createGoogleCalendarCreateEventTool, createGoogleCalendarTool, createGoogleCalendarUpdateEventTool, createGoogleDriveCreateFileTool, createGoogleDriveGetContentTool, createGoogleDriveListRecentTool, createGoogleDriveSearchTool, createGoogleDriveUpdateFileTool, createInteractiveTool, createNotionCreateCommentTool, createNotionCreateDatabaseTool, createNotionCreatePagesTool, createNotionDuplicatePageTool, createNotionFetchTool, createNotionGetCommentsTool, createNotionGetTeamsTool, createNotionGetUsersTool, createNotionMovePagesTool, createNotionSearchTool, createNotionTools, createNotionUpdateDataSourceTool, createNotionUpdatePageTool, createPhoneCallOfferTool, createSlackTools, createSlideTools, createWeatherTool, createXTools, documentPath, exportAppToHtml, findById, findParentOfId, getFontByName, getId, getMCPEndpoints, getNumberAttr, getStringAttr, insertAfterId, insertChild, installAppCompleteIframeShim, isAnumaTag, isHtmlTag, isKnownFont, isLegacyDeckJson, isPdfTag, migrateDisplayResult, normalizePath, parseDocumentDsl, parseJsx, pdfStyleKeys, pdfTags, removeById, replaceById, serializeJsx, truncateContent, updateAttrs, walk };