sunpeak 0.20.42 → 0.20.47

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 (56) hide show
  1. package/bin/commands/inspect.mjs +66 -30
  2. package/bin/commands/test-init.mjs +2 -0
  3. package/bin/lib/eval/eval-runner.mjs +4 -0
  4. package/bin/lib/eval/model-registry.mjs +3 -6
  5. package/dist/chatgpt/index.cjs +1 -1
  6. package/dist/chatgpt/index.js +1 -1
  7. package/dist/claude/index.cjs +1 -1
  8. package/dist/claude/index.js +1 -1
  9. package/dist/hooks/tool-data-store.d.ts +26 -0
  10. package/dist/hooks/use-tool-data.d.ts +3 -9
  11. package/dist/host/chatgpt/index.cjs +1 -1
  12. package/dist/host/chatgpt/index.js +1 -1
  13. package/dist/index.cjs +35 -21
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.js +35 -21
  16. package/dist/index.js.map +1 -1
  17. package/dist/inspector/index.cjs +1 -1
  18. package/dist/inspector/index.js +1 -1
  19. package/dist/{inspector-C6n8zap3.js → inspector-BSha-CAW.js} +45 -19
  20. package/dist/inspector-BSha-CAW.js.map +1 -0
  21. package/dist/{inspector-DOmiG64-.cjs → inspector-Chhc2GNO.cjs} +45 -19
  22. package/dist/inspector-Chhc2GNO.cjs.map +1 -0
  23. package/dist/lib/utils.d.ts +8 -7
  24. package/dist/mcp/index.cjs +5 -3
  25. package/dist/mcp/index.cjs.map +1 -1
  26. package/dist/mcp/index.js +5 -3
  27. package/dist/mcp/index.js.map +1 -1
  28. package/dist/mcp/server.d.ts +12 -1
  29. package/dist/{use-app-Duar2Ipu.js → use-app-CtKy52kw.js} +62 -1
  30. package/dist/use-app-CtKy52kw.js.map +1 -0
  31. package/dist/{use-app-DUdnDLP5.cjs → use-app-xaiN0HAd.cjs} +62 -1
  32. package/dist/use-app-xaiN0HAd.cjs.map +1 -0
  33. package/package.json +8 -8
  34. package/template/dist/albums/albums.html +2 -2
  35. package/template/dist/albums/albums.json +1 -1
  36. package/template/dist/carousel/carousel.html +2 -2
  37. package/template/dist/carousel/carousel.json +1 -1
  38. package/template/dist/map/map.html +3 -3
  39. package/template/dist/map/map.json +1 -1
  40. package/template/dist/review/review.html +2 -2
  41. package/template/dist/review/review.json +1 -1
  42. package/template/node_modules/.bin/tsc +2 -2
  43. package/template/node_modules/.bin/tsserver +2 -2
  44. package/template/node_modules/.bin/vitest +2 -2
  45. package/template/node_modules/.vite/deps/_metadata.json +3 -3
  46. package/template/node_modules/.vite-mcp/deps/_metadata.json +20 -20
  47. package/template/node_modules/.vite-mcp/deps/vitest.js +7 -7
  48. package/template/node_modules/.vite-mcp/deps/vitest.js.map +1 -1
  49. package/template/package.json +1 -1
  50. package/template/tests/e2e/visual.spec.ts-snapshots/albums-fullscreen-chatgpt-darwin.png +0 -0
  51. package/template/tests/e2e/visual.spec.ts-snapshots/albums-fullscreen-claude-darwin.png +0 -0
  52. package/template/tsconfig.json +2 -0
  53. package/dist/inspector-C6n8zap3.js.map +0 -1
  54. package/dist/inspector-DOmiG64-.cjs.map +0 -1
  55. package/dist/use-app-DUdnDLP5.cjs.map +0 -1
  56. package/dist/use-app-Duar2Ipu.js.map +0 -1
@@ -308,7 +308,7 @@ async function negotiateOAuth(serverUrl) {
308
308
 
309
309
  // Try the anonymous/auto-approved path first: follow the authorization URL
310
310
  // without a browser and see if it immediately redirects with a code.
311
- const code = await tryAnonymousOAuth(authUrl.toString(), callbackUrl);
311
+ const code = await tryAnonymousOAuth(authUrl.toString(), callbackUrl, oauthState.stateParam);
312
312
  if (code) {
313
313
  // Complete the flow with the authorization code.
314
314
  const tokenResult = await auth(provider, {
@@ -347,15 +347,17 @@ async function negotiateOAuth(serverUrl) {
347
347
  *
348
348
  * @param {string} authUrl - The authorization URL
349
349
  * @param {string} callbackUrl - The expected callback URL prefix
350
+ * @param {string} [expectedState] - OAuth state value that must be echoed by the callback
351
+ * @param {typeof fetch} [fetchFn]
350
352
  * @returns {Promise<string | null>}
351
353
  */
352
- async function tryAnonymousOAuth(authUrl, callbackUrl) {
354
+ async function tryAnonymousOAuth(authUrl, callbackUrl, expectedState, fetchFn = fetch) {
353
355
  // Follow redirects manually to detect when the server redirects back
354
356
  // to our callback URL with a code parameter.
355
357
  let url = authUrl;
356
358
  const maxRedirects = 10;
357
359
  for (let i = 0; i < maxRedirects; i++) {
358
- const response = await fetch(url, { redirect: 'manual' });
360
+ const response = await fetchFn(url, { redirect: 'manual' });
359
361
  const location = response.headers.get('location');
360
362
 
361
363
  if (!location) {
@@ -366,11 +368,21 @@ async function tryAnonymousOAuth(authUrl, callbackUrl) {
366
368
  }
367
369
 
368
370
  // Resolve relative redirects.
369
- const resolved = new URL(location, url).toString();
371
+ const resolvedUrl = new URL(location, url);
372
+ if (resolvedUrl.protocol !== 'http:' && resolvedUrl.protocol !== 'https:') {
373
+ throw new Error(
374
+ `OAuth authorization redirect has unsupported scheme: ${resolvedUrl.protocol}`
375
+ );
376
+ }
377
+ const resolved = resolvedUrl.toString();
370
378
 
371
379
  // Check if the redirect goes to our callback URL.
372
380
  if (resolved.startsWith(callbackUrl)) {
373
381
  const params = new URL(resolved).searchParams;
382
+ const state = params.get('state');
383
+ if (expectedState && state !== expectedState) {
384
+ throw new Error('OAuth state mismatch — callback rejected');
385
+ }
374
386
  const code = params.get('code');
375
387
  if (code) return code;
376
388
  const error = params.get('error');
@@ -906,45 +918,68 @@ async function discoverSimulations(client) {
906
918
 
907
919
  /**
908
920
  * Load simulation JSON fixtures from a directory and merge into discovered simulations.
921
+ *
922
+ * Each fixture becomes a simulation keyed by its filename, so a tool can have
923
+ * multiple fixtures (e.g. `show-albums.json` and `show-albums-empty.json`
924
+ * both targeting tool `show-albums`). Auto-discovered slots are kept only for
925
+ * tools that have no fixture file.
926
+ *
909
927
  * @param {string} dir - Simulation directory path
910
928
  * @param {Record<string, object>} simulations - Discovered simulations to merge into
911
929
  */
912
- function mergeSimulationFixtures(dir, simulations) {
930
+ export function mergeSimulationFixtures(dir, simulations) {
913
931
  if (!existsSync(dir)) return;
914
932
 
915
933
  const files = readdirSync(dir).filter((f) => f.endsWith('.json'));
934
+
935
+ // Load every fixture first so we can group by tool name. We need the grouping
936
+ // to decide whether to keep the auto-discovered slot (no fixtures) or replace
937
+ // it with one entry per fixture file (one or more fixtures).
938
+ const fixtures = [];
916
939
  for (const file of files) {
917
940
  try {
918
941
  const fixture = JSON.parse(readFileSync(join(dir, file), 'utf-8'));
919
- const toolName = fixture.tool;
920
- if (!toolName) continue;
921
-
922
- // Find matching simulation by tool name
923
- const sim = simulations[toolName];
924
- if (sim) {
925
- // Merge fixture data into discovered simulation
926
- if (fixture.toolInput !== undefined) sim.toolInput = fixture.toolInput;
927
- if (fixture.toolResult !== undefined) sim.toolResult = fixture.toolResult;
928
- if (fixture.serverTools !== undefined) sim.serverTools = fixture.serverTools;
929
- if (fixture.userMessage !== undefined) sim.userMessage = fixture.userMessage;
930
- if (fixture.hostContext !== undefined) sim.hostContext = fixture.hostContext;
931
- } else {
932
- // Create a new simulation from the fixture (tool not on server, but user wants to mock it)
933
- const simName = file.replace(/\.json$/, '');
934
- simulations[simName] = {
935
- name: simName,
936
- tool: { name: toolName, inputSchema: { type: 'object' } },
937
- toolInput: fixture.toolInput,
938
- toolResult: fixture.toolResult,
939
- serverTools: fixture.serverTools,
940
- userMessage: fixture.userMessage,
941
- hostContext: fixture.hostContext,
942
- };
943
- }
942
+ if (!fixture.tool) continue;
943
+ fixtures.push({ file, fixture });
944
944
  } catch (err) {
945
945
  console.warn(`Warning: Failed to parse simulation fixture ${file}:`, err.message);
946
946
  }
947
947
  }
948
+
949
+ const byTool = new Map();
950
+ for (const item of fixtures) {
951
+ const tool = item.fixture.tool;
952
+ if (!byTool.has(tool)) byTool.set(tool, []);
953
+ byTool.get(tool).push(item);
954
+ }
955
+
956
+ for (const [toolName, items] of byTool) {
957
+ const discovered = simulations[toolName];
958
+
959
+ // Drop the auto-discovered slot if none of the fixtures will reuse its
960
+ // key (filename === tool name). Otherwise the named fixture overwrites
961
+ // it in place below.
962
+ const reusesSlot = items.some(({ file }) => file.replace(/\.json$/, '') === toolName);
963
+ if (discovered && !reusesSlot) {
964
+ delete simulations[toolName];
965
+ }
966
+
967
+ for (const { file, fixture } of items) {
968
+ const simName = file.replace(/\.json$/, '');
969
+ const sim = discovered
970
+ ? { ...discovered, name: simName }
971
+ : {
972
+ name: simName,
973
+ tool: { name: toolName, inputSchema: { type: 'object' } },
974
+ };
975
+ if (fixture.toolInput !== undefined) sim.toolInput = fixture.toolInput;
976
+ if (fixture.toolResult !== undefined) sim.toolResult = fixture.toolResult;
977
+ if (fixture.serverTools !== undefined) sim.serverTools = fixture.serverTools;
978
+ if (fixture.userMessage !== undefined) sim.userMessage = fixture.userMessage;
979
+ if (fixture.hostContext !== undefined) sim.hostContext = fixture.hostContext;
980
+ simulations[simName] = sim;
981
+ }
982
+ }
948
983
  }
949
984
 
950
985
  const MODEL_PROVIDERS = new Set(['openai', 'anthropic']);
@@ -2875,6 +2910,7 @@ export const _securityTestExports = {
2875
2910
  readRequestBody,
2876
2911
  resolveHttpRedirectsForMcp,
2877
2912
  shouldAllowPrivateServerUrls,
2913
+ tryAnonymousOAuth,
2878
2914
  };
2879
2915
 
2880
2916
  /**
@@ -596,8 +596,10 @@ ${serverBlock}
596
596
  {
597
597
  compilerOptions: {
598
598
  target: 'ES2022',
599
+ lib: ['ESNext', 'DOM'],
599
600
  module: 'ESNext',
600
601
  moduleResolution: 'bundler',
602
+ types: ['node'],
601
603
  strict: true,
602
604
  esModuleInterop: true,
603
605
  },
@@ -220,12 +220,16 @@ export async function runSingleEval({
220
220
  }) {
221
221
  const { generateText } = await import('ai');
222
222
  const system = formatEvalAppContextForModel(appContext);
223
+ const providerOptions = model?.provider?.startsWith('openai.')
224
+ ? { openai: { strictJsonSchema: false } }
225
+ : undefined;
223
226
 
224
227
  const result = await generateText({
225
228
  model,
226
229
  tools,
227
230
  prompt,
228
231
  ...(system ? { system } : {}),
232
+ ...(providerOptions ? { providerOptions } : {}),
229
233
  maxSteps,
230
234
  temperature,
231
235
  maxRetries: 0, // We manage runs ourselves; AI SDK retries compound rate limits
@@ -47,12 +47,9 @@ export async function resolveModel(modelId) {
47
47
  // @ai-sdk/openai v3 defaults to the Responses API, which requires strict
48
48
  // JSON Schema (additionalProperties: false at every level, all properties
49
49
  // required) — incompatible with arbitrary MCP server schemas. Use .chat()
50
- // (Chat Completions API) when available and disable structured outputs,
51
- // because reasoning models also enable strict function schemas by default.
52
- const settings = { structuredOutputs: false };
53
- return typeof openai.chat === 'function'
54
- ? openai.chat(modelId, settings)
55
- : openai(modelId, settings);
50
+ // (Chat Completions API) when available. The eval runner disables strict
51
+ // JSON Schema through per-call provider options.
52
+ return typeof openai.chat === 'function' ? openai.chat(modelId) : openai(modelId);
56
53
  }
57
54
  if (pkg === '@ai-sdk/anthropic') {
58
55
  const { anthropic } = provider;
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("../chunk-Cek0wNdY.cjs");
3
- const require_inspector = require("../inspector-DOmiG64-.cjs");
3
+ const require_inspector = require("../inspector-Chhc2GNO.cjs");
4
4
  const require_inspector_url = require("../inspector-url-BxScdDag.cjs");
5
5
  const require_discovery = require("../discovery-31_n0zcu.cjs");
6
6
  //#region src/chatgpt/index.ts
@@ -1,5 +1,5 @@
1
1
  import { Ct as __exportAll } from "../protocol-bhrz2H_E.js";
2
- import { _ as extractResourceCSP, f as ThemeProvider, g as IframeResource, p as useThemeContext, r as resolveServerToolResult, t as Inspector, v as McpAppHost, y as SCREEN_WIDTHS } from "../inspector-C6n8zap3.js";
2
+ import { _ as extractResourceCSP, f as ThemeProvider, g as IframeResource, p as useThemeContext, r as resolveServerToolResult, t as Inspector, v as McpAppHost, y as SCREEN_WIDTHS } from "../inspector-BSha-CAW.js";
3
3
  import { t as createInspectorUrl } from "../inspector-url-xUMGbWis.js";
4
4
  import { c as toPascalCase, i as findResourceKey, n as extractSimulationKey, r as findResourceDirs, s as getComponentName, t as extractResourceKey } from "../discovery-DOVner--.js";
5
5
  //#region src/chatgpt/index.ts
@@ -1,4 +1,4 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  require("../chunk-Cek0wNdY.cjs");
3
- const require_inspector = require("../inspector-DOmiG64-.cjs");
3
+ const require_inspector = require("../inspector-Chhc2GNO.cjs");
4
4
  exports.Inspector = require_inspector.Inspector;
@@ -1,2 +1,2 @@
1
- import { t as Inspector } from "../inspector-C6n8zap3.js";
1
+ import { t as Inspector } from "../inspector-BSha-CAW.js";
2
2
  export { Inspector };
@@ -0,0 +1,26 @@
1
+ import { App } from '@modelcontextprotocol/ext-apps';
2
+ export interface ToolData<TInput = unknown, TOutput = unknown> {
3
+ input: TInput | null;
4
+ inputPartial: TInput | null;
5
+ output: TOutput | null;
6
+ isError: boolean;
7
+ isLoading: boolean;
8
+ isCancelled: boolean;
9
+ cancelReason: string | null;
10
+ }
11
+ export interface ToolDataStore<TInput = unknown, TOutput = unknown> {
12
+ data: ToolData<TInput, TOutput>;
13
+ listeners: Set<() => void>;
14
+ }
15
+ declare module '@modelcontextprotocol/ext-apps' {
16
+ interface App {
17
+ /** @internal Eager store attached by AppProvider before connect(). */
18
+ __toolDataStore?: ToolDataStore;
19
+ }
20
+ }
21
+ /**
22
+ * Initialize the tool-data store on an App instance and wire its event
23
+ * listeners. Idempotent - returns the existing store if one is already
24
+ * attached.
25
+ */
26
+ export declare function initToolDataStore(app: App): ToolDataStore;
@@ -1,12 +1,6 @@
1
- export interface ToolData<TInput = unknown, TOutput = unknown> {
2
- input: TInput | null;
3
- inputPartial: TInput | null;
4
- output: TOutput | null;
5
- isError: boolean;
6
- isLoading: boolean;
7
- isCancelled: boolean;
8
- cancelReason: string | null;
9
- }
1
+ import { initToolDataStore, ToolData } from './tool-data-store';
2
+ export type { ToolData };
3
+ export { initToolDataStore };
10
4
  /**
11
5
  * Reactive access to tool input and output data from the MCP Apps host.
12
6
  *
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  require("../../chunk-Cek0wNdY.cjs");
3
- const require_use_app = require("../../use-app-DUdnDLP5.cjs");
3
+ const require_use_app = require("../../use-app-xaiN0HAd.cjs");
4
4
  let react = require("react");
5
5
  //#region src/host/chatgpt/openai-types.ts
6
6
  /**
@@ -1,4 +1,4 @@
1
- import { t as useApp } from "../../use-app-Duar2Ipu.js";
1
+ import { t as useApp } from "../../use-app-CtKy52kw.js";
2
2
  import { useCallback } from "react";
3
3
  //#region src/host/chatgpt/openai-types.ts
4
4
  /**
package/dist/index.cjs CHANGED
@@ -1,8 +1,8 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("./chunk-Cek0wNdY.cjs");
3
3
  const require_protocol = require("./protocol-Cafvpf0x.cjs");
4
- const require_use_app = require("./use-app-DUdnDLP5.cjs");
5
- const require_inspector = require("./inspector-DOmiG64-.cjs");
4
+ const require_use_app = require("./use-app-xaiN0HAd.cjs");
5
+ const require_inspector = require("./inspector-Chhc2GNO.cjs");
6
6
  const require_host_index = require("./host/index.cjs");
7
7
  const require_inspector_index = require("./inspector/index.cjs");
8
8
  const require_chatgpt_index = require("./chatgpt/index.cjs");
@@ -455,9 +455,22 @@ function useHostContext() {
455
455
  //#region src/hooks/use-tool-data.ts
456
456
  var stores = /* @__PURE__ */ new WeakMap();
457
457
  function getStore(app, defaultInput, defaultOutput) {
458
+ const eager = app.__toolDataStore;
459
+ if (eager) {
460
+ if (defaultInput !== void 0 && eager.data.input === null) eager.data = {
461
+ ...eager.data,
462
+ input: defaultInput
463
+ };
464
+ if (defaultOutput !== void 0 && eager.data.output === null) eager.data = {
465
+ ...eager.data,
466
+ output: defaultOutput,
467
+ isLoading: false
468
+ };
469
+ return eager;
470
+ }
458
471
  let store = stores.get(app);
459
472
  if (!store) {
460
- store = {
473
+ const lazy = {
461
474
  data: {
462
475
  input: defaultInput ?? null,
463
476
  inputPartial: null,
@@ -469,43 +482,44 @@ function getStore(app, defaultInput, defaultOutput) {
469
482
  },
470
483
  listeners: /* @__PURE__ */ new Set()
471
484
  };
472
- stores.set(app, store);
485
+ stores.set(app, lazy);
486
+ store = lazy;
473
487
  const notify = () => {
474
- for (const fn of store.listeners) fn();
488
+ for (const fn of lazy.listeners) fn();
475
489
  };
476
- app.ontoolinput = (_params) => {
477
- store.data = {
478
- ...store.data,
490
+ app.addEventListener("toolinput", (_params) => {
491
+ lazy.data = {
492
+ ...lazy.data,
479
493
  input: _params.arguments,
480
494
  inputPartial: null
481
495
  };
482
496
  notify();
483
- };
484
- app.ontoolinputpartial = (_params) => {
485
- store.data = {
486
- ...store.data,
497
+ });
498
+ app.addEventListener("toolinputpartial", (_params) => {
499
+ lazy.data = {
500
+ ...lazy.data,
487
501
  inputPartial: _params.arguments
488
502
  };
489
503
  notify();
490
- };
491
- app.ontoolresult = (_params) => {
492
- store.data = {
493
- ...store.data,
504
+ });
505
+ app.addEventListener("toolresult", (_params) => {
506
+ lazy.data = {
507
+ ...lazy.data,
494
508
  output: _params.structuredContent ?? _params.content,
495
509
  isError: _params.isError ?? false,
496
510
  isLoading: false
497
511
  };
498
512
  notify();
499
- };
500
- app.ontoolcancelled = (_params) => {
501
- store.data = {
502
- ...store.data,
513
+ });
514
+ app.addEventListener("toolcancelled", (_params) => {
515
+ lazy.data = {
516
+ ...lazy.data,
503
517
  isCancelled: true,
504
518
  cancelReason: _params.reason ?? null,
505
519
  isLoading: false
506
520
  };
507
521
  notify();
508
- };
522
+ });
509
523
  }
510
524
  return store;
511
525
  }