skybridge 0.0.0-dev.f57f202 → 0.0.0-dev.f58ff90

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 (109) hide show
  1. package/README.md +9 -9
  2. package/dist/cli/build-helpers.d.ts +1 -1
  3. package/dist/cli/build-helpers.js +9 -1
  4. package/dist/cli/build-helpers.js.map +1 -1
  5. package/dist/cli/build-helpers.test.js +24 -0
  6. package/dist/cli/build-helpers.test.js.map +1 -1
  7. package/dist/cli/build-steps.js +16 -5
  8. package/dist/cli/build-steps.js.map +1 -1
  9. package/dist/cli/build-steps.test.js +20 -0
  10. package/dist/cli/build-steps.test.js.map +1 -1
  11. package/dist/cli/resolve-skybridge-config.d.ts +1 -0
  12. package/dist/cli/resolve-skybridge-config.js +11 -1
  13. package/dist/cli/resolve-skybridge-config.js.map +1 -1
  14. package/dist/cli/use-typescript-check.js +4 -2
  15. package/dist/cli/use-typescript-check.js.map +1 -1
  16. package/dist/server/app.d.ts +22 -5
  17. package/dist/server/app.js +24 -13
  18. package/dist/server/app.js.map +1 -1
  19. package/dist/server/app.test.js +81 -0
  20. package/dist/server/app.test.js.map +1 -1
  21. package/dist/server/events.d.ts +113 -0
  22. package/dist/server/events.js +318 -0
  23. package/dist/server/events.js.map +1 -0
  24. package/dist/server/events.test.d.ts +1 -0
  25. package/dist/server/events.test.js +172 -0
  26. package/dist/server/events.test.js.map +1 -0
  27. package/dist/server/express.d.ts +3 -2
  28. package/dist/server/express.js +17 -11
  29. package/dist/server/express.js.map +1 -1
  30. package/dist/server/express.test.js +45 -0
  31. package/dist/server/express.test.js.map +1 -1
  32. package/dist/server/form-input.d.ts +120 -0
  33. package/dist/server/form-input.js +89 -0
  34. package/dist/server/form-input.js.map +1 -0
  35. package/dist/server/index.d.ts +4 -2
  36. package/dist/server/index.js +3 -1
  37. package/dist/server/index.js.map +1 -1
  38. package/dist/server/mcp-errors.test.js +188 -0
  39. package/dist/server/mcp-errors.test.js.map +1 -1
  40. package/dist/server/metric.d.ts +5 -3
  41. package/dist/server/metric.js +5 -5
  42. package/dist/server/metric.js.map +1 -1
  43. package/dist/server/register-tool.test.js +153 -0
  44. package/dist/server/register-tool.test.js.map +1 -1
  45. package/dist/server/server.d.ts +179 -10
  46. package/dist/server/server.js +247 -23
  47. package/dist/server/server.js.map +1 -1
  48. package/dist/server/templateHelper.d.ts +1 -0
  49. package/dist/server/templateHelper.js.map +1 -1
  50. package/dist/server/templates.generated.js +26 -12
  51. package/dist/server/templates.generated.js.map +1 -1
  52. package/dist/server/tool-extra.test.js +79 -0
  53. package/dist/server/tool-extra.test.js.map +1 -1
  54. package/dist/server/view-resource-resolution.test.js +35 -0
  55. package/dist/server/view-resource-resolution.test.js.map +1 -1
  56. package/dist/web/bridges/adaptor.d.ts +9 -2
  57. package/dist/web/bridges/adaptor.js +113 -10
  58. package/dist/web/bridges/adaptor.js.map +1 -1
  59. package/dist/web/bridges/adaptor.test.js +66 -5
  60. package/dist/web/bridges/adaptor.test.js.map +1 -1
  61. package/dist/web/bridges/apps-sdk/bridge.js +1 -1
  62. package/dist/web/bridges/apps-sdk/bridge.js.map +1 -1
  63. package/dist/web/bridges/apps-sdk/types.d.ts +3 -4
  64. package/dist/web/bridges/apps-sdk/types.js.map +1 -1
  65. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.d.ts +3 -3
  66. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.js +3 -3
  67. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.js.map +1 -1
  68. package/dist/web/bridges/mcp-app/types.d.ts +5 -13
  69. package/dist/web/bridges/mcp-app/types.js.map +1 -1
  70. package/dist/web/bridges/mcp-app/use-mcp-app-context.d.ts +1 -1
  71. package/dist/web/bridges/mcp-app/use-mcp-app-context.js +1 -1
  72. package/dist/web/bridges/mcp-app/use-mcp-app-context.js.map +1 -1
  73. package/dist/web/bridges/types.d.ts +45 -6
  74. package/dist/web/bridges/types.js +1 -1
  75. package/dist/web/bridges/types.js.map +1 -1
  76. package/dist/web/hooks/index.d.ts +2 -0
  77. package/dist/web/hooks/index.js +2 -0
  78. package/dist/web/hooks/index.js.map +1 -1
  79. package/dist/web/hooks/openai-extensions.test.js +1 -8
  80. package/dist/web/hooks/openai-extensions.test.js.map +1 -1
  81. package/dist/web/hooks/use-call-tool.js +1 -1
  82. package/dist/web/hooks/use-call-tool.js.map +1 -1
  83. package/dist/web/hooks/use-call-tool.test.js +6 -2
  84. package/dist/web/hooks/use-call-tool.test.js.map +1 -1
  85. package/dist/web/hooks/use-deep-link.js +1 -27
  86. package/dist/web/hooks/use-deep-link.js.map +1 -1
  87. package/dist/web/hooks/use-file-resource.d.ts +37 -0
  88. package/dist/web/hooks/use-file-resource.js +98 -0
  89. package/dist/web/hooks/use-file-resource.js.map +1 -0
  90. package/dist/web/hooks/use-file-resource.test.d.ts +1 -0
  91. package/dist/web/hooks/use-file-resource.test.js +65 -0
  92. package/dist/web/hooks/use-file-resource.test.js.map +1 -0
  93. package/dist/web/hooks/use-host.d.ts +3 -3
  94. package/dist/web/hooks/use-host.js +4 -4
  95. package/dist/web/hooks/use-host.js.map +1 -1
  96. package/dist/web/hooks/use-model-context.js +3 -5
  97. package/dist/web/hooks/use-model-context.js.map +1 -1
  98. package/dist/web/hooks/use-open-file.d.ts +20 -0
  99. package/dist/web/hooks/use-open-file.js +25 -0
  100. package/dist/web/hooks/use-open-file.js.map +1 -0
  101. package/dist/web/hooks/use-register-view-tool.d.ts +1 -1
  102. package/dist/web/hooks/use-register-view-tool.js +1 -1
  103. package/dist/web/hooks/use-register-view-tool.js.map +1 -1
  104. package/dist/web/hooks/use-send-follow-up-message.d.ts +8 -2
  105. package/dist/web/hooks/use-send-follow-up-message.js +6 -0
  106. package/dist/web/hooks/use-send-follow-up-message.js.map +1 -1
  107. package/dist/web/mount-view.js +3 -5
  108. package/dist/web/mount-view.js.map +1 -1
  109. package/package.json +4 -2
@@ -0,0 +1,98 @@
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
+ import { getAdaptor, NotSupportedError, } from "../bridges/index.js";
3
+ /**
4
+ * Read a resource by URI, re-read it whenever the host reports a change, and
5
+ * write it back. Built for the file a ChatGPT file entrypoint opened, whose
6
+ * `resourceUri` arrives in the tool input as `file.resourceUri`.
7
+ *
8
+ * Reading uses the standard `resources/read`. `representation` asks ChatGPT
9
+ * for text or a base64 blob. `write` uses the OpenAI MCP extensions
10
+ * (`openai/resources/write`) and sends the current `etag` as `ifMatch`, so it
11
+ * resolves with `conflict` instead of overwriting a newer version, and then
12
+ * re-reads the latest one. When ChatGPT returned no `etag`, the write has no
13
+ * version check. It rejects with `NotSupportedError` when the host doesn't
14
+ * advertise `openai/resource`, or the resource isn't loaded or writable.
15
+ *
16
+ * Pass `undefined` or an empty string as `uri` to do nothing, for example outside a file
17
+ * entrypoint.
18
+ *
19
+ * @example
20
+ * ```tsx
21
+ * const { input } = useToolInfo<"open-part">();
22
+ * const { data, write } = useFileResource(input.file?.resourceUri, {
23
+ * representation: "text",
24
+ * });
25
+ * ```
26
+ *
27
+ * @see https://docs.skybridge.tech/api-reference/use-file-resource
28
+ */
29
+ export function useFileResource(uri, options) {
30
+ const representation = options?.representation;
31
+ const [state, setState] = useState();
32
+ const sequence = useRef(0);
33
+ const current = uri && state?.uri === uri ? state : undefined;
34
+ const currentRef = useRef(current);
35
+ currentRef.current = current;
36
+ const read = useCallback(() => {
37
+ if (!uri) {
38
+ return;
39
+ }
40
+ const id = ++sequence.current;
41
+ getAdaptor()
42
+ .readResource(uri, representation)
43
+ .then((resource) => ({ resource, error: undefined }), (error) => ({ error }))
44
+ .then((next) => {
45
+ if (id === sequence.current) {
46
+ setState((previous) => ({
47
+ ...(previous?.uri === uri && previous),
48
+ uri,
49
+ ...next,
50
+ }));
51
+ }
52
+ });
53
+ }, [uri, representation]);
54
+ useEffect(() => {
55
+ if (!uri) {
56
+ return;
57
+ }
58
+ read();
59
+ const stopWatching = getAdaptor().watchResource(uri, read);
60
+ return () => {
61
+ sequence.current++;
62
+ stopWatching();
63
+ };
64
+ }, [uri, read]);
65
+ const write = useCallback(async (content) => {
66
+ const resource = currentRef.current?.resource;
67
+ if (!uri || currentRef.current?.uri !== uri || !resource?.writable) {
68
+ throw new NotSupportedError("writeResource", "the resource is not loaded or not writable");
69
+ }
70
+ const result = await getAdaptor().writeResource(uri, content, resource.etag);
71
+ if (currentRef.current?.uri !== uri) {
72
+ return result;
73
+ }
74
+ if (result.outcome === "saved") {
75
+ sequence.current++;
76
+ setState({
77
+ uri,
78
+ resource: {
79
+ ...content,
80
+ mimeType: resource.mimeType,
81
+ writable: true,
82
+ etag: result.etag,
83
+ },
84
+ });
85
+ }
86
+ else if (result.outcome === "conflict") {
87
+ read();
88
+ }
89
+ return result;
90
+ }, [uri, read]);
91
+ return {
92
+ data: current?.resource,
93
+ isLoading: Boolean(uri) && current === undefined,
94
+ error: current?.error,
95
+ write,
96
+ };
97
+ }
98
+ //# sourceMappingURL=use-file-resource.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-file-resource.js","sourceRoot":"","sources":["../../../src/web/hooks/use-file-resource.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport {\n type FileResource,\n type FileResourceContent,\n type FileResourceWriteResult,\n getAdaptor,\n NotSupportedError,\n type ResourceRepresentation,\n} from \"../bridges/index.js\";\n\n/** State and actions returned by {@link useFileResource}. */\nexport type FileResourceState = {\n data: FileResource | undefined;\n isLoading: boolean;\n error: unknown;\n write: (content: FileResourceContent) => Promise<FileResourceWriteResult>;\n};\n\n/**\n * Read a resource by URI, re-read it whenever the host reports a change, and\n * write it back. Built for the file a ChatGPT file entrypoint opened, whose\n * `resourceUri` arrives in the tool input as `file.resourceUri`.\n *\n * Reading uses the standard `resources/read`. `representation` asks ChatGPT\n * for text or a base64 blob. `write` uses the OpenAI MCP extensions\n * (`openai/resources/write`) and sends the current `etag` as `ifMatch`, so it\n * resolves with `conflict` instead of overwriting a newer version, and then\n * re-reads the latest one. When ChatGPT returned no `etag`, the write has no\n * version check. It rejects with `NotSupportedError` when the host doesn't\n * advertise `openai/resource`, or the resource isn't loaded or writable.\n *\n * Pass `undefined` or an empty string as `uri` to do nothing, for example outside a file\n * entrypoint.\n *\n * @example\n * ```tsx\n * const { input } = useToolInfo<\"open-part\">();\n * const { data, write } = useFileResource(input.file?.resourceUri, {\n * representation: \"text\",\n * });\n * ```\n *\n * @see https://docs.skybridge.tech/api-reference/use-file-resource\n */\nexport function useFileResource(\n uri: string | undefined,\n options?: { representation?: ResourceRepresentation },\n): FileResourceState {\n const representation = options?.representation;\n const [state, setState] = useState<{\n uri: string;\n resource?: FileResource;\n error?: unknown;\n }>();\n const sequence = useRef(0);\n const current = uri && state?.uri === uri ? state : undefined;\n const currentRef = useRef(current);\n currentRef.current = current;\n\n const read = useCallback(() => {\n if (!uri) {\n return;\n }\n const id = ++sequence.current;\n getAdaptor()\n .readResource(uri, representation)\n .then(\n (resource) => ({ resource, error: undefined }),\n (error: unknown) => ({ error }),\n )\n .then((next) => {\n if (id === sequence.current) {\n setState((previous) => ({\n ...(previous?.uri === uri && previous),\n uri,\n ...next,\n }));\n }\n });\n }, [uri, representation]);\n\n useEffect(() => {\n if (!uri) {\n return;\n }\n read();\n const stopWatching = getAdaptor().watchResource(uri, read);\n return () => {\n sequence.current++;\n stopWatching();\n };\n }, [uri, read]);\n\n const write = useCallback(\n async (content: FileResourceContent) => {\n const resource = currentRef.current?.resource;\n if (!uri || currentRef.current?.uri !== uri || !resource?.writable) {\n throw new NotSupportedError(\n \"writeResource\",\n \"the resource is not loaded or not writable\",\n );\n }\n const result = await getAdaptor().writeResource(\n uri,\n content,\n resource.etag,\n );\n if (currentRef.current?.uri !== uri) {\n return result;\n }\n if (result.outcome === \"saved\") {\n sequence.current++;\n setState({\n uri,\n resource: {\n ...content,\n mimeType: resource.mimeType,\n writable: true,\n etag: result.etag,\n },\n });\n } else if (result.outcome === \"conflict\") {\n read();\n }\n return result;\n },\n [uri, read],\n );\n\n return {\n data: current?.resource,\n isLoading: Boolean(uri) && current === undefined,\n error: current?.error,\n write,\n };\n}\n"]}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,65 @@
1
+ import { act, renderHook, waitFor } from "@testing-library/react";
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3
+ import { HostAdaptor } from "../bridges/adaptor.js";
4
+ import { getAdaptor } from "../bridges/get-adaptor.js";
5
+ import { McpAppBridge } from "../bridges/mcp-app/bridge.js";
6
+ import { useFileResource } from "./use-file-resource.js";
7
+ describe("useFileResource", () => {
8
+ beforeEach(() => {
9
+ vi.stubGlobal("parent", { postMessage: vi.fn() });
10
+ vi.stubGlobal("skybridge", {});
11
+ vi.stubGlobal("openai", undefined);
12
+ });
13
+ afterEach(() => {
14
+ vi.unstubAllGlobals();
15
+ HostAdaptor.resetInstance();
16
+ McpAppBridge.resetInstance();
17
+ });
18
+ it("reads the resource, re-reads it on update and writes with the current etag", async () => {
19
+ let version = 1;
20
+ let onUpdated = () => { };
21
+ const request = vi.fn(async ({ method }) => method === "openai/resources/write"
22
+ ? { outcome: "saved", etag: "v3" }
23
+ : {});
24
+ const app = {
25
+ getHostCapabilities: () => ({ experimental: { "openai/resource": {} } }),
26
+ readServerResource: vi.fn(async ({ uri }) => ({
27
+ contents: [
28
+ {
29
+ uri,
30
+ text: `version ${version}`,
31
+ _meta: {
32
+ "openai/resource": { writable: true, etag: `v${version}` },
33
+ },
34
+ },
35
+ ],
36
+ })),
37
+ setNotificationHandler: (_method, handler) => {
38
+ onUpdated = handler;
39
+ },
40
+ request,
41
+ };
42
+ getAdaptor().mcp.getApp = () => Promise.resolve(app);
43
+ const { result } = renderHook(() => useFileResource("host-resource://part", { representation: "text" }));
44
+ await waitFor(() => expect(result.current.data).toMatchObject({
45
+ text: "version 1",
46
+ etag: "v1",
47
+ }));
48
+ await waitFor(() => expect(request).toHaveBeenCalledWith({
49
+ method: "resources/subscribe",
50
+ params: { uri: "host-resource://part" },
51
+ }, expect.anything()));
52
+ version = 2;
53
+ act(() => onUpdated({ params: { uri: "host-resource://part" } }));
54
+ await waitFor(() => expect(result.current.data).toMatchObject({ etag: "v2" }));
55
+ await act(async () => {
56
+ await result.current.write({ text: "edited" });
57
+ });
58
+ expect(request).toHaveBeenCalledWith({
59
+ method: "openai/resources/write",
60
+ params: { uri: "host-resource://part", text: "edited", ifMatch: "v2" },
61
+ }, expect.anything());
62
+ expect(result.current.data).toMatchObject({ text: "edited", etag: "v3" });
63
+ });
64
+ });
65
+ //# sourceMappingURL=use-file-resource.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-file-resource.test.js","sourceRoot":"","sources":["../../../src/web/hooks/use-file-resource.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { act, renderHook, waitFor } from \"@testing-library/react\";\nimport { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\nimport { HostAdaptor } from \"../bridges/adaptor.js\";\nimport { getAdaptor } from \"../bridges/get-adaptor.js\";\nimport { McpAppBridge } from \"../bridges/mcp-app/bridge.js\";\nimport { useFileResource } from \"./use-file-resource.js\";\n\ndescribe(\"useFileResource\", () => {\n beforeEach(() => {\n vi.stubGlobal(\"parent\", { postMessage: vi.fn() });\n vi.stubGlobal(\"skybridge\", {});\n vi.stubGlobal(\"openai\", undefined);\n });\n\n afterEach(() => {\n vi.unstubAllGlobals();\n HostAdaptor.resetInstance();\n McpAppBridge.resetInstance();\n });\n\n it(\"reads the resource, re-reads it on update and writes with the current etag\", async () => {\n let version = 1;\n let onUpdated: (notification: { params: { uri: string } }) => void =\n () => {};\n const request = vi.fn(async ({ method }: { method: string }) =>\n method === \"openai/resources/write\"\n ? { outcome: \"saved\", etag: \"v3\" }\n : {},\n );\n const app = {\n getHostCapabilities: () => ({ experimental: { \"openai/resource\": {} } }),\n readServerResource: vi.fn(async ({ uri }: { uri: string }) => ({\n contents: [\n {\n uri,\n text: `version ${version}`,\n _meta: {\n \"openai/resource\": { writable: true, etag: `v${version}` },\n },\n },\n ],\n })),\n setNotificationHandler: (_method: string, handler: typeof onUpdated) => {\n onUpdated = handler;\n },\n request,\n };\n (\n getAdaptor() as unknown as { mcp: { getApp: () => Promise<object> } }\n ).mcp.getApp = () => Promise.resolve(app);\n\n const { result } = renderHook(() =>\n useFileResource(\"host-resource://part\", { representation: \"text\" }),\n );\n await waitFor(() =>\n expect(result.current.data).toMatchObject({\n text: \"version 1\",\n etag: \"v1\",\n }),\n );\n await waitFor(() =>\n expect(request).toHaveBeenCalledWith(\n {\n method: \"resources/subscribe\",\n params: { uri: \"host-resource://part\" },\n },\n expect.anything(),\n ),\n );\n\n version = 2;\n act(() => onUpdated({ params: { uri: \"host-resource://part\" } }));\n await waitFor(() =>\n expect(result.current.data).toMatchObject({ etag: \"v2\" }),\n );\n\n await act(async () => {\n await result.current.write({ text: \"edited\" });\n });\n expect(request).toHaveBeenCalledWith(\n {\n method: \"openai/resources/write\",\n params: { uri: \"host-resource://part\", text: \"edited\", ifMatch: \"v2\" },\n },\n expect.anything(),\n );\n expect(result.current.data).toMatchObject({ text: \"edited\", etag: \"v3\" });\n });\n});\n"]}
@@ -25,7 +25,7 @@ export type HostInfo = {
25
25
  /** The host's MCP Apps capabilities, `undefined` until the handshake. */
26
26
  capabilities: McpUiHostCapabilities | undefined;
27
27
  /** OpenAI MCP extensions the host supports. */
28
- openai: OpenAIHostCapabilities;
28
+ openaiCapabilities: OpenAIHostCapabilities;
29
29
  };
30
30
  /**
31
31
  * Identity and capabilities of the host application rendering the view, from
@@ -36,9 +36,9 @@ export type HostInfo = {
36
36
  *
37
37
  * @example
38
38
  * ```tsx
39
- * const { name, openai } = useHost();
39
+ * const { name, openaiCapabilities } = useHost();
40
40
  * if (name === "claude") return <ClaudeLayout />;
41
- * if (openai.files) return <OpenFileButton />;
41
+ * if (openaiCapabilities.files) return <OpenFileButton />;
42
42
  * ```
43
43
  */
44
44
  export declare function useHost(): HostInfo;
@@ -17,16 +17,16 @@ const HOST_BY_REPORTED_NAME = {
17
17
  *
18
18
  * @example
19
19
  * ```tsx
20
- * const { name, openai } = useHost();
20
+ * const { name, openaiCapabilities } = useHost();
21
21
  * if (name === "claude") return <ClaudeLayout />;
22
- * if (openai.files) return <OpenFileButton />;
22
+ * if (openaiCapabilities.files) return <OpenFileButton />;
23
23
  * ```
24
24
  */
25
25
  export function useHost() {
26
26
  const hostInfo = useMcpAppContext("hostInfo");
27
27
  const capabilities = useMcpAppContext("hostCapabilities") ?? undefined;
28
28
  const name = hostInfo?.name;
29
- const openai = useMemo(() => {
29
+ const openaiCapabilities = useMemo(() => {
30
30
  const supports = (extension) => Boolean(capabilities?.experimental?.[`openai/${extension}`]);
31
31
  return {
32
32
  resource: supports("resource"),
@@ -39,7 +39,7 @@ export function useHost() {
39
39
  name: name !== undefined ? (HOST_BY_REPORTED_NAME[name] ?? name) : undefined,
40
40
  version: hostInfo?.version,
41
41
  capabilities,
42
- openai,
42
+ openaiCapabilities,
43
43
  };
44
44
  }
45
45
  //# sourceMappingURL=use-host.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-host.js","sourceRoot":"","sources":["../../../src/web/hooks/use-host.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAChC,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAcvD,MAAM,qBAAqB,GAAyB;IAClD,OAAO,EAAE,SAAS;IAClB,MAAM,EAAE,QAAQ;IAChB,MAAM,EAAE,QAAQ;IAChB,aAAa,EAAE,OAAO;IACtB,SAAS,EAAE,cAAc;IACzB,kBAAkB,EAAE,OAAO;CAC5B,CAAC;AA2BF;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,OAAO;IACrB,MAAM,QAAQ,GAAG,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAC9C,MAAM,YAAY,GAAG,gBAAgB,CAAC,kBAAkB,CAAC,IAAI,SAAS,CAAC;IACvE,MAAM,IAAI,GAAG,QAAQ,EAAE,IAAI,CAAC;IAC5B,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE;QAC1B,MAAM,QAAQ,GAAG,CAAC,SAAiB,EAAE,EAAE,CACrC,OAAO,CAAC,YAAY,EAAE,YAAY,EAAE,CAAC,UAAU,SAAS,EAAE,CAAC,CAAC,CAAC;QAC/D,OAAO;YACL,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC;YAC9B,YAAY,EAAE,QAAQ,CAAC,cAAc,CAAC;YACtC,OAAO,EAAE,QAAQ,CAAC,SAAS,CAAC;YAC5B,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC;SACzB,CAAC;IACJ,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC;IAEnB,OAAO;QACL,IAAI,EACF,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,qBAAqB,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;QACxE,OAAO,EAAE,QAAQ,EAAE,OAAO;QAC1B,YAAY;QACZ,MAAM;KACP,CAAC;AACJ,CAAC","sourcesContent":["import type { McpUiHostCapabilities } from \"@modelcontextprotocol/ext-apps\";\nimport { useMemo } from \"react\";\nimport { useMcpAppContext } from \"../bridges/index.js\";\n\n/**\n * Known host applications, as normalized slugs. Unrecognized hosts surface\n * their raw `hostInfo.name` string instead.\n */\nexport type Host =\n | \"chatgpt\"\n | \"claude\"\n | \"cursor\"\n | \"goose\"\n | \"mistral-vibe\"\n | \"alpic\";\n\nconst HOST_BY_REPORTED_NAME: Record<string, Host> = {\n chatgpt: \"chatgpt\",\n Claude: \"claude\",\n Cursor: \"cursor\",\n \"MCP-UI Host\": \"goose\",\n \"Le Chat\": \"mistral-vibe\",\n \"alpic-playground\": \"alpic\",\n};\n\n/**\n * OpenAI MCP extensions the host advertises. Each flag is `true` only when the\n * host sends a truthy `hostCapabilities.experimental[\"openai/<name>\"]`, so every flag is\n * `false` before the handshake and on hosts other than ChatGPT.\n */\nexport type OpenAIHostCapabilities = {\n /** Read, subscribe to and write the file opened through a file entrypoint. */\n resource: boolean;\n /** Titles, thumbnails and background content in `ui/update-model-context`. */\n modelContext: boolean;\n /** `ui/message` targets and titled items. */\n message: boolean;\n /** Open a local file with `openai/files/open`. */\n files: boolean;\n};\n\nexport type HostInfo = {\n name: Host | (string & {}) | undefined;\n version: string | undefined;\n /** The host's MCP Apps capabilities, `undefined` until the handshake. */\n capabilities: McpUiHostCapabilities | undefined;\n /** OpenAI MCP extensions the host supports. */\n openai: OpenAIHostCapabilities;\n};\n\n/**\n * Identity and capabilities of the host application rendering the view, from\n * the MCP Apps `ui/initialize` handshake. `name` is normalized to a\n * {@link Host} slug when recognized, otherwise the raw string; `name`,\n * `version` and `capabilities` are `undefined` until the handshake resolves\n * (the view renders first and re-renders once it lands).\n *\n * @example\n * ```tsx\n * const { name, openai } = useHost();\n * if (name === \"claude\") return <ClaudeLayout />;\n * if (openai.files) return <OpenFileButton />;\n * ```\n */\nexport function useHost(): HostInfo {\n const hostInfo = useMcpAppContext(\"hostInfo\");\n const capabilities = useMcpAppContext(\"hostCapabilities\") ?? undefined;\n const name = hostInfo?.name;\n const openai = useMemo(() => {\n const supports = (extension: string) =>\n Boolean(capabilities?.experimental?.[`openai/${extension}`]);\n return {\n resource: supports(\"resource\"),\n modelContext: supports(\"modelContext\"),\n message: supports(\"message\"),\n files: supports(\"files\"),\n };\n }, [capabilities]);\n\n return {\n name:\n name !== undefined ? (HOST_BY_REPORTED_NAME[name] ?? name) : undefined,\n version: hostInfo?.version,\n capabilities,\n openai,\n };\n}\n"]}
1
+ {"version":3,"file":"use-host.js","sourceRoot":"","sources":["../../../src/web/hooks/use-host.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAChC,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAcvD,MAAM,qBAAqB,GAAyB;IAClD,OAAO,EAAE,SAAS;IAClB,MAAM,EAAE,QAAQ;IAChB,MAAM,EAAE,QAAQ;IAChB,aAAa,EAAE,OAAO;IACtB,SAAS,EAAE,cAAc;IACzB,kBAAkB,EAAE,OAAO;CAC5B,CAAC;AA2BF;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,OAAO;IACrB,MAAM,QAAQ,GAAG,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAC9C,MAAM,YAAY,GAAG,gBAAgB,CAAC,kBAAkB,CAAC,IAAI,SAAS,CAAC;IACvE,MAAM,IAAI,GAAG,QAAQ,EAAE,IAAI,CAAC;IAC5B,MAAM,kBAAkB,GAAG,OAAO,CAAC,GAAG,EAAE;QACtC,MAAM,QAAQ,GAAG,CAAC,SAAiB,EAAE,EAAE,CACrC,OAAO,CAAC,YAAY,EAAE,YAAY,EAAE,CAAC,UAAU,SAAS,EAAE,CAAC,CAAC,CAAC;QAC/D,OAAO;YACL,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC;YAC9B,YAAY,EAAE,QAAQ,CAAC,cAAc,CAAC;YACtC,OAAO,EAAE,QAAQ,CAAC,SAAS,CAAC;YAC5B,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC;SACzB,CAAC;IACJ,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC;IAEnB,OAAO;QACL,IAAI,EACF,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,qBAAqB,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;QACxE,OAAO,EAAE,QAAQ,EAAE,OAAO;QAC1B,YAAY;QACZ,kBAAkB;KACnB,CAAC;AACJ,CAAC","sourcesContent":["import type { McpUiHostCapabilities } from \"@modelcontextprotocol/ext-apps\";\nimport { useMemo } from \"react\";\nimport { useMcpAppContext } from \"../bridges/index.js\";\n\n/**\n * Known host applications, as normalized slugs. Unrecognized hosts surface\n * their raw `hostInfo.name` string instead.\n */\nexport type Host =\n | \"chatgpt\"\n | \"claude\"\n | \"cursor\"\n | \"goose\"\n | \"mistral-vibe\"\n | \"alpic\";\n\nconst HOST_BY_REPORTED_NAME: Record<string, Host> = {\n chatgpt: \"chatgpt\",\n Claude: \"claude\",\n Cursor: \"cursor\",\n \"MCP-UI Host\": \"goose\",\n \"Le Chat\": \"mistral-vibe\",\n \"alpic-playground\": \"alpic\",\n};\n\n/**\n * OpenAI MCP extensions the host advertises. Each flag is `true` only when the\n * host sends a truthy `hostCapabilities.experimental[\"openai/<name>\"]`, so every flag is\n * `false` before the handshake and on hosts other than ChatGPT.\n */\nexport type OpenAIHostCapabilities = {\n /** Read, subscribe to and write the file opened through a file entrypoint. */\n resource: boolean;\n /** Titles, thumbnails and background content in `ui/update-model-context`. */\n modelContext: boolean;\n /** `ui/message` targets and titled items. */\n message: boolean;\n /** Open a local file with `openai/files/open`. */\n files: boolean;\n};\n\nexport type HostInfo = {\n name: Host | (string & {}) | undefined;\n version: string | undefined;\n /** The host's MCP Apps capabilities, `undefined` until the handshake. */\n capabilities: McpUiHostCapabilities | undefined;\n /** OpenAI MCP extensions the host supports. */\n openaiCapabilities: OpenAIHostCapabilities;\n};\n\n/**\n * Identity and capabilities of the host application rendering the view, from\n * the MCP Apps `ui/initialize` handshake. `name` is normalized to a\n * {@link Host} slug when recognized, otherwise the raw string; `name`,\n * `version` and `capabilities` are `undefined` until the handshake resolves\n * (the view renders first and re-renders once it lands).\n *\n * @example\n * ```tsx\n * const { name, openaiCapabilities } = useHost();\n * if (name === \"claude\") return <ClaudeLayout />;\n * if (openaiCapabilities.files) return <OpenFileButton />;\n * ```\n */\nexport function useHost(): HostInfo {\n const hostInfo = useMcpAppContext(\"hostInfo\");\n const capabilities = useMcpAppContext(\"hostCapabilities\") ?? undefined;\n const name = hostInfo?.name;\n const openaiCapabilities = useMemo(() => {\n const supports = (extension: string) =>\n Boolean(capabilities?.experimental?.[`openai/${extension}`]);\n return {\n resource: supports(\"resource\"),\n modelContext: supports(\"modelContext\"),\n message: supports(\"message\"),\n files: supports(\"files\"),\n };\n }, [capabilities]);\n\n return {\n name:\n name !== undefined ? (HOST_BY_REPORTED_NAME[name] ?? name) : undefined,\n version: hostInfo?.version,\n capabilities,\n openaiCapabilities,\n };\n}\n"]}
@@ -46,18 +46,16 @@ const ModelContextSchema = z
46
46
  * @see https://docs.skybridge.tech/api-reference/use-model-context
47
47
  */
48
48
  export function useModelContext() {
49
- const { capabilities, openai } = useHost();
49
+ const { capabilities, openaiCapabilities } = useHost();
50
50
  const reported = useMcpAppContext("openai/modelContext");
51
51
  const context = useMemo(() => {
52
- if (reported === undefined) {
53
- return undefined;
54
- }
55
52
  const parsed = ModelContextSchema.safeParse(reported);
56
53
  return parsed.success ? parsed.data : undefined;
57
54
  }, [reported]);
58
55
  const update = useCallback((params) => getAdaptor().updateModelContext(params), []);
59
56
  return {
60
- supported: Boolean(capabilities?.updateModelContext) || openai.modelContext,
57
+ supported: Boolean(capabilities?.updateModelContext) ||
58
+ openaiCapabilities.modelContext,
61
59
  context,
62
60
  update,
63
61
  };
@@ -1 +1 @@
1
- {"version":3,"file":"use-model-context.js","sourceRoot":"","sources":["../../../src/web/hooks/use-model-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAC7C,OAAO,KAAK,CAAC,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAGnE,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAExC,MAAM,kBAAkB,GAAG,CAAC;KACzB,MAAM,CAAC;IACN,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3B,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC,QAAQ,EAAE;IAC/C,iBAAiB,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ,EAAE;CAChE,CAAC;KACD,QAAQ,EAAE,CAAC;AAKd;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,MAAM,UAAU,eAAe;IAK7B,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,OAAO,EAAE,CAAC;IAC3C,MAAM,QAAQ,GAAG,gBAAgB,CAAC,qBAAqB,CAAC,CAAC;IACzD,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE;QAC3B,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,MAAM,MAAM,GAAG,kBAAkB,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QACtD,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,CAAE,MAAM,CAAC,IAA0B,CAAC,CAAC,CAAC,SAAS,CAAC;IACzE,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;IACf,MAAM,MAAM,GAAG,WAAW,CACxB,CAAC,MAA0B,EAAE,EAAE,CAAC,UAAU,EAAE,CAAC,kBAAkB,CAAC,MAAM,CAAC,EACvE,EAAE,CACH,CAAC;IAEF,OAAO;QACL,SAAS,EAAE,OAAO,CAAC,YAAY,EAAE,kBAAkB,CAAC,IAAI,MAAM,CAAC,YAAY;QAC3E,OAAO;QACP,MAAM;KACP,CAAC;AACJ,CAAC","sourcesContent":["import { ContentBlockSchema } from \"@modelcontextprotocol/core\";\nimport { useCallback, useMemo } from \"react\";\nimport * as z from \"zod/v4\";\nimport { getAdaptor, useMcpAppContext } from \"../bridges/index.js\";\nimport type { OpenAIHostContext } from \"../bridges/mcp-app/types.js\";\nimport type { ModelContextParams } from \"../bridges/types.js\";\nimport { useHost } from \"./use-host.js\";\n\nconst ModelContextSchema = z\n .object({\n updateId: z.string().min(1),\n content: z.array(ContentBlockSchema).optional(),\n structuredContent: z.record(z.string(), z.unknown()).optional(),\n })\n .nullable();\n\n/** Model context the view attached, as ChatGPT reports it back. */\nexport type ModelContextState = OpenAIHostContext[\"openai/modelContext\"];\n\n/**\n * Send content to the model through the MCP Apps `ui/update-model-context`\n * request. Each `update` replaces what this hook sent before. Outside ChatGPT\n * it is merged with the {@link useViewState} state, which wins on shared\n * `structuredContent` keys.\n *\n * ChatGPT also reads the OpenAI presentation metadata, shows each block as a\n * composer attachment and reports the attached context back:\n * `_meta[\"openai/title\"]`, `_meta[\"openai/thumbnail\"]` on text blocks, and\n * `annotations.audience: [\"assistant\"]` to hide a block from the user.\n *\n * - `supported`: whether the host advertises `updateModelContext` or\n * `openai/modelContext`.\n * - `context`: ChatGPT only, the attached context; `null` once the user\n * removed it, `undefined` when the host never reported one.\n * - `update`: replaces the context and resolves with ChatGPT's `updateId`,\n * to compare with `context.updateId`. Rejects with `NotSupportedError` on\n * hosts that don't advertise model context updates.\n *\n * @example\n * ```tsx\n * const { supported, update } = useModelContext();\n * update({\n * content: [\n * {\n * type: \"text\",\n * text: \"Selected: M6 hex bolt\",\n * _meta: { \"openai/title\": \"Hex bolt\" },\n * },\n * ],\n * });\n * ```\n *\n * @see https://docs.skybridge.tech/api-reference/use-model-context\n */\nexport function useModelContext(): {\n supported: boolean;\n context: ModelContextState;\n update: (params: ModelContextParams) => Promise<string | undefined>;\n} {\n const { capabilities, openai } = useHost();\n const reported = useMcpAppContext(\"openai/modelContext\");\n const context = useMemo(() => {\n if (reported === undefined) {\n return undefined;\n }\n const parsed = ModelContextSchema.safeParse(reported);\n return parsed.success ? (parsed.data as ModelContextState) : undefined;\n }, [reported]);\n const update = useCallback(\n (params: ModelContextParams) => getAdaptor().updateModelContext(params),\n [],\n );\n\n return {\n supported: Boolean(capabilities?.updateModelContext) || openai.modelContext,\n context,\n update,\n };\n}\n"]}
1
+ {"version":3,"file":"use-model-context.js","sourceRoot":"","sources":["../../../src/web/hooks/use-model-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAC7C,OAAO,KAAK,CAAC,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAGnE,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAExC,MAAM,kBAAkB,GAAG,CAAC;KACzB,MAAM,CAAC;IACN,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3B,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC,QAAQ,EAAE;IAC/C,iBAAiB,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ,EAAE;CAChE,CAAC;KACD,QAAQ,EAAE,CAAC;AAKd;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,MAAM,UAAU,eAAe;IAK7B,MAAM,EAAE,YAAY,EAAE,kBAAkB,EAAE,GAAG,OAAO,EAAE,CAAC;IACvD,MAAM,QAAQ,GAAG,gBAAgB,CAAC,qBAAqB,CAAC,CAAC;IACzD,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE;QAC3B,MAAM,MAAM,GAAG,kBAAkB,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QACtD,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IAClD,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;IACf,MAAM,MAAM,GAAG,WAAW,CACxB,CAAC,MAA0B,EAAE,EAAE,CAAC,UAAU,EAAE,CAAC,kBAAkB,CAAC,MAAM,CAAC,EACvE,EAAE,CACH,CAAC;IAEF,OAAO;QACL,SAAS,EACP,OAAO,CAAC,YAAY,EAAE,kBAAkB,CAAC;YACzC,kBAAkB,CAAC,YAAY;QACjC,OAAO;QACP,MAAM;KACP,CAAC;AACJ,CAAC","sourcesContent":["import { ContentBlockSchema } from \"@modelcontextprotocol/core\";\nimport { useCallback, useMemo } from \"react\";\nimport * as z from \"zod/v4\";\nimport { getAdaptor, useMcpAppContext } from \"../bridges/index.js\";\nimport type { OpenAIHostContext } from \"../bridges/mcp-app/types.js\";\nimport type { ModelContextParams } from \"../bridges/types.js\";\nimport { useHost } from \"./use-host.js\";\n\nconst ModelContextSchema = z\n .object({\n updateId: z.string().min(1),\n content: z.array(ContentBlockSchema).optional(),\n structuredContent: z.record(z.string(), z.unknown()).optional(),\n })\n .nullable();\n\n/** Model context the view attached, as ChatGPT reports it back. */\nexport type ModelContextState = OpenAIHostContext[\"openai/modelContext\"];\n\n/**\n * Send content to the model through the MCP Apps `ui/update-model-context`\n * request. Each `update` replaces what this hook sent before. Outside ChatGPT\n * it is merged with the {@link useViewState} state, which wins on shared\n * `structuredContent` keys.\n *\n * ChatGPT also reads the OpenAI presentation metadata, shows each block as a\n * composer attachment and reports the attached context back:\n * `_meta[\"openai/title\"]`, `_meta[\"openai/thumbnail\"]` on text blocks, and\n * `annotations.audience: [\"assistant\"]` to hide a block from the user.\n *\n * - `supported`: whether the host advertises `updateModelContext` or\n * `openai/modelContext`.\n * - `context`: ChatGPT only, the attached context; `null` once the user\n * removed it, `undefined` when the host never reported one.\n * - `update`: replaces the context and resolves with ChatGPT's `updateId`,\n * to compare with `context.updateId`. Rejects with `NotSupportedError` on\n * hosts that don't advertise model context updates.\n *\n * @example\n * ```tsx\n * const { supported, update } = useModelContext();\n * update({\n * content: [\n * {\n * type: \"text\",\n * text: \"Selected: M6 hex bolt\",\n * _meta: { \"openai/title\": \"Hex bolt\" },\n * },\n * ],\n * });\n * ```\n *\n * @see https://docs.skybridge.tech/api-reference/use-model-context\n */\nexport function useModelContext(): {\n supported: boolean;\n context: ModelContextState;\n update: (params: ModelContextParams) => Promise<string | undefined>;\n} {\n const { capabilities, openaiCapabilities } = useHost();\n const reported = useMcpAppContext(\"openai/modelContext\");\n const context = useMemo(() => {\n const parsed = ModelContextSchema.safeParse(reported);\n return parsed.success ? parsed.data : undefined;\n }, [reported]);\n const update = useCallback(\n (params: ModelContextParams) => getAdaptor().updateModelContext(params),\n [],\n );\n\n return {\n supported:\n Boolean(capabilities?.updateModelContext) ||\n openaiCapabilities.modelContext,\n context,\n update,\n };\n}\n"]}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Ask ChatGPT to open a local file by its absolute path, through the OpenAI
3
+ * MCP extensions (`openai/files/open`). ChatGPT desktop only, with a path on
4
+ * the machine that runs ChatGPT, which shows the file in its built-in viewer
5
+ * rather than in the app's own file entrypoint. Rejects with `NotSupportedError` on
6
+ * hosts that don't advertise `openai/files`: check `useHost().openaiCapabilities.files`
7
+ * before showing the control.
8
+ *
9
+ * @example
10
+ * ```tsx
11
+ * const { openaiCapabilities } = useHost();
12
+ * const openFile = useOpenFile();
13
+ * if (openaiCapabilities.files) {
14
+ * return <button onClick={() => openFile(part.path)}>Open in ChatGPT</button>;
15
+ * }
16
+ * ```
17
+ *
18
+ * @see https://docs.skybridge.tech/api-reference/use-open-file
19
+ */
20
+ export declare function useOpenFile(): (path: string) => Promise<void>;
@@ -0,0 +1,25 @@
1
+ import { useCallback } from "react";
2
+ import { getAdaptor } from "../bridges/index.js";
3
+ /**
4
+ * Ask ChatGPT to open a local file by its absolute path, through the OpenAI
5
+ * MCP extensions (`openai/files/open`). ChatGPT desktop only, with a path on
6
+ * the machine that runs ChatGPT, which shows the file in its built-in viewer
7
+ * rather than in the app's own file entrypoint. Rejects with `NotSupportedError` on
8
+ * hosts that don't advertise `openai/files`: check `useHost().openaiCapabilities.files`
9
+ * before showing the control.
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * const { openaiCapabilities } = useHost();
14
+ * const openFile = useOpenFile();
15
+ * if (openaiCapabilities.files) {
16
+ * return <button onClick={() => openFile(part.path)}>Open in ChatGPT</button>;
17
+ * }
18
+ * ```
19
+ *
20
+ * @see https://docs.skybridge.tech/api-reference/use-open-file
21
+ */
22
+ export function useOpenFile() {
23
+ return useCallback((path) => getAdaptor().openFile(path), []);
24
+ }
25
+ //# sourceMappingURL=use-open-file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-open-file.js","sourceRoot":"","sources":["../../../src/web/hooks/use-open-file.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEjD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,WAAW;IACzB,OAAO,WAAW,CAAC,CAAC,IAAY,EAAE,EAAE,CAAC,UAAU,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;AACxE,CAAC","sourcesContent":["import { useCallback } from \"react\";\nimport { getAdaptor } from \"../bridges/index.js\";\n\n/**\n * Ask ChatGPT to open a local file by its absolute path, through the OpenAI\n * MCP extensions (`openai/files/open`). ChatGPT desktop only, with a path on\n * the machine that runs ChatGPT, which shows the file in its built-in viewer\n * rather than in the app's own file entrypoint. Rejects with `NotSupportedError` on\n * hosts that don't advertise `openai/files`: check `useHost().openaiCapabilities.files`\n * before showing the control.\n *\n * @example\n * ```tsx\n * const { openaiCapabilities } = useHost();\n * const openFile = useOpenFile();\n * if (openaiCapabilities.files) {\n * return <button onClick={() => openFile(part.path)}>Open in ChatGPT</button>;\n * }\n * ```\n *\n * @see https://docs.skybridge.tech/api-reference/use-open-file\n */\nexport function useOpenFile(): (path: string) => Promise<void> {\n return useCallback((path: string) => getAdaptor().openFile(path), []);\n}\n"]}
@@ -7,7 +7,7 @@ import type { ViewToolConfig, ViewToolHandler, ViewToolInputShape } from "../bri
7
7
  * {@link useCallTool} (which calls a server tool). Registered on mount, removed
8
8
  * on unmount; re-registered when `config.name` changes.
9
9
  *
10
- * MCP Apps only — on the Apps SDK (`window.openai`) runtime it is a no-op.
10
+ * MCP Apps only — on the ChatGPT (`window.openai`) runtime it is a no-op.
11
11
  *
12
12
  * @example
13
13
  * ```tsx
@@ -8,7 +8,7 @@ import { getAdaptor } from "../bridges/index.js";
8
8
  * {@link useCallTool} (which calls a server tool). Registered on mount, removed
9
9
  * on unmount; re-registered when `config.name` changes.
10
10
  *
11
- * MCP Apps only — on the Apps SDK (`window.openai`) runtime it is a no-op.
11
+ * MCP Apps only — on the ChatGPT (`window.openai`) runtime it is a no-op.
12
12
  *
13
13
  * @example
14
14
  * ```tsx
@@ -1 +1 @@
1
- {"version":3,"file":"use-register-view-tool.js","sourceRoot":"","sources":["../../../src/web/hooks/use-register-view-tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAQjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAGjC,MAA8B,EAC9B,OAAgC,EAChC,EAAE;IACF,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC;IACxB,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IACjC,SAAS,CAAC,OAAO,GAAG,MAAM,CAAC;IAC3B,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IACnC,UAAU,CAAC,OAAO,GAAG,OAAO,CAAC;IAE7B,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;QAC7B,MAAM,cAAc,GAAuB,CAAC,IAAI,EAAE,EAAE,CAClD,UAAU,CAAC,OAAO,CAAC,IAA8C,CAAC,CAAC;QAErE,OAAO,OAAO,CAAC,gBAAgB,CAC7B,EAAE,GAAG,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,EAC9B,cAAc,CACf,CAAC;IACJ,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;AACb,CAAC,CAAC","sourcesContent":["import { useEffect, useRef } from \"react\";\nimport { getAdaptor } from \"../bridges/index.js\";\nimport type {\n AnyViewToolHandler,\n ViewToolConfig,\n ViewToolHandler,\n ViewToolInputShape,\n} from \"../bridges/types.js\";\n\n/**\n * Register a tool the view exposes to the host and model — the MCP Apps\n * \"app-provided tools\" feature. A view tool runs *inside the view*: the host\n * discovers it via `tools/list` and invokes it via `tools/call`, and the\n * handler executes against the view's live state. It is the inverse of\n * {@link useCallTool} (which calls a server tool). Registered on mount, removed\n * on unmount; re-registered when `config.name` changes.\n *\n * MCP Apps only — on the Apps SDK (`window.openai`) runtime it is a no-op.\n *\n * @example\n * ```tsx\n * import * as z from \"zod\";\n * import { useRegisterViewTool } from \"skybridge/web\";\n *\n * useRegisterViewTool(\n * {\n * name: \"chess_make_move\",\n * description: \"Play a move in algebraic notation, e.g. 'e4' or 'Nf3'.\",\n * inputSchema: { san: z.string() },\n * annotations: { readOnlyHint: false },\n * },\n * ({ san }) => {\n * const move = game.move(san);\n * return {\n * content: [{ type: \"text\", text: move ? `Played ${move.san}` : \"Illegal move\" }],\n * structuredContent: { fen: game.fen() },\n * isError: !move,\n * };\n * },\n * );\n * ```\n *\n * @see https://docs.skybridge.tech/api-reference/use-register-view-tool\n */\nexport const useRegisterViewTool = <\n TInput extends ViewToolInputShape = ViewToolInputShape,\n>(\n config: ViewToolConfig<TInput>,\n handler: ViewToolHandler<TInput>,\n) => {\n const { name } = config;\n const configRef = useRef(config);\n configRef.current = config;\n const handlerRef = useRef(handler);\n handlerRef.current = handler;\n\n useEffect(() => {\n const adaptor = getAdaptor();\n const wrappedHandler: AnyViewToolHandler = (args) =>\n handlerRef.current(args as Parameters<ViewToolHandler<TInput>>[0]);\n\n return adaptor.registerViewTool(\n { ...configRef.current, name },\n wrappedHandler,\n );\n }, [name]);\n};\n"]}
1
+ {"version":3,"file":"use-register-view-tool.js","sourceRoot":"","sources":["../../../src/web/hooks/use-register-view-tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAQjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAGjC,MAA8B,EAC9B,OAAgC,EAChC,EAAE;IACF,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC;IACxB,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IACjC,SAAS,CAAC,OAAO,GAAG,MAAM,CAAC;IAC3B,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IACnC,UAAU,CAAC,OAAO,GAAG,OAAO,CAAC;IAE7B,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;QAC7B,MAAM,cAAc,GAAuB,CAAC,IAAI,EAAE,EAAE,CAClD,UAAU,CAAC,OAAO,CAAC,IAA8C,CAAC,CAAC;QAErE,OAAO,OAAO,CAAC,gBAAgB,CAC7B,EAAE,GAAG,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,EAC9B,cAAc,CACf,CAAC;IACJ,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;AACb,CAAC,CAAC","sourcesContent":["import { useEffect, useRef } from \"react\";\nimport { getAdaptor } from \"../bridges/index.js\";\nimport type {\n AnyViewToolHandler,\n ViewToolConfig,\n ViewToolHandler,\n ViewToolInputShape,\n} from \"../bridges/types.js\";\n\n/**\n * Register a tool the view exposes to the host and model — the MCP Apps\n * \"app-provided tools\" feature. A view tool runs *inside the view*: the host\n * discovers it via `tools/list` and invokes it via `tools/call`, and the\n * handler executes against the view's live state. It is the inverse of\n * {@link useCallTool} (which calls a server tool). Registered on mount, removed\n * on unmount; re-registered when `config.name` changes.\n *\n * MCP Apps only — on the ChatGPT (`window.openai`) runtime it is a no-op.\n *\n * @example\n * ```tsx\n * import * as z from \"zod\";\n * import { useRegisterViewTool } from \"skybridge/web\";\n *\n * useRegisterViewTool(\n * {\n * name: \"chess_make_move\",\n * description: \"Play a move in algebraic notation, e.g. 'e4' or 'Nf3'.\",\n * inputSchema: { san: z.string() },\n * annotations: { readOnlyHint: false },\n * },\n * ({ san }) => {\n * const move = game.move(san);\n * return {\n * content: [{ type: \"text\", text: move ? `Played ${move.san}` : \"Illegal move\" }],\n * structuredContent: { fen: game.fen() },\n * isError: !move,\n * };\n * },\n * );\n * ```\n *\n * @see https://docs.skybridge.tech/api-reference/use-register-view-tool\n */\nexport const useRegisterViewTool = <\n TInput extends ViewToolInputShape = ViewToolInputShape,\n>(\n config: ViewToolConfig<TInput>,\n handler: ViewToolHandler<TInput>,\n) => {\n const { name } = config;\n const configRef = useRef(config);\n configRef.current = config;\n const handlerRef = useRef(handler);\n handlerRef.current = handler;\n\n useEffect(() => {\n const adaptor = getAdaptor();\n const wrappedHandler: AnyViewToolHandler = (args) =>\n handlerRef.current(args as Parameters<ViewToolHandler<TInput>>[0]);\n\n return adaptor.registerViewTool(\n { ...configRef.current, name },\n wrappedHandler,\n );\n }, [name]);\n};\n"]}
@@ -1,4 +1,4 @@
1
- import { type SendFollowUpMessageOptions } from "../bridges/index.js";
1
+ import { type FollowUpMessage, type SendFollowUpMessageOptions } from "../bridges/index.js";
2
2
  /**
3
3
  * Send a follow-up message to the LLM on behalf of the view, as if the user
4
4
  * had sent it. Use to chain interactions from view UI (e.g. a button that
@@ -8,6 +8,12 @@ import { type SendFollowUpMessageOptions } from "../bridges/index.js";
8
8
  * posts the message. This option is Apps-SDK-only; it is silently ignored in
9
9
  * the MCP Apps runtime.
10
10
  *
11
+ * `prompt` can also be an array of content blocks. On ChatGPT, a text block
12
+ * with `_meta["openai/title"]` becomes a labeled item the user can remove, and
13
+ * `target: "new"` sends the message to a new conversation. Both use the OpenAI
14
+ * MCP extensions: other hosts ignore the title, and `target: "new"` rejects
15
+ * with `NotSupportedError` on hosts that don't advertise `openai/message`.
16
+ *
11
17
  * @example
12
18
  * ```tsx
13
19
  * const send = useSendFollowUpMessage();
@@ -16,4 +22,4 @@ import { type SendFollowUpMessageOptions } from "../bridges/index.js";
16
22
  *
17
23
  * @see https://docs.skybridge.tech/api-reference/use-send-follow-up-message
18
24
  */
19
- export declare function useSendFollowUpMessage(): (prompt: string, options?: SendFollowUpMessageOptions) => Promise<void>;
25
+ export declare function useSendFollowUpMessage(): (prompt: FollowUpMessage, options?: SendFollowUpMessageOptions) => Promise<void>;
@@ -9,6 +9,12 @@ import { getAdaptor, } from "../bridges/index.js";
9
9
  * posts the message. This option is Apps-SDK-only; it is silently ignored in
10
10
  * the MCP Apps runtime.
11
11
  *
12
+ * `prompt` can also be an array of content blocks. On ChatGPT, a text block
13
+ * with `_meta["openai/title"]` becomes a labeled item the user can remove, and
14
+ * `target: "new"` sends the message to a new conversation. Both use the OpenAI
15
+ * MCP extensions: other hosts ignore the title, and `target: "new"` rejects
16
+ * with `NotSupportedError` on hosts that don't advertise `openai/message`.
17
+ *
12
18
  * @example
13
19
  * ```tsx
14
20
  * const send = useSendFollowUpMessage();
@@ -1 +1 @@
1
- {"version":3,"file":"use-send-follow-up-message.js","sourceRoot":"","sources":["../../../src/web/hooks/use-send-follow-up-message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AACpC,OAAO,EACL,UAAU,GAEX,MAAM,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,sBAAsB;IACpC,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,MAAM,mBAAmB,GAAG,WAAW,CACrC,CAAC,MAAc,EAAE,OAAoC,EAAE,EAAE,CACvD,OAAO,CAAC,mBAAmB,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9C,CAAC,OAAO,CAAC,CACV,CAAC;IAEF,OAAO,mBAAmB,CAAC;AAC7B,CAAC","sourcesContent":["import { useCallback } from \"react\";\nimport {\n getAdaptor,\n type SendFollowUpMessageOptions,\n} from \"../bridges/index.js\";\n\n/**\n * Send a follow-up message to the LLM on behalf of the view, as if the user\n * had sent it. Use to chain interactions from view UI (e.g. a button that\n * triggers the next assistant turn).\n *\n * Pass `scrollToBottom: false` to keep the chat scroll position when the host\n * posts the message. This option is Apps-SDK-only; it is silently ignored in\n * the MCP Apps runtime.\n *\n * @example\n * ```tsx\n * const send = useSendFollowUpMessage();\n * <button onClick={() => send(\"Summarize the last 5 results\")}>Summarize</button>\n * ```\n *\n * @see https://docs.skybridge.tech/api-reference/use-send-follow-up-message\n */\nexport function useSendFollowUpMessage() {\n const adaptor = getAdaptor();\n const sendFollowUpMessage = useCallback(\n (prompt: string, options?: SendFollowUpMessageOptions) =>\n adaptor.sendFollowUpMessage(prompt, options),\n [adaptor],\n );\n\n return sendFollowUpMessage;\n}\n"]}
1
+ {"version":3,"file":"use-send-follow-up-message.js","sourceRoot":"","sources":["../../../src/web/hooks/use-send-follow-up-message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AACpC,OAAO,EAEL,UAAU,GAEX,MAAM,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,sBAAsB;IACpC,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,MAAM,mBAAmB,GAAG,WAAW,CACrC,CAAC,MAAuB,EAAE,OAAoC,EAAE,EAAE,CAChE,OAAO,CAAC,mBAAmB,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9C,CAAC,OAAO,CAAC,CACV,CAAC;IAEF,OAAO,mBAAmB,CAAC;AAC7B,CAAC","sourcesContent":["import { useCallback } from \"react\";\nimport {\n type FollowUpMessage,\n getAdaptor,\n type SendFollowUpMessageOptions,\n} from \"../bridges/index.js\";\n\n/**\n * Send a follow-up message to the LLM on behalf of the view, as if the user\n * had sent it. Use to chain interactions from view UI (e.g. a button that\n * triggers the next assistant turn).\n *\n * Pass `scrollToBottom: false` to keep the chat scroll position when the host\n * posts the message. This option is Apps-SDK-only; it is silently ignored in\n * the MCP Apps runtime.\n *\n * `prompt` can also be an array of content blocks. On ChatGPT, a text block\n * with `_meta[\"openai/title\"]` becomes a labeled item the user can remove, and\n * `target: \"new\"` sends the message to a new conversation. Both use the OpenAI\n * MCP extensions: other hosts ignore the title, and `target: \"new\"` rejects\n * with `NotSupportedError` on hosts that don't advertise `openai/message`.\n *\n * @example\n * ```tsx\n * const send = useSendFollowUpMessage();\n * <button onClick={() => send(\"Summarize the last 5 results\")}>Summarize</button>\n * ```\n *\n * @see https://docs.skybridge.tech/api-reference/use-send-follow-up-message\n */\nexport function useSendFollowUpMessage() {\n const adaptor = getAdaptor();\n const sendFollowUpMessage = useCallback(\n (prompt: FollowUpMessage, options?: SendFollowUpMessageOptions) =>\n adaptor.sendFollowUpMessage(prompt, options),\n [adaptor],\n );\n\n return sendFollowUpMessage;\n}\n"]}
@@ -1,6 +1,7 @@
1
1
  /// <reference types="vite/client" />
2
2
  import { createElement, StrictMode } from "react";
3
3
  import { createRoot } from "react-dom/client";
4
+ import { ModalProvider } from "./components/modal-provider.js";
4
5
  import { installOpenAILoggingProxy } from "./proxy.js";
5
6
  let rootInstance = null;
6
7
  /**
@@ -34,10 +35,7 @@ export const mountView = (component) => {
34
35
  if (import.meta.env.DEV) {
35
36
  installOpenAILoggingProxy();
36
37
  }
37
- (async () => {
38
- const { ModalProvider } = await import("./components/modal-provider.js");
39
- const app = createElement(ModalProvider, null, component);
40
- rootInstance.render(createElement(StrictMode, null, app));
41
- })();
38
+ const app = createElement(ModalProvider, null, component);
39
+ rootInstance.render(createElement(StrictMode, null, app));
42
40
  };
43
41
  //# sourceMappingURL=mount-view.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"mount-view.js","sourceRoot":"","sources":["../../src/web/mount-view.ts"],"names":[],"mappings":"AAAA,qCAAqC;AAErC,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAClD,OAAO,EAAE,UAAU,EAAa,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAEvD,IAAI,YAAY,GAAgB,IAAI,CAAC;AAErC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,SAA0B,EAAE,EAAE;IACtD,MAAM,WAAW,GAAG,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;IACpD,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;IAC5C,CAAC;IAED,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,YAAY,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC;IACzC,CAAC;IAED,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC;QACxB,yBAAyB,EAAE,CAAC;IAC9B,CAAC;IAED,CAAC,KAAK,IAAI,EAAE;QACV,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,MAAM,CAAC,gCAAgC,CAAC,CAAC;QACzE,MAAM,GAAG,GAAG,aAAa,CAAC,aAAa,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;QAC1D,YAAY,CAAC,MAAM,CAAC,aAAa,CAAC,UAAU,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;IAC5D,CAAC,CAAC,EAAE,CAAC;AACP,CAAC,CAAC","sourcesContent":["/// <reference types=\"vite/client\" />\n\nimport { createElement, StrictMode } from \"react\";\nimport { createRoot, type Root } from \"react-dom/client\";\nimport { installOpenAILoggingProxy } from \"./proxy.js\";\n\nlet rootInstance: Root | null = null;\n\n/**\n * Mount a view's root React component into `#root`. The Vite plugin\n * auto-mounts every view, so apps normally never call this themselves —\n * it is the entry point the generated view wrappers use.\n *\n * Wraps the component in `StrictMode`, applies host-specific providers\n * automatically (e.g. modal support for MCP Apps), and installs the dev-mode\n * logging proxy for `window.openai` calls.\n *\n * @param component - Your root React element (already constructed, e.g. `<App />`).\n *\n * @example\n * ```tsx\n * // src/views/search.tsx\n * import { mountView } from \"skybridge/web\";\n * import { App } from \"./App\";\n *\n * mountView(<App />);\n * ```\n */\nexport const mountView = (component: React.ReactNode) => {\n const rootElement = document.getElementById(\"root\");\n if (!rootElement) {\n throw new Error(\"Root element not found\");\n }\n\n if (!rootInstance) {\n rootInstance = createRoot(rootElement);\n }\n\n if (import.meta.env.DEV) {\n installOpenAILoggingProxy();\n }\n\n (async () => {\n const { ModalProvider } = await import(\"./components/modal-provider.js\");\n const app = createElement(ModalProvider, null, component);\n rootInstance.render(createElement(StrictMode, null, app));\n })();\n};\n"]}
1
+ {"version":3,"file":"mount-view.js","sourceRoot":"","sources":["../../src/web/mount-view.ts"],"names":[],"mappings":"AAAA,qCAAqC;AAErC,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAClD,OAAO,EAAE,UAAU,EAAa,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAC/D,OAAO,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAEvD,IAAI,YAAY,GAAgB,IAAI,CAAC;AAErC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,SAA0B,EAAE,EAAE;IACtD,MAAM,WAAW,GAAG,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;IACpD,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;IAC5C,CAAC;IAED,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,YAAY,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC;IACzC,CAAC;IAED,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC;QACxB,yBAAyB,EAAE,CAAC;IAC9B,CAAC;IAED,MAAM,GAAG,GAAG,aAAa,CAAC,aAAa,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;IAC1D,YAAY,CAAC,MAAM,CAAC,aAAa,CAAC,UAAU,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;AAC5D,CAAC,CAAC","sourcesContent":["/// <reference types=\"vite/client\" />\n\nimport { createElement, StrictMode } from \"react\";\nimport { createRoot, type Root } from \"react-dom/client\";\nimport { ModalProvider } from \"./components/modal-provider.js\";\nimport { installOpenAILoggingProxy } from \"./proxy.js\";\n\nlet rootInstance: Root | null = null;\n\n/**\n * Mount a view's root React component into `#root`. The Vite plugin\n * auto-mounts every view, so apps normally never call this themselves —\n * it is the entry point the generated view wrappers use.\n *\n * Wraps the component in `StrictMode`, applies host-specific providers\n * automatically (e.g. modal support for MCP Apps), and installs the dev-mode\n * logging proxy for `window.openai` calls.\n *\n * @param component - Your root React element (already constructed, e.g. `<App />`).\n *\n * @example\n * ```tsx\n * // src/views/search.tsx\n * import { mountView } from \"skybridge/web\";\n * import { App } from \"./App\";\n *\n * mountView(<App />);\n * ```\n */\nexport const mountView = (component: React.ReactNode) => {\n const rootElement = document.getElementById(\"root\");\n if (!rootElement) {\n throw new Error(\"Root element not found\");\n }\n\n if (!rootInstance) {\n rootInstance = createRoot(rootElement);\n }\n\n if (import.meta.env.DEV) {\n installOpenAILoggingProxy();\n }\n\n const app = createElement(ModalProvider, null, component);\n rootInstance.render(createElement(StrictMode, null, app));\n};\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "skybridge",
3
- "version": "0.0.0-dev.f57f202",
4
- "description": "Skybridge is a framework for building ChatGPT and MCP Apps",
3
+ "version": "0.0.0-dev.f58ff90",
4
+ "description": "Skybridge is a framework for building ChatGPT plugins and MCP Apps",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/alpic-ai/skybridge.git"
@@ -67,6 +67,8 @@
67
67
  "jose": "^6.2.3",
68
68
  "open": "^11.0.0",
69
69
  "posthog-node": "^5.45.2",
70
+ "request-filtering-agent": "^3.2.1",
71
+ "standardwebhooks": "^1.1.1",
70
72
  "std-env": "^4.2.0",
71
73
  "superjson": "^2.2.6",
72
74
  "yaml": "^2.9.0",