@ethisyscore/extension-runtime 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +42 -0
  2. package/bin/mock-host.cjs +14 -0
  3. package/dist/host/index.cjs +600 -0
  4. package/dist/host/index.cjs.map +1 -0
  5. package/dist/host/index.d.cts +260 -0
  6. package/dist/host/index.d.ts +260 -0
  7. package/dist/host/index.js +577 -0
  8. package/dist/host/index.js.map +1 -0
  9. package/dist/index.cjs +16 -0
  10. package/dist/index.cjs.map +1 -0
  11. package/dist/index.d.cts +6 -0
  12. package/dist/index.d.ts +6 -0
  13. package/dist/index.js +10 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/mock-host/cli.cjs +800 -0
  16. package/dist/mock-host/cli.cjs.map +1 -0
  17. package/dist/mock-host/cli.d.cts +155 -0
  18. package/dist/mock-host/cli.d.ts +155 -0
  19. package/dist/mock-host/cli.js +770 -0
  20. package/dist/mock-host/cli.js.map +1 -0
  21. package/dist/mock-host/index.cjs +74 -0
  22. package/dist/mock-host/index.cjs.map +1 -0
  23. package/dist/mock-host/index.d.cts +95 -0
  24. package/dist/mock-host/index.d.ts +95 -0
  25. package/dist/mock-host/index.js +71 -0
  26. package/dist/mock-host/index.js.map +1 -0
  27. package/dist/plugin/index.cjs +113 -0
  28. package/dist/plugin/index.cjs.map +1 -0
  29. package/dist/plugin/index.d.cts +120 -0
  30. package/dist/plugin/index.d.ts +120 -0
  31. package/dist/plugin/index.js +107 -0
  32. package/dist/plugin/index.js.map +1 -0
  33. package/dist/registry-DpCx_LxF.d.cts +25 -0
  34. package/dist/registry-DpCx_LxF.d.ts +25 -0
  35. package/dist/transport-73otePiw.d.cts +307 -0
  36. package/dist/transport-73otePiw.d.ts +307 -0
  37. package/dist/transport-DVn2GVZh.d.cts +32 -0
  38. package/dist/transport-DVn2GVZh.d.ts +32 -0
  39. package/package.json +78 -0
@@ -0,0 +1,113 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+
6
+ // src/plugin/ExtensionRuntimeProvider.tsx
7
+ var ExtensionRuntimeContext = react.createContext(null);
8
+ function ExtensionRuntimeProvider({ transport, children }) {
9
+ const value = react.useMemo(() => transport, [transport]);
10
+ return /* @__PURE__ */ jsxRuntime.jsx(ExtensionRuntimeContext.Provider, { value, children });
11
+ }
12
+ function useExtensionRuntimeTransport(override) {
13
+ const fromContext = react.useContext(ExtensionRuntimeContext);
14
+ const resolved = override ?? fromContext;
15
+ if (!resolved) {
16
+ throw new Error(
17
+ "No McpTransport available. Wrap your plugin in <ExtensionRuntimeProvider transport={...}> or pass `transport` directly to the hook."
18
+ );
19
+ }
20
+ return resolved;
21
+ }
22
+ function useMcpResource(uri, opts) {
23
+ const transport = useExtensionRuntimeTransport(opts?.transport);
24
+ const [data, setData] = react.useState(void 0);
25
+ const [error, setError] = react.useState(void 0);
26
+ const [loading, setLoading] = react.useState(true);
27
+ const [refetchTick, setRefetchTick] = react.useState(0);
28
+ const controllerRef = react.useRef(null);
29
+ react.useEffect(() => {
30
+ const controller = new AbortController();
31
+ controllerRef.current?.abort();
32
+ controllerRef.current = controller;
33
+ let cancelled = false;
34
+ setLoading(true);
35
+ setError(void 0);
36
+ transport.getResource(uri, controller.signal).then((result) => {
37
+ if (cancelled || controller.signal.aborted) {
38
+ return;
39
+ }
40
+ setData(result.data);
41
+ setLoading(false);
42
+ }).catch((err) => {
43
+ if (cancelled || controller.signal.aborted) {
44
+ return;
45
+ }
46
+ setError(err instanceof Error ? err : new Error(String(err)));
47
+ setLoading(false);
48
+ });
49
+ return () => {
50
+ cancelled = true;
51
+ controller.abort();
52
+ };
53
+ }, [uri, transport, refetchTick]);
54
+ const refetch = react.useCallback(() => {
55
+ setRefetchTick((tick) => tick + 1);
56
+ }, []);
57
+ return { data, error, loading, refetch };
58
+ }
59
+ function useMcpTool(toolName, opts) {
60
+ const transport = useExtensionRuntimeTransport(opts?.transport);
61
+ const [loading, setLoading] = react.useState(false);
62
+ const [error, setError] = react.useState(void 0);
63
+ const controllerRef = react.useRef(null);
64
+ const mountedRef = react.useRef(true);
65
+ react.useEffect(() => {
66
+ mountedRef.current = true;
67
+ return () => {
68
+ mountedRef.current = false;
69
+ controllerRef.current?.abort();
70
+ };
71
+ }, []);
72
+ const invoke = react.useCallback(
73
+ async (req) => {
74
+ const controller = new AbortController();
75
+ controllerRef.current?.abort();
76
+ controllerRef.current = controller;
77
+ setLoading(true);
78
+ setError(void 0);
79
+ try {
80
+ const result = await transport.invokeTool(
81
+ toolName,
82
+ req,
83
+ controller.signal
84
+ );
85
+ if (mountedRef.current && !controller.signal.aborted) {
86
+ setLoading(false);
87
+ }
88
+ return result;
89
+ } catch (err) {
90
+ const wrapped = err instanceof Error ? err : new Error(String(err));
91
+ if (mountedRef.current && !controller.signal.aborted) {
92
+ setError(wrapped);
93
+ setLoading(false);
94
+ }
95
+ throw wrapped;
96
+ }
97
+ },
98
+ [transport, toolName]
99
+ );
100
+ return { invoke, loading, error };
101
+ }
102
+
103
+ // src/plugin/define.ts
104
+ var defineDeclarativePlugin = (cfg) => cfg;
105
+ var defineEthisysPlugin = (cfg) => cfg;
106
+
107
+ exports.ExtensionRuntimeProvider = ExtensionRuntimeProvider;
108
+ exports.defineDeclarativePlugin = defineDeclarativePlugin;
109
+ exports.defineEthisysPlugin = defineEthisysPlugin;
110
+ exports.useMcpResource = useMcpResource;
111
+ exports.useMcpTool = useMcpTool;
112
+ //# sourceMappingURL=index.cjs.map
113
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/plugin/ExtensionRuntimeProvider.tsx","../../src/plugin/useMcpResource.ts","../../src/plugin/useMcpTool.ts","../../src/plugin/define.ts"],"names":["createContext","useMemo","jsx","useContext","useState","useRef","useEffect","useCallback"],"mappings":"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","file":"index.cjs","sourcesContent":["import { createContext, useContext, useMemo, type ReactNode } from \"react\";\nimport type { McpTransport } from \"./transport\";\n\n/**\n * React context carrying the {@link McpTransport} the plugin should use to\n * reach the host. `null` is the explicit \"not provided\" sentinel so the hooks\n * can disambiguate from a transport that was provided but is incidentally\n * falsy in some other dimension.\n */\nconst ExtensionRuntimeContext = createContext<McpTransport | null>(null);\n\nexport interface ExtensionRuntimeProviderProps\n{\n transport: McpTransport;\n children?: ReactNode;\n}\n\n/**\n * Wrap a plugin's React tree so descendant {@link useMcpResource} and\n * {@link useMcpTool} calls resolve a default transport without having to\n * thread it through every component.\n *\n * Hooks still accept a per-call `transport` override, which takes precedence\n * over the context value — useful for tests and for plugins that want to\n * shard work across multiple hosts.\n */\nexport function ExtensionRuntimeProvider({ transport, children }: ExtensionRuntimeProviderProps): ReactNode\n{\n // Memoise so swapping `children` doesn't churn the context identity.\n const value = useMemo(() => transport, [transport]);\n return (\n <ExtensionRuntimeContext.Provider value={value}>\n {children}\n </ExtensionRuntimeContext.Provider>\n );\n}\n\n/**\n * Internal helper used by the hooks. Returns the explicit override when\n * supplied, otherwise falls back to the context. Throws a deterministic\n * error if neither is available so misconfiguration fails loudly at the\n * first render rather than producing silent no-ops.\n */\nexport function useExtensionRuntimeTransport(override?: McpTransport): McpTransport\n{\n const fromContext = useContext(ExtensionRuntimeContext);\n const resolved = override ?? fromContext;\n if (!resolved)\n {\n throw new Error(\n \"No McpTransport available. Wrap your plugin in <ExtensionRuntimeProvider transport={...}> \"\n + \"or pass `transport` directly to the hook.\",\n );\n }\n return resolved;\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useExtensionRuntimeTransport } from \"./ExtensionRuntimeProvider\";\nimport type { McpTransport } from \"./transport\";\n\nexport interface UseMcpResourceOptions\n{\n /**\n * Override the transport resolved from {@link ExtensionRuntimeProvider}.\n * Primarily intended for tests and advanced multi-host scenarios.\n */\n transport?: McpTransport;\n}\n\nexport interface UseMcpResourceResult<T>\n{\n data: T | undefined;\n error: Error | undefined;\n loading: boolean;\n /** Re-run the fetch against the current URI. Stable across renders. */\n refetch: () => void;\n}\n\n/**\n * Subscribe to an MCP resource by URI.\n *\n * The hook fetches via the resolved {@link McpTransport} on mount and whenever\n * the URI changes. Each fetch is cancellable: when the component unmounts or\n * the URI changes, the in-flight call is aborted via an {@link AbortController}\n * so stale responses cannot overwrite later state.\n *\n * `refetch` re-runs the fetch against the latest URI. The returned callback is\n * stable across renders so it is safe to include in `useEffect` dependency\n * arrays.\n */\nexport function useMcpResource<T>(\n uri: string,\n opts?: UseMcpResourceOptions,\n): UseMcpResourceResult<T>\n{\n const transport = useExtensionRuntimeTransport(opts?.transport);\n\n const [data, setData] = useState<T | undefined>(undefined);\n const [error, setError] = useState<Error | undefined>(undefined);\n const [loading, setLoading] = useState<boolean>(true);\n\n // Bump this counter to force a re-fetch from `refetch()`.\n const [refetchTick, setRefetchTick] = useState(0);\n\n // Track the active controller so `refetch` and unmount can abort it.\n const controllerRef = useRef<AbortController | null>(null);\n\n useEffect(() =>\n {\n const controller = new AbortController();\n controllerRef.current?.abort();\n controllerRef.current = controller;\n\n let cancelled = false;\n setLoading(true);\n setError(undefined);\n\n transport\n .getResource<T>(uri, controller.signal)\n .then((result) =>\n {\n if (cancelled || controller.signal.aborted)\n {\n return;\n }\n setData(result.data);\n setLoading(false);\n })\n .catch((err: unknown) =>\n {\n if (cancelled || controller.signal.aborted)\n {\n return;\n }\n setError(err instanceof Error ? err : new Error(String(err)));\n setLoading(false);\n });\n\n return () =>\n {\n cancelled = true;\n controller.abort();\n };\n }, [uri, transport, refetchTick]);\n\n const refetch = useCallback(() =>\n {\n setRefetchTick((tick) => tick + 1);\n }, []);\n\n return { data, error, loading, refetch };\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useExtensionRuntimeTransport } from \"./ExtensionRuntimeProvider\";\nimport type { McpTransport } from \"./transport\";\n\nexport interface UseMcpToolOptions\n{\n /**\n * Override the transport resolved from {@link ExtensionRuntimeProvider}.\n * Primarily intended for tests and advanced multi-host scenarios.\n */\n transport?: McpTransport;\n}\n\nexport interface UseMcpToolResult<TReq, TRes>\n{\n /**\n * Invoke the tool. Each call gets a fresh {@link AbortController} that is\n * aborted on unmount so unresolved promises cannot keep state alive after\n * the component is gone.\n */\n invoke: (req: TReq) => Promise<TRes>;\n loading: boolean;\n error: Error | undefined;\n}\n\n/**\n * Hook returning a callback that invokes an MCP tool by name through the\n * resolved {@link McpTransport}.\n *\n * `loading` and `error` track the most recent in-flight invocation. Callers\n * may also `await` the returned promise directly — failures are both rejected\n * to the caller AND surfaced via `error` so component-level UI can react.\n *\n * `invoke` is a stable reference across renders for the same `toolName` and\n * transport, making it safe to use inside `useEffect`/`useCallback` deps.\n */\nexport function useMcpTool<TReq, TRes>(\n toolName: string,\n opts?: UseMcpToolOptions,\n): UseMcpToolResult<TReq, TRes>\n{\n const transport = useExtensionRuntimeTransport(opts?.transport);\n\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | undefined>(undefined);\n\n // Track the latest controller so unmount can abort an in-flight call.\n const controllerRef = useRef<AbortController | null>(null);\n const mountedRef = useRef(true);\n\n useEffect(() =>\n {\n mountedRef.current = true;\n return () =>\n {\n mountedRef.current = false;\n controllerRef.current?.abort();\n };\n }, []);\n\n const invoke = useCallback(\n async (req: TReq): Promise<TRes> =>\n {\n const controller = new AbortController();\n controllerRef.current?.abort();\n controllerRef.current = controller;\n\n setLoading(true);\n setError(undefined);\n\n try\n {\n const result = await transport.invokeTool<TReq, TRes>(\n toolName,\n req,\n controller.signal,\n );\n if (mountedRef.current && !controller.signal.aborted)\n {\n setLoading(false);\n }\n return result;\n }\n catch (err)\n {\n const wrapped = err instanceof Error ? err : new Error(String(err));\n if (mountedRef.current && !controller.signal.aborted)\n {\n setError(wrapped);\n setLoading(false);\n }\n throw wrapped;\n }\n },\n [transport, toolName],\n );\n\n return { invoke, loading, error };\n}\n","import type { RenderMode, SduiNode } from \"@ethisyscore/protocol\";\n\n/**\n * Configuration accepted by {@link defineDeclarativePlugin}.\n *\n * A Contract A (host-rendered) plugin contributes a map of resource URIs to\n * declarative SDUI trees. The host fetches a resource by URI and renders it\n * against the v1 vocabulary.\n */\nexport interface DeclarativePluginConfig\n{\n resources: Record<string, SduiNode>;\n}\n\n/**\n * Configuration accepted by {@link defineEthisysPlugin}.\n *\n * `renderMode` is constrained to the protocol's {@link RenderMode} enum so\n * authoring mistakes (`\"iframe\"`, `\"webview\"`, …) are caught at compile time.\n * `mount` is generic so Contract B remote-runtime authors can attach their\n * own mount surface without losing type information at the call site.\n */\nexport interface EthisysPluginConfig<TMount>\n{\n renderMode: RenderMode;\n mount?: TMount;\n}\n\n/**\n * Author-facing identity helper for a Contract A (host-rendered) declarative\n * plugin definition.\n *\n * The helper performs no runtime work — it exists purely so authoring sites\n * receive precise type inference and editor tooling against the protocol's\n * {@link SduiNode} contract. The returned value is the exact same reference\n * the caller passed in.\n */\nexport const defineDeclarativePlugin = (\n cfg: DeclarativePluginConfig,\n): DeclarativePluginConfig => cfg;\n\n/**\n * Author-facing identity helper for a generic EthisysCore plugin definition.\n *\n * The helper performs no runtime work. It constrains `renderMode` to the\n * protocol's {@link RenderMode} enum and preserves the inferred type of an\n * optional `mount` surface, so Contract B authors can pass through their own\n * mount object without widening it to `unknown`.\n */\nexport const defineEthisysPlugin = <TMount>(\n cfg: EthisysPluginConfig<TMount>,\n): EthisysPluginConfig<TMount> => cfg;\n"]}
@@ -0,0 +1,120 @@
1
+ import { M as McpTransport } from '../transport-DVn2GVZh.cjs';
2
+ import { ReactNode } from 'react';
3
+ import { SduiNode, RenderMode } from '@ethisyscore/protocol';
4
+
5
+ interface ExtensionRuntimeProviderProps {
6
+ transport: McpTransport;
7
+ children?: ReactNode;
8
+ }
9
+ /**
10
+ * Wrap a plugin's React tree so descendant {@link useMcpResource} and
11
+ * {@link useMcpTool} calls resolve a default transport without having to
12
+ * thread it through every component.
13
+ *
14
+ * Hooks still accept a per-call `transport` override, which takes precedence
15
+ * over the context value — useful for tests and for plugins that want to
16
+ * shard work across multiple hosts.
17
+ */
18
+ declare function ExtensionRuntimeProvider({ transport, children }: ExtensionRuntimeProviderProps): ReactNode;
19
+
20
+ interface UseMcpResourceOptions {
21
+ /**
22
+ * Override the transport resolved from {@link ExtensionRuntimeProvider}.
23
+ * Primarily intended for tests and advanced multi-host scenarios.
24
+ */
25
+ transport?: McpTransport;
26
+ }
27
+ interface UseMcpResourceResult<T> {
28
+ data: T | undefined;
29
+ error: Error | undefined;
30
+ loading: boolean;
31
+ /** Re-run the fetch against the current URI. Stable across renders. */
32
+ refetch: () => void;
33
+ }
34
+ /**
35
+ * Subscribe to an MCP resource by URI.
36
+ *
37
+ * The hook fetches via the resolved {@link McpTransport} on mount and whenever
38
+ * the URI changes. Each fetch is cancellable: when the component unmounts or
39
+ * the URI changes, the in-flight call is aborted via an {@link AbortController}
40
+ * so stale responses cannot overwrite later state.
41
+ *
42
+ * `refetch` re-runs the fetch against the latest URI. The returned callback is
43
+ * stable across renders so it is safe to include in `useEffect` dependency
44
+ * arrays.
45
+ */
46
+ declare function useMcpResource<T>(uri: string, opts?: UseMcpResourceOptions): UseMcpResourceResult<T>;
47
+
48
+ interface UseMcpToolOptions {
49
+ /**
50
+ * Override the transport resolved from {@link ExtensionRuntimeProvider}.
51
+ * Primarily intended for tests and advanced multi-host scenarios.
52
+ */
53
+ transport?: McpTransport;
54
+ }
55
+ interface UseMcpToolResult<TReq, TRes> {
56
+ /**
57
+ * Invoke the tool. Each call gets a fresh {@link AbortController} that is
58
+ * aborted on unmount so unresolved promises cannot keep state alive after
59
+ * the component is gone.
60
+ */
61
+ invoke: (req: TReq) => Promise<TRes>;
62
+ loading: boolean;
63
+ error: Error | undefined;
64
+ }
65
+ /**
66
+ * Hook returning a callback that invokes an MCP tool by name through the
67
+ * resolved {@link McpTransport}.
68
+ *
69
+ * `loading` and `error` track the most recent in-flight invocation. Callers
70
+ * may also `await` the returned promise directly — failures are both rejected
71
+ * to the caller AND surfaced via `error` so component-level UI can react.
72
+ *
73
+ * `invoke` is a stable reference across renders for the same `toolName` and
74
+ * transport, making it safe to use inside `useEffect`/`useCallback` deps.
75
+ */
76
+ declare function useMcpTool<TReq, TRes>(toolName: string, opts?: UseMcpToolOptions): UseMcpToolResult<TReq, TRes>;
77
+
78
+ /**
79
+ * Configuration accepted by {@link defineDeclarativePlugin}.
80
+ *
81
+ * A Contract A (host-rendered) plugin contributes a map of resource URIs to
82
+ * declarative SDUI trees. The host fetches a resource by URI and renders it
83
+ * against the v1 vocabulary.
84
+ */
85
+ interface DeclarativePluginConfig {
86
+ resources: Record<string, SduiNode>;
87
+ }
88
+ /**
89
+ * Configuration accepted by {@link defineEthisysPlugin}.
90
+ *
91
+ * `renderMode` is constrained to the protocol's {@link RenderMode} enum so
92
+ * authoring mistakes (`"iframe"`, `"webview"`, …) are caught at compile time.
93
+ * `mount` is generic so Contract B remote-runtime authors can attach their
94
+ * own mount surface without losing type information at the call site.
95
+ */
96
+ interface EthisysPluginConfig<TMount> {
97
+ renderMode: RenderMode;
98
+ mount?: TMount;
99
+ }
100
+ /**
101
+ * Author-facing identity helper for a Contract A (host-rendered) declarative
102
+ * plugin definition.
103
+ *
104
+ * The helper performs no runtime work — it exists purely so authoring sites
105
+ * receive precise type inference and editor tooling against the protocol's
106
+ * {@link SduiNode} contract. The returned value is the exact same reference
107
+ * the caller passed in.
108
+ */
109
+ declare const defineDeclarativePlugin: (cfg: DeclarativePluginConfig) => DeclarativePluginConfig;
110
+ /**
111
+ * Author-facing identity helper for a generic EthisysCore plugin definition.
112
+ *
113
+ * The helper performs no runtime work. It constrains `renderMode` to the
114
+ * protocol's {@link RenderMode} enum and preserves the inferred type of an
115
+ * optional `mount` surface, so Contract B authors can pass through their own
116
+ * mount object without widening it to `unknown`.
117
+ */
118
+ declare const defineEthisysPlugin: <TMount>(cfg: EthisysPluginConfig<TMount>) => EthisysPluginConfig<TMount>;
119
+
120
+ export { type DeclarativePluginConfig, type EthisysPluginConfig, ExtensionRuntimeProvider, type ExtensionRuntimeProviderProps, McpTransport, type UseMcpResourceOptions, type UseMcpResourceResult, type UseMcpToolOptions, type UseMcpToolResult, defineDeclarativePlugin, defineEthisysPlugin, useMcpResource, useMcpTool };
@@ -0,0 +1,120 @@
1
+ import { M as McpTransport } from '../transport-DVn2GVZh.js';
2
+ import { ReactNode } from 'react';
3
+ import { SduiNode, RenderMode } from '@ethisyscore/protocol';
4
+
5
+ interface ExtensionRuntimeProviderProps {
6
+ transport: McpTransport;
7
+ children?: ReactNode;
8
+ }
9
+ /**
10
+ * Wrap a plugin's React tree so descendant {@link useMcpResource} and
11
+ * {@link useMcpTool} calls resolve a default transport without having to
12
+ * thread it through every component.
13
+ *
14
+ * Hooks still accept a per-call `transport` override, which takes precedence
15
+ * over the context value — useful for tests and for plugins that want to
16
+ * shard work across multiple hosts.
17
+ */
18
+ declare function ExtensionRuntimeProvider({ transport, children }: ExtensionRuntimeProviderProps): ReactNode;
19
+
20
+ interface UseMcpResourceOptions {
21
+ /**
22
+ * Override the transport resolved from {@link ExtensionRuntimeProvider}.
23
+ * Primarily intended for tests and advanced multi-host scenarios.
24
+ */
25
+ transport?: McpTransport;
26
+ }
27
+ interface UseMcpResourceResult<T> {
28
+ data: T | undefined;
29
+ error: Error | undefined;
30
+ loading: boolean;
31
+ /** Re-run the fetch against the current URI. Stable across renders. */
32
+ refetch: () => void;
33
+ }
34
+ /**
35
+ * Subscribe to an MCP resource by URI.
36
+ *
37
+ * The hook fetches via the resolved {@link McpTransport} on mount and whenever
38
+ * the URI changes. Each fetch is cancellable: when the component unmounts or
39
+ * the URI changes, the in-flight call is aborted via an {@link AbortController}
40
+ * so stale responses cannot overwrite later state.
41
+ *
42
+ * `refetch` re-runs the fetch against the latest URI. The returned callback is
43
+ * stable across renders so it is safe to include in `useEffect` dependency
44
+ * arrays.
45
+ */
46
+ declare function useMcpResource<T>(uri: string, opts?: UseMcpResourceOptions): UseMcpResourceResult<T>;
47
+
48
+ interface UseMcpToolOptions {
49
+ /**
50
+ * Override the transport resolved from {@link ExtensionRuntimeProvider}.
51
+ * Primarily intended for tests and advanced multi-host scenarios.
52
+ */
53
+ transport?: McpTransport;
54
+ }
55
+ interface UseMcpToolResult<TReq, TRes> {
56
+ /**
57
+ * Invoke the tool. Each call gets a fresh {@link AbortController} that is
58
+ * aborted on unmount so unresolved promises cannot keep state alive after
59
+ * the component is gone.
60
+ */
61
+ invoke: (req: TReq) => Promise<TRes>;
62
+ loading: boolean;
63
+ error: Error | undefined;
64
+ }
65
+ /**
66
+ * Hook returning a callback that invokes an MCP tool by name through the
67
+ * resolved {@link McpTransport}.
68
+ *
69
+ * `loading` and `error` track the most recent in-flight invocation. Callers
70
+ * may also `await` the returned promise directly — failures are both rejected
71
+ * to the caller AND surfaced via `error` so component-level UI can react.
72
+ *
73
+ * `invoke` is a stable reference across renders for the same `toolName` and
74
+ * transport, making it safe to use inside `useEffect`/`useCallback` deps.
75
+ */
76
+ declare function useMcpTool<TReq, TRes>(toolName: string, opts?: UseMcpToolOptions): UseMcpToolResult<TReq, TRes>;
77
+
78
+ /**
79
+ * Configuration accepted by {@link defineDeclarativePlugin}.
80
+ *
81
+ * A Contract A (host-rendered) plugin contributes a map of resource URIs to
82
+ * declarative SDUI trees. The host fetches a resource by URI and renders it
83
+ * against the v1 vocabulary.
84
+ */
85
+ interface DeclarativePluginConfig {
86
+ resources: Record<string, SduiNode>;
87
+ }
88
+ /**
89
+ * Configuration accepted by {@link defineEthisysPlugin}.
90
+ *
91
+ * `renderMode` is constrained to the protocol's {@link RenderMode} enum so
92
+ * authoring mistakes (`"iframe"`, `"webview"`, …) are caught at compile time.
93
+ * `mount` is generic so Contract B remote-runtime authors can attach their
94
+ * own mount surface without losing type information at the call site.
95
+ */
96
+ interface EthisysPluginConfig<TMount> {
97
+ renderMode: RenderMode;
98
+ mount?: TMount;
99
+ }
100
+ /**
101
+ * Author-facing identity helper for a Contract A (host-rendered) declarative
102
+ * plugin definition.
103
+ *
104
+ * The helper performs no runtime work — it exists purely so authoring sites
105
+ * receive precise type inference and editor tooling against the protocol's
106
+ * {@link SduiNode} contract. The returned value is the exact same reference
107
+ * the caller passed in.
108
+ */
109
+ declare const defineDeclarativePlugin: (cfg: DeclarativePluginConfig) => DeclarativePluginConfig;
110
+ /**
111
+ * Author-facing identity helper for a generic EthisysCore plugin definition.
112
+ *
113
+ * The helper performs no runtime work. It constrains `renderMode` to the
114
+ * protocol's {@link RenderMode} enum and preserves the inferred type of an
115
+ * optional `mount` surface, so Contract B authors can pass through their own
116
+ * mount object without widening it to `unknown`.
117
+ */
118
+ declare const defineEthisysPlugin: <TMount>(cfg: EthisysPluginConfig<TMount>) => EthisysPluginConfig<TMount>;
119
+
120
+ export { type DeclarativePluginConfig, type EthisysPluginConfig, ExtensionRuntimeProvider, type ExtensionRuntimeProviderProps, McpTransport, type UseMcpResourceOptions, type UseMcpResourceResult, type UseMcpToolOptions, type UseMcpToolResult, defineDeclarativePlugin, defineEthisysPlugin, useMcpResource, useMcpTool };
@@ -0,0 +1,107 @@
1
+ import { createContext, useMemo, useState, useRef, useEffect, useCallback, useContext } from 'react';
2
+ import { jsx } from 'react/jsx-runtime';
3
+
4
+ // src/plugin/ExtensionRuntimeProvider.tsx
5
+ var ExtensionRuntimeContext = createContext(null);
6
+ function ExtensionRuntimeProvider({ transport, children }) {
7
+ const value = useMemo(() => transport, [transport]);
8
+ return /* @__PURE__ */ jsx(ExtensionRuntimeContext.Provider, { value, children });
9
+ }
10
+ function useExtensionRuntimeTransport(override) {
11
+ const fromContext = useContext(ExtensionRuntimeContext);
12
+ const resolved = override ?? fromContext;
13
+ if (!resolved) {
14
+ throw new Error(
15
+ "No McpTransport available. Wrap your plugin in <ExtensionRuntimeProvider transport={...}> or pass `transport` directly to the hook."
16
+ );
17
+ }
18
+ return resolved;
19
+ }
20
+ function useMcpResource(uri, opts) {
21
+ const transport = useExtensionRuntimeTransport(opts?.transport);
22
+ const [data, setData] = useState(void 0);
23
+ const [error, setError] = useState(void 0);
24
+ const [loading, setLoading] = useState(true);
25
+ const [refetchTick, setRefetchTick] = useState(0);
26
+ const controllerRef = useRef(null);
27
+ useEffect(() => {
28
+ const controller = new AbortController();
29
+ controllerRef.current?.abort();
30
+ controllerRef.current = controller;
31
+ let cancelled = false;
32
+ setLoading(true);
33
+ setError(void 0);
34
+ transport.getResource(uri, controller.signal).then((result) => {
35
+ if (cancelled || controller.signal.aborted) {
36
+ return;
37
+ }
38
+ setData(result.data);
39
+ setLoading(false);
40
+ }).catch((err) => {
41
+ if (cancelled || controller.signal.aborted) {
42
+ return;
43
+ }
44
+ setError(err instanceof Error ? err : new Error(String(err)));
45
+ setLoading(false);
46
+ });
47
+ return () => {
48
+ cancelled = true;
49
+ controller.abort();
50
+ };
51
+ }, [uri, transport, refetchTick]);
52
+ const refetch = useCallback(() => {
53
+ setRefetchTick((tick) => tick + 1);
54
+ }, []);
55
+ return { data, error, loading, refetch };
56
+ }
57
+ function useMcpTool(toolName, opts) {
58
+ const transport = useExtensionRuntimeTransport(opts?.transport);
59
+ const [loading, setLoading] = useState(false);
60
+ const [error, setError] = useState(void 0);
61
+ const controllerRef = useRef(null);
62
+ const mountedRef = useRef(true);
63
+ useEffect(() => {
64
+ mountedRef.current = true;
65
+ return () => {
66
+ mountedRef.current = false;
67
+ controllerRef.current?.abort();
68
+ };
69
+ }, []);
70
+ const invoke = useCallback(
71
+ async (req) => {
72
+ const controller = new AbortController();
73
+ controllerRef.current?.abort();
74
+ controllerRef.current = controller;
75
+ setLoading(true);
76
+ setError(void 0);
77
+ try {
78
+ const result = await transport.invokeTool(
79
+ toolName,
80
+ req,
81
+ controller.signal
82
+ );
83
+ if (mountedRef.current && !controller.signal.aborted) {
84
+ setLoading(false);
85
+ }
86
+ return result;
87
+ } catch (err) {
88
+ const wrapped = err instanceof Error ? err : new Error(String(err));
89
+ if (mountedRef.current && !controller.signal.aborted) {
90
+ setError(wrapped);
91
+ setLoading(false);
92
+ }
93
+ throw wrapped;
94
+ }
95
+ },
96
+ [transport, toolName]
97
+ );
98
+ return { invoke, loading, error };
99
+ }
100
+
101
+ // src/plugin/define.ts
102
+ var defineDeclarativePlugin = (cfg) => cfg;
103
+ var defineEthisysPlugin = (cfg) => cfg;
104
+
105
+ export { ExtensionRuntimeProvider, defineDeclarativePlugin, defineEthisysPlugin, useMcpResource, useMcpTool };
106
+ //# sourceMappingURL=index.js.map
107
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/plugin/ExtensionRuntimeProvider.tsx","../../src/plugin/useMcpResource.ts","../../src/plugin/useMcpTool.ts","../../src/plugin/define.ts"],"names":["useState","useRef","useEffect","useCallback"],"mappings":"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","file":"index.js","sourcesContent":["import { createContext, useContext, useMemo, type ReactNode } from \"react\";\nimport type { McpTransport } from \"./transport\";\n\n/**\n * React context carrying the {@link McpTransport} the plugin should use to\n * reach the host. `null` is the explicit \"not provided\" sentinel so the hooks\n * can disambiguate from a transport that was provided but is incidentally\n * falsy in some other dimension.\n */\nconst ExtensionRuntimeContext = createContext<McpTransport | null>(null);\n\nexport interface ExtensionRuntimeProviderProps\n{\n transport: McpTransport;\n children?: ReactNode;\n}\n\n/**\n * Wrap a plugin's React tree so descendant {@link useMcpResource} and\n * {@link useMcpTool} calls resolve a default transport without having to\n * thread it through every component.\n *\n * Hooks still accept a per-call `transport` override, which takes precedence\n * over the context value — useful for tests and for plugins that want to\n * shard work across multiple hosts.\n */\nexport function ExtensionRuntimeProvider({ transport, children }: ExtensionRuntimeProviderProps): ReactNode\n{\n // Memoise so swapping `children` doesn't churn the context identity.\n const value = useMemo(() => transport, [transport]);\n return (\n <ExtensionRuntimeContext.Provider value={value}>\n {children}\n </ExtensionRuntimeContext.Provider>\n );\n}\n\n/**\n * Internal helper used by the hooks. Returns the explicit override when\n * supplied, otherwise falls back to the context. Throws a deterministic\n * error if neither is available so misconfiguration fails loudly at the\n * first render rather than producing silent no-ops.\n */\nexport function useExtensionRuntimeTransport(override?: McpTransport): McpTransport\n{\n const fromContext = useContext(ExtensionRuntimeContext);\n const resolved = override ?? fromContext;\n if (!resolved)\n {\n throw new Error(\n \"No McpTransport available. Wrap your plugin in <ExtensionRuntimeProvider transport={...}> \"\n + \"or pass `transport` directly to the hook.\",\n );\n }\n return resolved;\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useExtensionRuntimeTransport } from \"./ExtensionRuntimeProvider\";\nimport type { McpTransport } from \"./transport\";\n\nexport interface UseMcpResourceOptions\n{\n /**\n * Override the transport resolved from {@link ExtensionRuntimeProvider}.\n * Primarily intended for tests and advanced multi-host scenarios.\n */\n transport?: McpTransport;\n}\n\nexport interface UseMcpResourceResult<T>\n{\n data: T | undefined;\n error: Error | undefined;\n loading: boolean;\n /** Re-run the fetch against the current URI. Stable across renders. */\n refetch: () => void;\n}\n\n/**\n * Subscribe to an MCP resource by URI.\n *\n * The hook fetches via the resolved {@link McpTransport} on mount and whenever\n * the URI changes. Each fetch is cancellable: when the component unmounts or\n * the URI changes, the in-flight call is aborted via an {@link AbortController}\n * so stale responses cannot overwrite later state.\n *\n * `refetch` re-runs the fetch against the latest URI. The returned callback is\n * stable across renders so it is safe to include in `useEffect` dependency\n * arrays.\n */\nexport function useMcpResource<T>(\n uri: string,\n opts?: UseMcpResourceOptions,\n): UseMcpResourceResult<T>\n{\n const transport = useExtensionRuntimeTransport(opts?.transport);\n\n const [data, setData] = useState<T | undefined>(undefined);\n const [error, setError] = useState<Error | undefined>(undefined);\n const [loading, setLoading] = useState<boolean>(true);\n\n // Bump this counter to force a re-fetch from `refetch()`.\n const [refetchTick, setRefetchTick] = useState(0);\n\n // Track the active controller so `refetch` and unmount can abort it.\n const controllerRef = useRef<AbortController | null>(null);\n\n useEffect(() =>\n {\n const controller = new AbortController();\n controllerRef.current?.abort();\n controllerRef.current = controller;\n\n let cancelled = false;\n setLoading(true);\n setError(undefined);\n\n transport\n .getResource<T>(uri, controller.signal)\n .then((result) =>\n {\n if (cancelled || controller.signal.aborted)\n {\n return;\n }\n setData(result.data);\n setLoading(false);\n })\n .catch((err: unknown) =>\n {\n if (cancelled || controller.signal.aborted)\n {\n return;\n }\n setError(err instanceof Error ? err : new Error(String(err)));\n setLoading(false);\n });\n\n return () =>\n {\n cancelled = true;\n controller.abort();\n };\n }, [uri, transport, refetchTick]);\n\n const refetch = useCallback(() =>\n {\n setRefetchTick((tick) => tick + 1);\n }, []);\n\n return { data, error, loading, refetch };\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useExtensionRuntimeTransport } from \"./ExtensionRuntimeProvider\";\nimport type { McpTransport } from \"./transport\";\n\nexport interface UseMcpToolOptions\n{\n /**\n * Override the transport resolved from {@link ExtensionRuntimeProvider}.\n * Primarily intended for tests and advanced multi-host scenarios.\n */\n transport?: McpTransport;\n}\n\nexport interface UseMcpToolResult<TReq, TRes>\n{\n /**\n * Invoke the tool. Each call gets a fresh {@link AbortController} that is\n * aborted on unmount so unresolved promises cannot keep state alive after\n * the component is gone.\n */\n invoke: (req: TReq) => Promise<TRes>;\n loading: boolean;\n error: Error | undefined;\n}\n\n/**\n * Hook returning a callback that invokes an MCP tool by name through the\n * resolved {@link McpTransport}.\n *\n * `loading` and `error` track the most recent in-flight invocation. Callers\n * may also `await` the returned promise directly — failures are both rejected\n * to the caller AND surfaced via `error` so component-level UI can react.\n *\n * `invoke` is a stable reference across renders for the same `toolName` and\n * transport, making it safe to use inside `useEffect`/`useCallback` deps.\n */\nexport function useMcpTool<TReq, TRes>(\n toolName: string,\n opts?: UseMcpToolOptions,\n): UseMcpToolResult<TReq, TRes>\n{\n const transport = useExtensionRuntimeTransport(opts?.transport);\n\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | undefined>(undefined);\n\n // Track the latest controller so unmount can abort an in-flight call.\n const controllerRef = useRef<AbortController | null>(null);\n const mountedRef = useRef(true);\n\n useEffect(() =>\n {\n mountedRef.current = true;\n return () =>\n {\n mountedRef.current = false;\n controllerRef.current?.abort();\n };\n }, []);\n\n const invoke = useCallback(\n async (req: TReq): Promise<TRes> =>\n {\n const controller = new AbortController();\n controllerRef.current?.abort();\n controllerRef.current = controller;\n\n setLoading(true);\n setError(undefined);\n\n try\n {\n const result = await transport.invokeTool<TReq, TRes>(\n toolName,\n req,\n controller.signal,\n );\n if (mountedRef.current && !controller.signal.aborted)\n {\n setLoading(false);\n }\n return result;\n }\n catch (err)\n {\n const wrapped = err instanceof Error ? err : new Error(String(err));\n if (mountedRef.current && !controller.signal.aborted)\n {\n setError(wrapped);\n setLoading(false);\n }\n throw wrapped;\n }\n },\n [transport, toolName],\n );\n\n return { invoke, loading, error };\n}\n","import type { RenderMode, SduiNode } from \"@ethisyscore/protocol\";\n\n/**\n * Configuration accepted by {@link defineDeclarativePlugin}.\n *\n * A Contract A (host-rendered) plugin contributes a map of resource URIs to\n * declarative SDUI trees. The host fetches a resource by URI and renders it\n * against the v1 vocabulary.\n */\nexport interface DeclarativePluginConfig\n{\n resources: Record<string, SduiNode>;\n}\n\n/**\n * Configuration accepted by {@link defineEthisysPlugin}.\n *\n * `renderMode` is constrained to the protocol's {@link RenderMode} enum so\n * authoring mistakes (`\"iframe\"`, `\"webview\"`, …) are caught at compile time.\n * `mount` is generic so Contract B remote-runtime authors can attach their\n * own mount surface without losing type information at the call site.\n */\nexport interface EthisysPluginConfig<TMount>\n{\n renderMode: RenderMode;\n mount?: TMount;\n}\n\n/**\n * Author-facing identity helper for a Contract A (host-rendered) declarative\n * plugin definition.\n *\n * The helper performs no runtime work — it exists purely so authoring sites\n * receive precise type inference and editor tooling against the protocol's\n * {@link SduiNode} contract. The returned value is the exact same reference\n * the caller passed in.\n */\nexport const defineDeclarativePlugin = (\n cfg: DeclarativePluginConfig,\n): DeclarativePluginConfig => cfg;\n\n/**\n * Author-facing identity helper for a generic EthisysCore plugin definition.\n *\n * The helper performs no runtime work. It constrains `renderMode` to the\n * protocol's {@link RenderMode} enum and preserves the inferred type of an\n * optional `mount` surface, so Contract B authors can pass through their own\n * mount object without widening it to `unknown`.\n */\nexport const defineEthisysPlugin = <TMount>(\n cfg: EthisysPluginConfig<TMount>,\n): EthisysPluginConfig<TMount> => cfg;\n"]}
@@ -0,0 +1,25 @@
1
+ import { ComponentType, ReactNode } from 'react';
2
+ import { PrimitiveName } from '@ethisyscore/protocol';
3
+
4
+ /**
5
+ * Props supplied by the interpreter to every registered primitive component.
6
+ *
7
+ * - `children` — already-interpreted child nodes (zero or more React elements).
8
+ * - `props` — opaque per-primitive properties from the SDUI tree. The host
9
+ * component owns interpretation. The interpreter never reads or mutates them.
10
+ */
11
+ interface PrimitiveProps {
12
+ children?: ReactNode;
13
+ props?: Record<string, unknown>;
14
+ }
15
+ /**
16
+ * Exhaustive primitive-name → component map.
17
+ *
18
+ * `Record<PrimitiveName, ...>` forces TypeScript to fail compilation if any of
19
+ * the closed v1 vocabulary entries are missing when a consumer constructs the
20
+ * registry. This is the mechanism that keeps host registries in lockstep with
21
+ * the protocol-owned primitive set.
22
+ */
23
+ type ComponentRegistry = Record<PrimitiveName, ComponentType<PrimitiveProps>>;
24
+
25
+ export type { ComponentRegistry as C, PrimitiveProps as P };
@@ -0,0 +1,25 @@
1
+ import { ComponentType, ReactNode } from 'react';
2
+ import { PrimitiveName } from '@ethisyscore/protocol';
3
+
4
+ /**
5
+ * Props supplied by the interpreter to every registered primitive component.
6
+ *
7
+ * - `children` — already-interpreted child nodes (zero or more React elements).
8
+ * - `props` — opaque per-primitive properties from the SDUI tree. The host
9
+ * component owns interpretation. The interpreter never reads or mutates them.
10
+ */
11
+ interface PrimitiveProps {
12
+ children?: ReactNode;
13
+ props?: Record<string, unknown>;
14
+ }
15
+ /**
16
+ * Exhaustive primitive-name → component map.
17
+ *
18
+ * `Record<PrimitiveName, ...>` forces TypeScript to fail compilation if any of
19
+ * the closed v1 vocabulary entries are missing when a consumer constructs the
20
+ * registry. This is the mechanism that keeps host registries in lockstep with
21
+ * the protocol-owned primitive set.
22
+ */
23
+ type ComponentRegistry = Record<PrimitiveName, ComponentType<PrimitiveProps>>;
24
+
25
+ export type { ComponentRegistry as C, PrimitiveProps as P };