sunpeak 0.7.11 → 0.8.4

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 (76) hide show
  1. package/README.md +2 -1
  2. package/bin/commands/deploy.mjs +18 -8
  3. package/bin/commands/dev.mjs +60 -4
  4. package/bin/commands/login.mjs +73 -55
  5. package/bin/commands/logout.mjs +26 -12
  6. package/bin/commands/mcp.mjs +1 -1
  7. package/bin/commands/pull.mjs +60 -39
  8. package/bin/commands/push.mjs +73 -49
  9. package/bin/commands/upgrade.mjs +203 -0
  10. package/bin/sunpeak.js +68 -35
  11. package/dist/chatgpt/chatgpt-simulator.d.ts +2 -1
  12. package/dist/index.cjs +13 -14
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.js +13 -14
  15. package/dist/index.js.map +1 -1
  16. package/dist/mcp/entry.cjs +41 -9
  17. package/dist/mcp/entry.cjs.map +1 -1
  18. package/dist/mcp/entry.js +42 -10
  19. package/dist/mcp/entry.js.map +1 -1
  20. package/dist/mcp/index.cjs +1 -1
  21. package/dist/mcp/index.js +1 -1
  22. package/dist/{server-CziiHU7V.cjs → server-B9YgCQdS.cjs} +3 -2
  23. package/dist/{server-CziiHU7V.cjs.map → server-B9YgCQdS.cjs.map} +1 -1
  24. package/dist/{server-D8kyzuiq.js → server-DVmTC-SF.js} +3 -2
  25. package/dist/{server-D8kyzuiq.js.map → server-DVmTC-SF.js.map} +1 -1
  26. package/dist/style.css +62 -0
  27. package/dist/types/simulation.d.ts +1 -1
  28. package/package.json +1 -1
  29. package/template/.sunpeak/dev.tsx +78 -15
  30. package/template/.sunpeak/vite-env.d.ts +1 -0
  31. package/template/README.md +35 -20
  32. package/template/dist/albums.js +1 -1
  33. package/template/dist/albums.json +3 -2
  34. package/template/dist/carousel.js +1 -1
  35. package/template/dist/carousel.json +3 -2
  36. package/template/dist/confirmation.js +49 -0
  37. package/template/dist/confirmation.json +16 -0
  38. package/template/dist/counter.js +1 -1
  39. package/template/dist/counter.json +7 -2
  40. package/template/dist/map.js +1 -1
  41. package/template/dist/map.json +6 -3
  42. package/template/node_modules/.vite/deps/_metadata.json +19 -19
  43. package/template/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  44. package/template/src/components/map/map-view.test.tsx +1 -1
  45. package/template/src/components/map/map-view.tsx +1 -1
  46. package/template/src/components/map/map.tsx +1 -1
  47. package/template/src/components/map/place-card.test.tsx +1 -1
  48. package/template/src/components/map/place-card.tsx +1 -1
  49. package/template/src/components/map/place-carousel.test.tsx +1 -1
  50. package/template/src/components/map/place-carousel.tsx +1 -1
  51. package/template/src/components/map/place-inspector.test.tsx +1 -1
  52. package/template/src/components/map/place-inspector.tsx +1 -1
  53. package/template/src/components/map/place-list.test.tsx +1 -1
  54. package/template/src/components/map/place-list.tsx +1 -1
  55. package/template/src/components/map/types.ts +18 -0
  56. package/template/src/resources/albums-resource.json +1 -1
  57. package/template/src/resources/carousel-resource.json +1 -1
  58. package/template/src/resources/confirmation-resource.json +12 -0
  59. package/template/src/resources/confirmation-resource.tsx +479 -0
  60. package/template/src/resources/counter-resource.json +4 -1
  61. package/template/src/resources/index.ts +39 -4
  62. package/template/src/resources/map-resource.json +7 -2
  63. package/template/src/simulations/albums-show-simulation.json +131 -0
  64. package/template/src/simulations/carousel-show-simulation.json +68 -0
  65. package/template/src/simulations/confirmation-diff-simulation.json +80 -0
  66. package/template/src/simulations/confirmation-post-simulation.json +56 -0
  67. package/template/src/simulations/confirmation-purchase-simulation.json +88 -0
  68. package/template/src/simulations/counter-show-simulation.json +20 -0
  69. package/template/src/simulations/index.ts +17 -12
  70. package/template/src/simulations/map-show-simulation.json +123 -0
  71. package/template/src/vite-env.d.ts +1 -0
  72. package/template/tsconfig.json +1 -1
  73. package/template/src/simulations/albums-simulation.ts +0 -147
  74. package/template/src/simulations/carousel-simulation.ts +0 -84
  75. package/template/src/simulations/counter-simulation.ts +0 -34
  76. package/template/src/simulations/map-simulation.ts +0 -154
@@ -0,0 +1,479 @@
1
+ import * as React from 'react';
2
+ import {
3
+ useWidgetProps,
4
+ useWidgetState,
5
+ useSafeArea,
6
+ useMaxHeight,
7
+ useUserAgent,
8
+ useDisplayMode,
9
+ useWidgetAPI,
10
+ } from 'sunpeak';
11
+ import { Button } from '@openai/apps-sdk-ui/components/Button';
12
+ import { ExpandLg } from '@openai/apps-sdk-ui/components/Icon';
13
+
14
+ /**
15
+ * Production-ready Confirmation Resource
16
+ *
17
+ * A flexible confirmation dialog that adapts to various use cases:
18
+ * - Purchase confirmations (items, totals, payment)
19
+ * - Code change confirmations (file changes with diffs)
20
+ * - Social media post confirmations (content preview)
21
+ * - Booking confirmations (details, dates, prices)
22
+ * - Generic action confirmations (simple approve/reject)
23
+ */
24
+
25
+ // ============================================================================
26
+ // Type Definitions
27
+ // ============================================================================
28
+
29
+ /** A key-value detail row */
30
+ interface Detail {
31
+ label: string;
32
+ value: string;
33
+ /** Optional sublabel/description */
34
+ sublabel?: string;
35
+ /** Highlight this row (e.g., for totals) */
36
+ emphasis?: boolean;
37
+ }
38
+
39
+ /** An item with optional image and metadata (for purchases, lists) */
40
+ interface Item {
41
+ id: string;
42
+ title: string;
43
+ subtitle?: string;
44
+ /** Image URL */
45
+ image?: string;
46
+ /** Right-aligned value (e.g., price, quantity) */
47
+ value?: string;
48
+ /** Small badge text (e.g., "New", "Sale") */
49
+ badge?: string;
50
+ }
51
+
52
+ /** A code/file change entry */
53
+ interface Change {
54
+ id: string;
55
+ type: 'create' | 'modify' | 'delete' | 'action';
56
+ /** File path or identifier */
57
+ path?: string;
58
+ description: string;
59
+ details?: string;
60
+ }
61
+
62
+ /** Alert/warning message */
63
+ interface Alert {
64
+ type: 'info' | 'warning' | 'error' | 'success';
65
+ message: string;
66
+ }
67
+
68
+ /** Content section - supports multiple display types */
69
+ interface Section {
70
+ /** Optional section title */
71
+ title?: string;
72
+ /** Section content type */
73
+ type: 'details' | 'items' | 'changes' | 'preview' | 'summary';
74
+ /** Content data (type depends on section type) */
75
+ content: Detail[] | Item[] | Change[] | string;
76
+ }
77
+
78
+ /** Tool call configuration for domain-specific confirmation actions */
79
+ interface ConfirmationTool {
80
+ /** Tool name to call (e.g., "complete_purchase", "publish_post") */
81
+ name: string;
82
+ /** Additional arguments to pass to the tool */
83
+ arguments?: Record<string, unknown>;
84
+ }
85
+
86
+ interface ConfirmationData extends Record<string, unknown> {
87
+ /** Main title */
88
+ title: string;
89
+ /** Optional description below title */
90
+ description?: string;
91
+ /** Content sections */
92
+ sections?: Section[];
93
+ /** Alert messages to display */
94
+ alerts?: Alert[];
95
+ /** Accept button label */
96
+ acceptLabel?: string;
97
+ /** Reject button label */
98
+ rejectLabel?: string;
99
+ /** Use danger styling for accept button (for destructive actions) */
100
+ acceptDanger?: boolean;
101
+ /** Message shown after accepting */
102
+ acceptedMessage?: string;
103
+ /** Message shown after rejecting */
104
+ rejectedMessage?: string;
105
+ /** Domain-specific tool to call on confirmation */
106
+ confirmationTool?: ConfirmationTool;
107
+ }
108
+
109
+ interface ConfirmationState extends Record<string, unknown> {
110
+ decision?: 'accepted' | 'rejected' | null;
111
+ decidedAt?: string | null;
112
+ }
113
+
114
+ // ============================================================================
115
+ // Section Renderers
116
+ // ============================================================================
117
+
118
+ const changeTypeConfig = {
119
+ create: { icon: '+', color: '#16a34a', bg: '#f0fdf4' },
120
+ modify: { icon: '~', color: '#ca8a04', bg: '#fefce8' },
121
+ delete: { icon: '−', color: '#dc2626', bg: '#fef2f2' },
122
+ action: { icon: '→', color: '#2563eb', bg: '#eff6ff' },
123
+ };
124
+
125
+ const alertTypeConfig = {
126
+ info: { icon: 'ℹ', bg: '#eff6ff', border: '#bfdbfe', text: '#1e40af' },
127
+ warning: { icon: '⚠', bg: '#fefce8', border: '#fde047', text: '#a16207' },
128
+ error: { icon: '✕', bg: '#fef2f2', border: '#fecaca', text: '#b91c1c' },
129
+ success: { icon: '✓', bg: '#f0fdf4', border: '#bbf7d0', text: '#15803d' },
130
+ };
131
+
132
+ function DetailsSection({ content }: { content: Detail[] }) {
133
+ return (
134
+ <div className="space-y-2">
135
+ {content.map((detail, i) => (
136
+ <div
137
+ key={i}
138
+ className={`flex justify-between items-start gap-4 ${
139
+ detail.emphasis ? 'font-semibold pt-2 border-t border-subtle' : ''
140
+ }`}
141
+ >
142
+ <div className="flex-1 min-w-0">
143
+ <span className={detail.emphasis ? 'text-primary' : 'text-secondary'}>
144
+ {detail.label}
145
+ </span>
146
+ {detail.sublabel && <p className="text-xs text-secondary mt-0.5">{detail.sublabel}</p>}
147
+ </div>
148
+ <span className="text-primary flex-shrink-0">{detail.value}</span>
149
+ </div>
150
+ ))}
151
+ </div>
152
+ );
153
+ }
154
+
155
+ function ItemsSection({ content }: { content: Item[] }) {
156
+ return (
157
+ <div className="space-y-3">
158
+ {content.map((item) => (
159
+ <div key={item.id} className="flex items-center gap-3 p-2 rounded-lg bg-surface-secondary">
160
+ {item.image && (
161
+ <img
162
+ src={item.image}
163
+ alt={item.title}
164
+ className="w-12 h-12 rounded-lg object-cover flex-shrink-0"
165
+ />
166
+ )}
167
+ <div className="flex-1 min-w-0">
168
+ <div className="flex items-center gap-2">
169
+ <span className="text-sm font-medium text-primary truncate">{item.title}</span>
170
+ {item.badge && (
171
+ <span className="px-1.5 py-0.5 text-xs rounded bg-primary text-on-primary">
172
+ {item.badge}
173
+ </span>
174
+ )}
175
+ </div>
176
+ {item.subtitle && <p className="text-xs text-secondary truncate">{item.subtitle}</p>}
177
+ </div>
178
+ {item.value && (
179
+ <span className="text-sm font-medium text-primary flex-shrink-0">{item.value}</span>
180
+ )}
181
+ </div>
182
+ ))}
183
+ </div>
184
+ );
185
+ }
186
+
187
+ function ChangesSection({ content }: { content: Change[] }) {
188
+ return (
189
+ <ul className="space-y-2">
190
+ {content.map((change) => {
191
+ const config = changeTypeConfig[change.type];
192
+ return (
193
+ <li
194
+ key={change.id}
195
+ className="rounded-lg border border-subtle p-3"
196
+ style={{ backgroundColor: config.bg }}
197
+ >
198
+ <div className="flex items-start gap-3">
199
+ <span
200
+ className="flex-shrink-0 w-6 h-6 flex items-center justify-center rounded font-mono font-bold bg-white"
201
+ style={{
202
+ color: config.color,
203
+ borderWidth: 1,
204
+ borderStyle: 'solid',
205
+ borderColor: config.color,
206
+ }}
207
+ >
208
+ {config.icon}
209
+ </span>
210
+ <div className="flex-1 min-w-0">
211
+ {change.path && (
212
+ <code className="block text-xs text-secondary font-mono truncate mb-1">
213
+ {change.path}
214
+ </code>
215
+ )}
216
+ <p className="text-sm text-[#000000]">{change.description}</p>
217
+ {change.details && <p className="mt-1 text-xs text-secondary">{change.details}</p>}
218
+ </div>
219
+ </div>
220
+ </li>
221
+ );
222
+ })}
223
+ </ul>
224
+ );
225
+ }
226
+
227
+ function PreviewSection({ content }: { content: string }) {
228
+ return (
229
+ <div className="p-4 rounded-lg bg-surface-secondary border border-subtle">
230
+ <p className="text-sm text-primary whitespace-pre-wrap">{content}</p>
231
+ </div>
232
+ );
233
+ }
234
+
235
+ function SummarySection({ content }: { content: Detail[] }) {
236
+ return (
237
+ <div className="p-3 rounded-lg bg-surface-secondary space-y-1">
238
+ {content.map((item, i) => (
239
+ <div
240
+ key={i}
241
+ className={`flex justify-between items-center ${
242
+ item.emphasis ? 'font-semibold text-lg pt-2 border-t border-subtle mt-2' : 'text-sm'
243
+ }`}
244
+ >
245
+ <span className={item.emphasis ? 'text-primary' : 'text-secondary'}>{item.label}</span>
246
+ <span className="text-primary">{item.value}</span>
247
+ </div>
248
+ ))}
249
+ </div>
250
+ );
251
+ }
252
+
253
+ function Section({ section }: { section: Section }) {
254
+ const renderContent = () => {
255
+ switch (section.type) {
256
+ case 'details':
257
+ return <DetailsSection content={section.content as Detail[]} />;
258
+ case 'items':
259
+ return <ItemsSection content={section.content as Item[]} />;
260
+ case 'changes':
261
+ return <ChangesSection content={section.content as Change[]} />;
262
+ case 'preview':
263
+ return <PreviewSection content={section.content as string} />;
264
+ case 'summary':
265
+ return <SummarySection content={section.content as Detail[]} />;
266
+ default:
267
+ return null;
268
+ }
269
+ };
270
+
271
+ return (
272
+ <div className="space-y-2">
273
+ {section.title && (
274
+ <h2 className="text-sm font-medium text-secondary uppercase tracking-wide">
275
+ {section.title}
276
+ </h2>
277
+ )}
278
+ {renderContent()}
279
+ </div>
280
+ );
281
+ }
282
+
283
+ function AlertBanner({ alert }: { alert: Alert }) {
284
+ const config = alertTypeConfig[alert.type];
285
+ return (
286
+ <div
287
+ className="flex items-start gap-2 p-3 rounded-lg"
288
+ style={{
289
+ backgroundColor: config.bg,
290
+ borderWidth: 1,
291
+ borderStyle: 'solid',
292
+ borderColor: config.border,
293
+ }}
294
+ >
295
+ <span className="flex-shrink-0" style={{ color: config.text }}>
296
+ {config.icon}
297
+ </span>
298
+ <p className="text-sm" style={{ color: config.text }}>
299
+ {alert.message}
300
+ </p>
301
+ </div>
302
+ );
303
+ }
304
+
305
+ // ============================================================================
306
+ // Main Component
307
+ // ============================================================================
308
+
309
+ export const ConfirmationResource = React.forwardRef<HTMLDivElement>((_props, ref) => {
310
+ const data = useWidgetProps<ConfirmationData>(() => ({
311
+ title: 'Confirm',
312
+ sections: [],
313
+ }));
314
+
315
+ const [widgetState, setWidgetState] = useWidgetState<ConfirmationState>(() => ({
316
+ decision: null,
317
+ decidedAt: null,
318
+ }));
319
+
320
+ const safeArea = useSafeArea();
321
+ const maxHeight = useMaxHeight();
322
+ const userAgent = useUserAgent();
323
+ const displayMode = useDisplayMode();
324
+ const api = useWidgetAPI();
325
+
326
+ const hasTouch = userAgent?.capabilities.touch ?? false;
327
+ const decision = widgetState?.decision ?? null;
328
+ const isFullscreen = displayMode === 'fullscreen';
329
+
330
+ const handleRequestFullscreen = () => {
331
+ api?.requestDisplayMode?.({ mode: 'fullscreen' });
332
+ };
333
+
334
+ const handleAccept = () => {
335
+ const decidedAt = new Date().toISOString();
336
+ setWidgetState({
337
+ decision: 'accepted',
338
+ decidedAt,
339
+ });
340
+
341
+ const tool = data.confirmationTool;
342
+ if (tool) {
343
+ console.log('callTool', {
344
+ name: tool.name,
345
+ arguments: {
346
+ ...tool.arguments,
347
+ confirmed: true,
348
+ decidedAt,
349
+ },
350
+ });
351
+ }
352
+ };
353
+
354
+ const handleReject = () => {
355
+ const decidedAt = new Date().toISOString();
356
+ setWidgetState({
357
+ decision: 'rejected',
358
+ decidedAt,
359
+ });
360
+
361
+ const tool = data.confirmationTool;
362
+ if (tool) {
363
+ console.log('callTool', {
364
+ name: tool.name,
365
+ arguments: {
366
+ ...tool.arguments,
367
+ confirmed: false,
368
+ decidedAt,
369
+ },
370
+ });
371
+ }
372
+ };
373
+
374
+ const acceptLabel = data.acceptLabel ?? 'Confirm';
375
+ const rejectLabel = data.rejectLabel ?? 'Cancel';
376
+ const acceptedMessage = data.acceptedMessage ?? 'Confirmed';
377
+ const rejectedMessage = data.rejectedMessage ?? 'Cancelled';
378
+ const sections = data.sections ?? [];
379
+ const alerts = data.alerts ?? [];
380
+
381
+ return (
382
+ <div
383
+ ref={ref}
384
+ className="flex flex-col"
385
+ style={{
386
+ paddingTop: `${safeArea?.insets.top ?? 0}px`,
387
+ paddingBottom: `${safeArea?.insets.bottom ?? 0}px`,
388
+ paddingLeft: `${safeArea?.insets.left ?? 0}px`,
389
+ paddingRight: `${safeArea?.insets.right ?? 0}px`,
390
+ maxHeight: maxHeight ?? undefined,
391
+ }}
392
+ >
393
+ {/* Header */}
394
+ <div className="px-4 pt-4 pb-3 border-b border-subtle">
395
+ <div className="flex items-start justify-between gap-2">
396
+ <div className="flex-1 min-w-0">
397
+ <h1 className="text-xl font-semibold text-primary">{data.title}</h1>
398
+ {data.description && <p className="mt-1 text-sm text-secondary">{data.description}</p>}
399
+ </div>
400
+ {!isFullscreen && (
401
+ <Button
402
+ variant="ghost"
403
+ color="secondary"
404
+ size="sm"
405
+ onClick={handleRequestFullscreen}
406
+ aria-label="Enter fullscreen"
407
+ className="flex-shrink-0"
408
+ >
409
+ <ExpandLg className="h-4 w-4" aria-hidden="true" />
410
+ </Button>
411
+ )}
412
+ </div>
413
+ </div>
414
+
415
+ {/* Content */}
416
+ <div className="flex-1 overflow-y-auto px-4 py-3 space-y-4">
417
+ {/* Alerts */}
418
+ {alerts.length > 0 && (
419
+ <div className="space-y-2">
420
+ {alerts.map((alert, i) => (
421
+ <AlertBanner key={i} alert={alert} />
422
+ ))}
423
+ </div>
424
+ )}
425
+
426
+ {/* Sections */}
427
+ {sections.length === 0 ? (
428
+ <p className="text-secondary text-center py-8">Nothing to confirm</p>
429
+ ) : (
430
+ sections.map((section, i) => <Section key={i} section={section} />)
431
+ )}
432
+ </div>
433
+
434
+ {/* Footer with Actions */}
435
+ <div className="px-4 py-3 border-t border-subtle bg-surface">
436
+ {decision === null ? (
437
+ <div className="flex gap-3">
438
+ <Button
439
+ variant="outline"
440
+ color="secondary"
441
+ onClick={handleReject}
442
+ size={hasTouch ? 'lg' : 'md'}
443
+ className="flex-1"
444
+ >
445
+ {rejectLabel}
446
+ </Button>
447
+ <Button
448
+ variant="solid"
449
+ color={data.acceptDanger ? 'danger' : 'primary'}
450
+ onClick={handleAccept}
451
+ size={hasTouch ? 'lg' : 'md'}
452
+ className="flex-1"
453
+ >
454
+ {acceptLabel}
455
+ </Button>
456
+ </div>
457
+ ) : (
458
+ <div className="flex flex-col items-center gap-1">
459
+ <div
460
+ className="flex items-center justify-center gap-2"
461
+ style={{ color: decision === 'accepted' ? '#16a34a' : '#dc2626' }}
462
+ >
463
+ <span className="text-lg">{decision === 'accepted' ? '✓' : '✗'}</span>
464
+ <span className="font-medium">
465
+ {decision === 'accepted' ? acceptedMessage : rejectedMessage}
466
+ </span>
467
+ </div>
468
+ {widgetState?.decidedAt && (
469
+ <span className="text-xs text-secondary">
470
+ {new Date(widgetState.decidedAt).toLocaleString()}
471
+ </span>
472
+ )}
473
+ </div>
474
+ )}
475
+ </div>
476
+ </div>
477
+ );
478
+ });
479
+ ConfirmationResource.displayName = 'ConfirmationResource';
@@ -4,6 +4,9 @@
4
4
  "description": "Show a simple counter tool widget",
5
5
  "mimeType": "text/html+skybridge",
6
6
  "_meta": {
7
- "openai/widgetDomain": "https://sunpeak.ai"
7
+ "openai/widgetDomain": "https://sunpeak.ai",
8
+ "openai/widgetCSP": {
9
+ "resource_domains": ["https://cdn.openai.com"]
10
+ }
8
11
  }
9
12
  }
@@ -1,4 +1,39 @@
1
- export { CounterResource } from './counter-resource';
2
- export { AlbumsResource } from './albums-resource';
3
- export { CarouselResource } from './carousel-resource';
4
- export { MapResource } from './map-resource';
1
+ /**
2
+ * Auto-discovers and re-exports all resource components.
3
+ *
4
+ * Discovers all *-resource.tsx files and exports their component
5
+ * with a PascalCase name (e.g., counter-resource.tsx -> CounterResource).
6
+ *
7
+ * Supports both export styles:
8
+ * - Default export: export default MyComponent
9
+ * - Named export: export const CounterResource = ...
10
+ */
11
+
12
+ // Auto-discover all resource component files
13
+ const resourceModules = import.meta.glob('./*-resource.tsx', { eager: true });
14
+
15
+ // Build exports object from discovered files
16
+ const resources: Record<string, React.ComponentType> = {};
17
+
18
+ for (const [path, module] of Object.entries(resourceModules)) {
19
+ // Extract key from path: './counter-resource.tsx' -> 'counter'
20
+ const match = path.match(/\.\/(.+)-resource\.tsx$/);
21
+ const key = match?.[1];
22
+ if (!key) continue;
23
+
24
+ // Convert to PascalCase and append 'Resource': 'counter' -> 'CounterResource'
25
+ const pascalKey = key.charAt(0).toUpperCase() + key.slice(1);
26
+ const exportName = `${pascalKey}Resource`;
27
+
28
+ const mod = module as Record<string, unknown>;
29
+
30
+ // Try default export first, then named export matching the expected name
31
+ const component = mod.default ?? mod[exportName];
32
+
33
+ // Accept functions (regular components) or objects (forwardRef/memo components)
34
+ if (component && (typeof component === 'function' || typeof component === 'object')) {
35
+ resources[exportName] = component as React.ComponentType;
36
+ }
37
+ }
38
+
39
+ export default resources;
@@ -6,8 +6,13 @@
6
6
  "_meta": {
7
7
  "openai/widgetDomain": "https://sunpeak.ai",
8
8
  "openai/widgetCSP": {
9
- "connect_domains": ["https://api.mapbox.com"],
10
- "resource_domains": ["https://*.oaistatic.com", "https://api.mapbox.com"]
9
+ "connect_domains": ["https://api.mapbox.com", "https://events.mapbox.com"],
10
+ "resource_domains": [
11
+ "https://*.oaistatic.com",
12
+ "https://cdn.openai.com",
13
+ "https://api.mapbox.com",
14
+ "https://events.mapbox.com"
15
+ ]
11
16
  }
12
17
  }
13
18
  }
@@ -0,0 +1,131 @@
1
+ {
2
+ "userMessage": "Pizza time",
3
+ "tool": {
4
+ "name": "show-albums",
5
+ "description": "Show photo albums",
6
+ "inputSchema": { "type": "object", "properties": {}, "additionalProperties": false },
7
+ "title": "Show Albums",
8
+ "annotations": { "readOnlyHint": true },
9
+ "_meta": {
10
+ "openai/toolInvocation/invoking": "Loading albums",
11
+ "openai/toolInvocation/invoked": "Album loaded",
12
+ "openai/widgetAccessible": true,
13
+ "openai/resultCanProduceWidget": true
14
+ }
15
+ },
16
+ "toolCall": {
17
+ "structuredContent": {
18
+ "albums": [
19
+ {
20
+ "id": "summer-escape",
21
+ "title": "Summer Slice",
22
+ "cover": "https://persistent.oaistatic.com/pizzaz/pizzaz-1.png",
23
+ "photos": [
24
+ {
25
+ "id": "s1",
26
+ "title": "Waves",
27
+ "url": "https://persistent.oaistatic.com/pizzaz/pizzaz-2.png"
28
+ },
29
+ {
30
+ "id": "s2",
31
+ "title": "Palm trees",
32
+ "url": "https://persistent.oaistatic.com/pizzaz/pizzaz-3.png"
33
+ },
34
+ {
35
+ "id": "s3",
36
+ "title": "Sunset",
37
+ "url": "https://persistent.oaistatic.com/pizzaz/pizzaz-6.png"
38
+ }
39
+ ]
40
+ },
41
+ {
42
+ "id": "city-lights",
43
+ "title": "Pepperoni Nights",
44
+ "cover": "https://persistent.oaistatic.com/pizzaz/pizzaz-4.png",
45
+ "photos": [
46
+ {
47
+ "id": "c1",
48
+ "title": "Downtown",
49
+ "url": "https://persistent.oaistatic.com/pizzaz/pizzaz-5.png"
50
+ },
51
+ {
52
+ "id": "c2",
53
+ "title": "Neon",
54
+ "url": "https://persistent.oaistatic.com/pizzaz/pizzaz-1.png"
55
+ },
56
+ {
57
+ "id": "c3",
58
+ "title": "Streets",
59
+ "url": "https://persistent.oaistatic.com/pizzaz/pizzaz-2.png"
60
+ }
61
+ ]
62
+ },
63
+ {
64
+ "id": "into-the-woods",
65
+ "title": "Truffle Forest",
66
+ "cover": "https://persistent.oaistatic.com/pizzaz/pizzaz-3.png",
67
+ "photos": [
68
+ {
69
+ "id": "n1",
70
+ "title": "Forest path",
71
+ "url": "https://persistent.oaistatic.com/pizzaz/pizzaz-6.png"
72
+ },
73
+ {
74
+ "id": "n2",
75
+ "title": "Misty",
76
+ "url": "https://persistent.oaistatic.com/pizzaz/pizzaz-4.png"
77
+ },
78
+ {
79
+ "id": "n3",
80
+ "title": "Waterfall",
81
+ "url": "https://persistent.oaistatic.com/pizzaz/pizzaz-5.png"
82
+ }
83
+ ]
84
+ },
85
+ {
86
+ "id": "pizza-tour",
87
+ "title": "Pizza tour",
88
+ "cover": "https://persistent.oaistatic.com/pizzaz/pizzaz-1.png",
89
+ "photos": [
90
+ {
91
+ "id": "tonys-pizza-napoletana",
92
+ "title": "Tony's Pizza Napoletana",
93
+ "url": "https://persistent.oaistatic.com/pizzaz/pizzaz-2.png"
94
+ },
95
+ {
96
+ "id": "golden-boy-pizza",
97
+ "title": "Golden Boy Pizza",
98
+ "url": "https://persistent.oaistatic.com/pizzaz/pizzaz-3.png"
99
+ },
100
+ {
101
+ "id": "pizzeria-delfina-mission",
102
+ "title": "Pizzeria Delfina (Mission)",
103
+ "url": "https://persistent.oaistatic.com/pizzaz/pizzaz-6.png"
104
+ },
105
+ {
106
+ "id": "ragazza",
107
+ "title": "Ragazza",
108
+ "url": "https://persistent.oaistatic.com/pizzaz/pizzaz-4.png"
109
+ },
110
+ {
111
+ "id": "del-popolo",
112
+ "title": "Del Popolo",
113
+ "url": "https://persistent.oaistatic.com/pizzaz/pizzaz-5.png"
114
+ },
115
+ {
116
+ "id": "square-pie-guys",
117
+ "title": "Square Pie Guys",
118
+ "url": "https://persistent.oaistatic.com/pizzaz/pizzaz-1.png"
119
+ },
120
+ {
121
+ "id": "zero-zero",
122
+ "title": "Zero Zero",
123
+ "url": "https://persistent.oaistatic.com/pizzaz/pizzaz-2.png"
124
+ }
125
+ ]
126
+ }
127
+ ]
128
+ },
129
+ "_meta": {}
130
+ }
131
+ }