@immediately-run/sdk 0.31.0 → 0.33.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 (63) hide show
  1. package/dist/boot.d.cts +2 -2
  2. package/dist/boot.d.ts +2 -2
  3. package/dist/catalog.cjs +6 -3
  4. package/dist/catalog.cjs.map +1 -1
  5. package/dist/catalog.d.cts +3 -2
  6. package/dist/catalog.d.ts +3 -2
  7. package/dist/catalog.js +6 -3
  8. package/dist/catalog.js.map +1 -1
  9. package/dist/components/Include.d.cts +2 -3
  10. package/dist/components/Include.d.ts +2 -3
  11. package/dist/components/MainContent.d.cts +4 -4
  12. package/dist/components/MainContent.d.ts +4 -4
  13. package/dist/components/MountImage.d.cts +2 -2
  14. package/dist/components/MountImage.d.ts +2 -2
  15. package/dist/components/Routes.d.cts +2 -2
  16. package/dist/components/Routes.d.ts +2 -2
  17. package/dist/components/defaults.d.cts +2 -2
  18. package/dist/components/defaults.d.ts +2 -2
  19. package/dist/components/errors.d.cts +2 -2
  20. package/dist/components/errors.d.ts +2 -2
  21. package/dist/index.d.cts +1 -2
  22. package/dist/index.d.ts +1 -2
  23. package/dist/llm.cjs +6 -1
  24. package/dist/llm.cjs.map +1 -1
  25. package/dist/llm.d.cts +6 -0
  26. package/dist/llm.d.ts +6 -0
  27. package/dist/llm.js +6 -1
  28. package/dist/llm.js.map +1 -1
  29. package/dist/loading.d.cts +5 -5
  30. package/dist/loading.d.ts +5 -5
  31. package/dist/moduleCache.d.cts +1 -2
  32. package/dist/moduleCache.d.ts +1 -2
  33. package/dist/protocolStream.cjs +20 -4
  34. package/dist/protocolStream.cjs.map +1 -1
  35. package/dist/protocolStream.d.cts +15 -2
  36. package/dist/protocolStream.d.ts +15 -2
  37. package/dist/protocolStream.js +20 -4
  38. package/dist/protocolStream.js.map +1 -1
  39. package/dist/safeContent/SafeContent.cjs.map +1 -1
  40. package/dist/safeContent/SafeContent.js.map +1 -1
  41. package/dist/safeContent/index.cjs.map +1 -1
  42. package/dist/safeContent/index.d.cts +1 -1
  43. package/dist/safeContent/index.d.ts +1 -1
  44. package/dist/safeContent/index.js.map +1 -1
  45. package/dist/safeContent/mdastDeps.cjs +10697 -0
  46. package/dist/safeContent/mdastDeps.js +10680 -0
  47. package/dist/safeContent/parseSafeMdast.cjs +17 -15
  48. package/dist/safeContent/parseSafeMdast.cjs.map +1 -1
  49. package/dist/safeContent/parseSafeMdast.d.cts +16 -2
  50. package/dist/safeContent/parseSafeMdast.d.ts +16 -2
  51. package/dist/safeContent/parseSafeMdast.js +21 -15
  52. package/dist/safeContent/parseSafeMdast.js.map +1 -1
  53. package/dist/safeContent/renderMdast.cjs +5 -1
  54. package/dist/safeContent/renderMdast.cjs.map +1 -1
  55. package/dist/safeContent/renderMdast.js +5 -1
  56. package/dist/safeContent/renderMdast.js.map +1 -1
  57. package/dist/version.cjs +1 -1
  58. package/dist/version.cjs.map +1 -1
  59. package/dist/version.d.cts +1 -1
  60. package/dist/version.d.ts +1 -1
  61. package/dist/version.js +1 -1
  62. package/dist/version.js.map +1 -1
  63. package/package.json +9 -7
package/dist/boot.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
1
+ import * as react from 'react';
2
2
  import { FC, ReactNode } from 'react';
3
3
  import { RoutingSpec } from './RoutingSpec.cjs';
4
4
 
@@ -39,7 +39,7 @@ type BootProps = {
39
39
  declare const TinkerableApp: ({ routingSpec, children, }: {
40
40
  routingSpec: RoutingSpec;
41
41
  children?: ReactNode;
42
- }) => react_jsx_runtime.JSX.Element;
42
+ }) => react.JSX.Element;
43
43
  /** The default route table when `boot` is called with no `routingSpec`/`children`:
44
44
  * `/` → main content, `/files/<path>` → the file router, else not-found.
45
45
  * Re-expressed with path templates (SANDBOX_ROUTING_SPEC §7); the file path
package/dist/boot.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
1
+ import * as react from 'react';
2
2
  import { FC, ReactNode } from 'react';
3
3
  import { RoutingSpec } from './RoutingSpec.js';
4
4
 
@@ -39,7 +39,7 @@ type BootProps = {
39
39
  declare const TinkerableApp: ({ routingSpec, children, }: {
40
40
  routingSpec: RoutingSpec;
41
41
  children?: ReactNode;
42
- }) => react_jsx_runtime.JSX.Element;
42
+ }) => react.JSX.Element;
43
43
  /** The default route table when `boot` is called with no `routingSpec`/`children`:
44
44
  * `/` → main content, `/files/<path>` → the file router, else not-found.
45
45
  * Re-expressed with path templates (SANDBOX_ROUTING_SPEC §7); the file path
package/dist/catalog.cjs CHANGED
@@ -45,11 +45,14 @@ const invoke = async (name, params = {}) => {
45
45
  };
46
46
  const streamTransport = {
47
47
  send: (msg) => (0, import_sandboxUtils.sendMessage)(msg.type, msg),
48
- subscribe: (type, handler) => (0, import_sandboxUtils.addListener)(type, (msg) => handler(msg))
48
+ subscribe: (type, handler) => (0, import_sandboxUtils.addListener)(type, (msg) => handler(msg)),
49
+ // Early-cancel: route a `{type, msgId, cancel:true}` frame back to the host so it
50
+ // aborts the in-flight generation (and, for `llm:chat`, stops billing) — §3.3.
51
+ cancel: (msg) => (0, import_sandboxUtils.sendMessage)(msg.type, msg)
49
52
  };
50
- function invokeStream(name, params = {}) {
53
+ function invokeStream(name, params = {}, signal) {
51
54
  const [scheme, method] = split(name);
52
- return (0, import_protocolStream.consumeStream)(streamTransport, `protocol-${scheme}`, method, [params]);
55
+ return (0, import_protocolStream.consumeStream)(streamTransport, `protocol-${scheme}`, method, [params], void 0, signal);
53
56
  }
54
57
  const channel = (0, import_pushChannel.createPushChannel)({
55
58
  pushType: "api-catalog",
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/catalog.ts"],"sourcesContent":["// The method catalog (UI_AS_APPS_SPEC §5.5) — the app's own grant-filtered RPC\n// surface, and a generic way to call it. The host advertises exactly the methods\n// this app may invoke (MCP-tool-shaped); `invoke()` calls one by its catalog name.\n// Handing the catalog to an embedded agent as its tool list confines the agent to\n// the app's authority (agent sandboxing falls out of the capability model, §5.9).\nimport { protocolRequest, sendMessage, addListener } from './sandboxUtils';\nimport type { StreamFrame, StreamTransport } from './protocolStream';\nimport { consumeStream } from './protocolStream';\nimport { createPushChannel } from './pushChannel';\n\n/** One advertised method, as the host generated it from its gate table. */\nexport interface ApiMethod {\n /** Catalog name, `protocol-` stripped — e.g. `spaces:share`, `contribute:run`. */\n name: string;\n /** The capability this method requires (already held — it's in your catalog). */\n capability: string;\n /** True when the method STREAMS (use {@link invokeStream}) vs. single-reply. */\n stream?: boolean;\n /**\n * JSON Schema for the method's single object argument, when the host declares one.\n * Self-describes the call so a catalog-as-tools bridge (an embedded agent) can\n * advertise the real param shape — e.g. that `authoring:typecheck` takes a nested\n * `{ files: [{ path, content }] }` array — instead of a permissive \"any object\".\n * Absent for methods the host advertises without a schema.\n */\n paramsSchema?: Record<string, unknown>;\n}\n\n// `scheme:method` → ['scheme', 'method'] (the wire protocol is `protocol-scheme`).\nconst split = (name: string): [string, string] => {\n const i = name.indexOf(':');\n if (i <= 0) throw new Error(`invalid catalog method name: ${name}`);\n return [name.slice(0, i), name.slice(i + 1)];\n};\n\n/**\n * Call a catalog method by name — `invoke('spaces:share', { spaceId, login, role })`.\n * A thin generic over the host protocol: the host validates params and gates the\n * call (an un-granted method → `forbidden`, even if you name it directly). For a\n * STREAMING method (`ApiMethod.stream`), use {@link invokeStream}.\n */\nexport const invoke = async <T = unknown>(\n name: string,\n params: Record<string, unknown> = {},\n): Promise<T> => {\n const [scheme, method] = split(name);\n // The host replies with an `{ ok, data } | { ok:false, code }` envelope; unwrap\n // it and THROW on refusal (a `.code` like `forbidden` for an off-catalog call)\n // so callers — and any agent driving `invoke` — see the gate's verdict.\n const res = (await protocolRequest(scheme, method, [params])) as\n | { ok: true; data: unknown }\n | { ok: false; code?: string; message?: string }\n | undefined;\n if (!res || res.ok !== true) {\n const err = new Error(res?.message ?? `${name} failed`) as Error & { code?: string };\n err.code = res?.code ?? 'unknown';\n throw err;\n }\n return res.data as T;\n};\n\n// Stream transport over the resolver (SDK_PACKAGING_SPEC §4) — sendMessage /\n// addListener route through `transport()` (injected bundler messageBus or the §4\n// global), never `bundler.messageBus` directly.\nconst streamTransport: StreamTransport = {\n send: (msg) => sendMessage(msg.type, msg as unknown as Record<string, unknown>),\n subscribe: (type, handler) =>\n addListener(type, (msg) => handler(msg as { msgId?: number; stream?: StreamFrame })),\n};\n\n/** Call a STREAMING catalog method by name, yielding its events. */\nexport function invokeStream<T = unknown, R = unknown>(\n name: string,\n params: Record<string, unknown> = {},\n): AsyncGenerator<T, R, void> {\n const [scheme, method] = split(name);\n return consumeStream<T, R>(streamTransport, `protocol-${scheme}`, method, [params]);\n}\n\n// The catalog list is read over the transport (§4): the host pushes `api-catalog`\n// and answers `request-api-catalog` with this app's grant-filtered methods (wire\n// format: site-main channelBridge.ts).\nconst channel = createPushChannel<ApiMethod[]>({\n pushType: 'api-catalog',\n requestType: 'request-api-catalog',\n initial: [],\n parse: (msg) => (Array.isArray(msg.methods) ? (msg.methods as ApiMethod[]) : undefined),\n});\n\n/** The methods this app may call (grant-filtered, §5.5). Poll for a one-off read;\n * use {@link onCatalogChange} / {@link useCatalog} to react. */\nexport const getCatalog = (): ApiMethod[] => channel.get();\n\n/** Subscribe to catalog changes (e.g. a grant added/revoked). Invoked immediately\n * with the current catalog, then on every change. Returns an unsubscribe fn. */\nexport const onCatalogChange = (listener: (catalog: ApiMethod[]) => void): (() => void) =>\n channel.onChange(listener);\n\n/** React hook returning this app's method catalog, re-rendering on change. Hand\n * it to an embedded agent as its tool list to confine the agent to the app's\n * authority (§5.9). */\nexport const useCatalog = (): ApiMethod[] => channel.use();\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAKA,0BAA0D;AAE1D,4BAA8B;AAC9B,yBAAkC;AAqBlC,MAAM,QAAQ,CAAC,SAAmC;AAChD,QAAM,IAAI,KAAK,QAAQ,GAAG;AAC1B,MAAI,KAAK,EAAG,OAAM,IAAI,MAAM,gCAAgC,IAAI,EAAE;AAClE,SAAO,CAAC,KAAK,MAAM,GAAG,CAAC,GAAG,KAAK,MAAM,IAAI,CAAC,CAAC;AAC7C;AAQO,MAAM,SAAS,OACpB,MACA,SAAkC,CAAC,MACpB;AACf,QAAM,CAAC,QAAQ,MAAM,IAAI,MAAM,IAAI;AAInC,QAAM,MAAO,UAAM,qCAAgB,QAAQ,QAAQ,CAAC,MAAM,CAAC;AAI3D,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,GAAG,IAAI,SAAS;AACtD,QAAI,OAAO,KAAK,QAAQ;AACxB,UAAM;AAAA,EACR;AACA,SAAO,IAAI;AACb;AAKA,MAAM,kBAAmC;AAAA,EACvC,MAAM,CAAC,YAAQ,iCAAY,IAAI,MAAM,GAAyC;AAAA,EAC9E,WAAW,CAAC,MAAM,gBAChB,iCAAY,MAAM,CAAC,QAAQ,QAAQ,GAA+C,CAAC;AACvF;AAGO,SAAS,aACd,MACA,SAAkC,CAAC,GACP;AAC5B,QAAM,CAAC,QAAQ,MAAM,IAAI,MAAM,IAAI;AACnC,aAAO,qCAAoB,iBAAiB,YAAY,MAAM,IAAI,QAAQ,CAAC,MAAM,CAAC;AACpF;AAKA,MAAM,cAAU,sCAA+B;AAAA,EAC7C,UAAU;AAAA,EACV,aAAa;AAAA,EACb,SAAS,CAAC;AAAA,EACV,OAAO,CAAC,QAAS,MAAM,QAAQ,IAAI,OAAO,IAAK,IAAI,UAA0B;AAC/E,CAAC;AAIM,MAAM,aAAa,MAAmB,QAAQ,IAAI;AAIlD,MAAM,kBAAkB,CAAC,aAC9B,QAAQ,SAAS,QAAQ;AAKpB,MAAM,aAAa,MAAmB,QAAQ,IAAI;","names":[]}
1
+ {"version":3,"sources":["../src/catalog.ts"],"sourcesContent":["// The method catalog (UI_AS_APPS_SPEC §5.5) — the app's own grant-filtered RPC\n// surface, and a generic way to call it. The host advertises exactly the methods\n// this app may invoke (MCP-tool-shaped); `invoke()` calls one by its catalog name.\n// Handing the catalog to an embedded agent as its tool list confines the agent to\n// the app's authority (agent sandboxing falls out of the capability model, §5.9).\nimport { protocolRequest, sendMessage, addListener } from './sandboxUtils';\nimport type { StreamFrame, StreamTransport } from './protocolStream';\nimport { consumeStream } from './protocolStream';\nimport { createPushChannel } from './pushChannel';\n\n/** One advertised method, as the host generated it from its gate table. */\nexport interface ApiMethod {\n /** Catalog name, `protocol-` stripped — e.g. `spaces:share`, `contribute:run`. */\n name: string;\n /** The capability this method requires (already held — it's in your catalog). */\n capability: string;\n /** True when the method STREAMS (use {@link invokeStream}) vs. single-reply. */\n stream?: boolean;\n /**\n * JSON Schema for the method's single object argument, when the host declares one.\n * Self-describes the call so a catalog-as-tools bridge (an embedded agent) can\n * advertise the real param shape — e.g. that `authoring:typecheck` takes a nested\n * `{ files: [{ path, content }] }` array — instead of a permissive \"any object\".\n * Absent for methods the host advertises without a schema.\n */\n paramsSchema?: Record<string, unknown>;\n}\n\n// `scheme:method` → ['scheme', 'method'] (the wire protocol is `protocol-scheme`).\nconst split = (name: string): [string, string] => {\n const i = name.indexOf(':');\n if (i <= 0) throw new Error(`invalid catalog method name: ${name}`);\n return [name.slice(0, i), name.slice(i + 1)];\n};\n\n/**\n * Call a catalog method by name — `invoke('spaces:share', { spaceId, login, role })`.\n * A thin generic over the host protocol: the host validates params and gates the\n * call (an un-granted method → `forbidden`, even if you name it directly). For a\n * STREAMING method (`ApiMethod.stream`), use {@link invokeStream}.\n */\nexport const invoke = async <T = unknown>(\n name: string,\n params: Record<string, unknown> = {},\n): Promise<T> => {\n const [scheme, method] = split(name);\n // The host replies with an `{ ok, data } | { ok:false, code }` envelope; unwrap\n // it and THROW on refusal (a `.code` like `forbidden` for an off-catalog call)\n // so callers — and any agent driving `invoke` — see the gate's verdict.\n const res = (await protocolRequest(scheme, method, [params])) as\n | { ok: true; data: unknown }\n | { ok: false; code?: string; message?: string }\n | undefined;\n if (!res || res.ok !== true) {\n const err = new Error(res?.message ?? `${name} failed`) as Error & { code?: string };\n err.code = res?.code ?? 'unknown';\n throw err;\n }\n return res.data as T;\n};\n\n// Stream transport over the resolver (SDK_PACKAGING_SPEC §4) — sendMessage /\n// addListener route through `transport()` (injected bundler messageBus or the §4\n// global), never `bundler.messageBus` directly.\nconst streamTransport: StreamTransport = {\n send: (msg) => sendMessage(msg.type, msg as unknown as Record<string, unknown>),\n subscribe: (type, handler) =>\n addListener(type, (msg) => handler(msg as { msgId?: number; stream?: StreamFrame })),\n // Early-cancel: route a `{type, msgId, cancel:true}` frame back to the host so it\n // aborts the in-flight generation (and, for `llm:chat`, stops billing) — §3.3.\n cancel: (msg) => sendMessage(msg.type, msg as unknown as Record<string, unknown>),\n};\n\n/** Call a STREAMING catalog method by name, yielding its events. Pass `signal` to\n * abort mid-stream: the host stops generating and (for `llm:chat`) stops billing. */\nexport function invokeStream<T = unknown, R = unknown>(\n name: string,\n params: Record<string, unknown> = {},\n signal?: AbortSignal,\n): AsyncGenerator<T, R, void> {\n const [scheme, method] = split(name);\n return consumeStream<T, R>(streamTransport, `protocol-${scheme}`, method, [params], undefined, signal);\n}\n\n// The catalog list is read over the transport (§4): the host pushes `api-catalog`\n// and answers `request-api-catalog` with this app's grant-filtered methods (wire\n// format: site-main channelBridge.ts).\nconst channel = createPushChannel<ApiMethod[]>({\n pushType: 'api-catalog',\n requestType: 'request-api-catalog',\n initial: [],\n parse: (msg) => (Array.isArray(msg.methods) ? (msg.methods as ApiMethod[]) : undefined),\n});\n\n/** The methods this app may call (grant-filtered, §5.5). Poll for a one-off read;\n * use {@link onCatalogChange} / {@link useCatalog} to react. */\nexport const getCatalog = (): ApiMethod[] => channel.get();\n\n/** Subscribe to catalog changes (e.g. a grant added/revoked). Invoked immediately\n * with the current catalog, then on every change. Returns an unsubscribe fn. */\nexport const onCatalogChange = (listener: (catalog: ApiMethod[]) => void): (() => void) =>\n channel.onChange(listener);\n\n/** React hook returning this app's method catalog, re-rendering on change. Hand\n * it to an embedded agent as its tool list to confine the agent to the app's\n * authority (§5.9). */\nexport const useCatalog = (): ApiMethod[] => channel.use();\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAKA,0BAA0D;AAE1D,4BAA8B;AAC9B,yBAAkC;AAqBlC,MAAM,QAAQ,CAAC,SAAmC;AAChD,QAAM,IAAI,KAAK,QAAQ,GAAG;AAC1B,MAAI,KAAK,EAAG,OAAM,IAAI,MAAM,gCAAgC,IAAI,EAAE;AAClE,SAAO,CAAC,KAAK,MAAM,GAAG,CAAC,GAAG,KAAK,MAAM,IAAI,CAAC,CAAC;AAC7C;AAQO,MAAM,SAAS,OACpB,MACA,SAAkC,CAAC,MACpB;AACf,QAAM,CAAC,QAAQ,MAAM,IAAI,MAAM,IAAI;AAInC,QAAM,MAAO,UAAM,qCAAgB,QAAQ,QAAQ,CAAC,MAAM,CAAC;AAI3D,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,GAAG,IAAI,SAAS;AACtD,QAAI,OAAO,KAAK,QAAQ;AACxB,UAAM;AAAA,EACR;AACA,SAAO,IAAI;AACb;AAKA,MAAM,kBAAmC;AAAA,EACvC,MAAM,CAAC,YAAQ,iCAAY,IAAI,MAAM,GAAyC;AAAA,EAC9E,WAAW,CAAC,MAAM,gBAChB,iCAAY,MAAM,CAAC,QAAQ,QAAQ,GAA+C,CAAC;AAAA;AAAA;AAAA,EAGrF,QAAQ,CAAC,YAAQ,iCAAY,IAAI,MAAM,GAAyC;AAClF;AAIO,SAAS,aACd,MACA,SAAkC,CAAC,GACnC,QAC4B;AAC5B,QAAM,CAAC,QAAQ,MAAM,IAAI,MAAM,IAAI;AACnC,aAAO,qCAAoB,iBAAiB,YAAY,MAAM,IAAI,QAAQ,CAAC,MAAM,GAAG,QAAW,MAAM;AACvG;AAKA,MAAM,cAAU,sCAA+B;AAAA,EAC7C,UAAU;AAAA,EACV,aAAa;AAAA,EACb,SAAS,CAAC;AAAA,EACV,OAAO,CAAC,QAAS,MAAM,QAAQ,IAAI,OAAO,IAAK,IAAI,UAA0B;AAC/E,CAAC;AAIM,MAAM,aAAa,MAAmB,QAAQ,IAAI;AAIlD,MAAM,kBAAkB,CAAC,aAC9B,QAAQ,SAAS,QAAQ;AAKpB,MAAM,aAAa,MAAmB,QAAQ,IAAI;","names":[]}
@@ -22,8 +22,9 @@ interface ApiMethod {
22
22
  * STREAMING method (`ApiMethod.stream`), use {@link invokeStream}.
23
23
  */
24
24
  declare const invoke: <T = unknown>(name: string, params?: Record<string, unknown>) => Promise<T>;
25
- /** Call a STREAMING catalog method by name, yielding its events. */
26
- declare function invokeStream<T = unknown, R = unknown>(name: string, params?: Record<string, unknown>): AsyncGenerator<T, R, void>;
25
+ /** Call a STREAMING catalog method by name, yielding its events. Pass `signal` to
26
+ * abort mid-stream: the host stops generating and (for `llm:chat`) stops billing. */
27
+ declare function invokeStream<T = unknown, R = unknown>(name: string, params?: Record<string, unknown>, signal?: AbortSignal): AsyncGenerator<T, R, void>;
27
28
  /** The methods this app may call (grant-filtered, §5.5). Poll for a one-off read;
28
29
  * use {@link onCatalogChange} / {@link useCatalog} to react. */
29
30
  declare const getCatalog: () => ApiMethod[];
package/dist/catalog.d.ts CHANGED
@@ -22,8 +22,9 @@ interface ApiMethod {
22
22
  * STREAMING method (`ApiMethod.stream`), use {@link invokeStream}.
23
23
  */
24
24
  declare const invoke: <T = unknown>(name: string, params?: Record<string, unknown>) => Promise<T>;
25
- /** Call a STREAMING catalog method by name, yielding its events. */
26
- declare function invokeStream<T = unknown, R = unknown>(name: string, params?: Record<string, unknown>): AsyncGenerator<T, R, void>;
25
+ /** Call a STREAMING catalog method by name, yielding its events. Pass `signal` to
26
+ * abort mid-stream: the host stops generating and (for `llm:chat`) stops billing. */
27
+ declare function invokeStream<T = unknown, R = unknown>(name: string, params?: Record<string, unknown>, signal?: AbortSignal): AsyncGenerator<T, R, void>;
27
28
  /** The methods this app may call (grant-filtered, §5.5). Poll for a one-off read;
28
29
  * use {@link onCatalogChange} / {@link useCatalog} to react. */
29
30
  declare const getCatalog: () => ApiMethod[];
package/dist/catalog.js CHANGED
@@ -18,11 +18,14 @@ const invoke = async (name, params = {}) => {
18
18
  };
19
19
  const streamTransport = {
20
20
  send: (msg) => sendMessage(msg.type, msg),
21
- subscribe: (type, handler) => addListener(type, (msg) => handler(msg))
21
+ subscribe: (type, handler) => addListener(type, (msg) => handler(msg)),
22
+ // Early-cancel: route a `{type, msgId, cancel:true}` frame back to the host so it
23
+ // aborts the in-flight generation (and, for `llm:chat`, stops billing) — §3.3.
24
+ cancel: (msg) => sendMessage(msg.type, msg)
22
25
  };
23
- function invokeStream(name, params = {}) {
26
+ function invokeStream(name, params = {}, signal) {
24
27
  const [scheme, method] = split(name);
25
- return consumeStream(streamTransport, `protocol-${scheme}`, method, [params]);
28
+ return consumeStream(streamTransport, `protocol-${scheme}`, method, [params], void 0, signal);
26
29
  }
27
30
  const channel = createPushChannel({
28
31
  pushType: "api-catalog",
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/catalog.ts"],"sourcesContent":["// The method catalog (UI_AS_APPS_SPEC §5.5) — the app's own grant-filtered RPC\n// surface, and a generic way to call it. The host advertises exactly the methods\n// this app may invoke (MCP-tool-shaped); `invoke()` calls one by its catalog name.\n// Handing the catalog to an embedded agent as its tool list confines the agent to\n// the app's authority (agent sandboxing falls out of the capability model, §5.9).\nimport { protocolRequest, sendMessage, addListener } from './sandboxUtils';\nimport type { StreamFrame, StreamTransport } from './protocolStream';\nimport { consumeStream } from './protocolStream';\nimport { createPushChannel } from './pushChannel';\n\n/** One advertised method, as the host generated it from its gate table. */\nexport interface ApiMethod {\n /** Catalog name, `protocol-` stripped — e.g. `spaces:share`, `contribute:run`. */\n name: string;\n /** The capability this method requires (already held — it's in your catalog). */\n capability: string;\n /** True when the method STREAMS (use {@link invokeStream}) vs. single-reply. */\n stream?: boolean;\n /**\n * JSON Schema for the method's single object argument, when the host declares one.\n * Self-describes the call so a catalog-as-tools bridge (an embedded agent) can\n * advertise the real param shape — e.g. that `authoring:typecheck` takes a nested\n * `{ files: [{ path, content }] }` array — instead of a permissive \"any object\".\n * Absent for methods the host advertises without a schema.\n */\n paramsSchema?: Record<string, unknown>;\n}\n\n// `scheme:method` → ['scheme', 'method'] (the wire protocol is `protocol-scheme`).\nconst split = (name: string): [string, string] => {\n const i = name.indexOf(':');\n if (i <= 0) throw new Error(`invalid catalog method name: ${name}`);\n return [name.slice(0, i), name.slice(i + 1)];\n};\n\n/**\n * Call a catalog method by name — `invoke('spaces:share', { spaceId, login, role })`.\n * A thin generic over the host protocol: the host validates params and gates the\n * call (an un-granted method → `forbidden`, even if you name it directly). For a\n * STREAMING method (`ApiMethod.stream`), use {@link invokeStream}.\n */\nexport const invoke = async <T = unknown>(\n name: string,\n params: Record<string, unknown> = {},\n): Promise<T> => {\n const [scheme, method] = split(name);\n // The host replies with an `{ ok, data } | { ok:false, code }` envelope; unwrap\n // it and THROW on refusal (a `.code` like `forbidden` for an off-catalog call)\n // so callers — and any agent driving `invoke` — see the gate's verdict.\n const res = (await protocolRequest(scheme, method, [params])) as\n | { ok: true; data: unknown }\n | { ok: false; code?: string; message?: string }\n | undefined;\n if (!res || res.ok !== true) {\n const err = new Error(res?.message ?? `${name} failed`) as Error & { code?: string };\n err.code = res?.code ?? 'unknown';\n throw err;\n }\n return res.data as T;\n};\n\n// Stream transport over the resolver (SDK_PACKAGING_SPEC §4) — sendMessage /\n// addListener route through `transport()` (injected bundler messageBus or the §4\n// global), never `bundler.messageBus` directly.\nconst streamTransport: StreamTransport = {\n send: (msg) => sendMessage(msg.type, msg as unknown as Record<string, unknown>),\n subscribe: (type, handler) =>\n addListener(type, (msg) => handler(msg as { msgId?: number; stream?: StreamFrame })),\n};\n\n/** Call a STREAMING catalog method by name, yielding its events. */\nexport function invokeStream<T = unknown, R = unknown>(\n name: string,\n params: Record<string, unknown> = {},\n): AsyncGenerator<T, R, void> {\n const [scheme, method] = split(name);\n return consumeStream<T, R>(streamTransport, `protocol-${scheme}`, method, [params]);\n}\n\n// The catalog list is read over the transport (§4): the host pushes `api-catalog`\n// and answers `request-api-catalog` with this app's grant-filtered methods (wire\n// format: site-main channelBridge.ts).\nconst channel = createPushChannel<ApiMethod[]>({\n pushType: 'api-catalog',\n requestType: 'request-api-catalog',\n initial: [],\n parse: (msg) => (Array.isArray(msg.methods) ? (msg.methods as ApiMethod[]) : undefined),\n});\n\n/** The methods this app may call (grant-filtered, §5.5). Poll for a one-off read;\n * use {@link onCatalogChange} / {@link useCatalog} to react. */\nexport const getCatalog = (): ApiMethod[] => channel.get();\n\n/** Subscribe to catalog changes (e.g. a grant added/revoked). Invoked immediately\n * with the current catalog, then on every change. Returns an unsubscribe fn. */\nexport const onCatalogChange = (listener: (catalog: ApiMethod[]) => void): (() => void) =>\n channel.onChange(listener);\n\n/** React hook returning this app's method catalog, re-rendering on change. Hand\n * it to an embedded agent as its tool list to confine the agent to the app's\n * authority (§5.9). */\nexport const useCatalog = (): ApiMethod[] => channel.use();\n"],"mappings":"AAKA,SAAS,iBAAiB,aAAa,mBAAmB;AAE1D,SAAS,qBAAqB;AAC9B,SAAS,yBAAyB;AAqBlC,MAAM,QAAQ,CAAC,SAAmC;AAChD,QAAM,IAAI,KAAK,QAAQ,GAAG;AAC1B,MAAI,KAAK,EAAG,OAAM,IAAI,MAAM,gCAAgC,IAAI,EAAE;AAClE,SAAO,CAAC,KAAK,MAAM,GAAG,CAAC,GAAG,KAAK,MAAM,IAAI,CAAC,CAAC;AAC7C;AAQO,MAAM,SAAS,OACpB,MACA,SAAkC,CAAC,MACpB;AACf,QAAM,CAAC,QAAQ,MAAM,IAAI,MAAM,IAAI;AAInC,QAAM,MAAO,MAAM,gBAAgB,QAAQ,QAAQ,CAAC,MAAM,CAAC;AAI3D,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,GAAG,IAAI,SAAS;AACtD,QAAI,OAAO,KAAK,QAAQ;AACxB,UAAM;AAAA,EACR;AACA,SAAO,IAAI;AACb;AAKA,MAAM,kBAAmC;AAAA,EACvC,MAAM,CAAC,QAAQ,YAAY,IAAI,MAAM,GAAyC;AAAA,EAC9E,WAAW,CAAC,MAAM,YAChB,YAAY,MAAM,CAAC,QAAQ,QAAQ,GAA+C,CAAC;AACvF;AAGO,SAAS,aACd,MACA,SAAkC,CAAC,GACP;AAC5B,QAAM,CAAC,QAAQ,MAAM,IAAI,MAAM,IAAI;AACnC,SAAO,cAAoB,iBAAiB,YAAY,MAAM,IAAI,QAAQ,CAAC,MAAM,CAAC;AACpF;AAKA,MAAM,UAAU,kBAA+B;AAAA,EAC7C,UAAU;AAAA,EACV,aAAa;AAAA,EACb,SAAS,CAAC;AAAA,EACV,OAAO,CAAC,QAAS,MAAM,QAAQ,IAAI,OAAO,IAAK,IAAI,UAA0B;AAC/E,CAAC;AAIM,MAAM,aAAa,MAAmB,QAAQ,IAAI;AAIlD,MAAM,kBAAkB,CAAC,aAC9B,QAAQ,SAAS,QAAQ;AAKpB,MAAM,aAAa,MAAmB,QAAQ,IAAI;","names":[]}
1
+ {"version":3,"sources":["../src/catalog.ts"],"sourcesContent":["// The method catalog (UI_AS_APPS_SPEC §5.5) — the app's own grant-filtered RPC\n// surface, and a generic way to call it. The host advertises exactly the methods\n// this app may invoke (MCP-tool-shaped); `invoke()` calls one by its catalog name.\n// Handing the catalog to an embedded agent as its tool list confines the agent to\n// the app's authority (agent sandboxing falls out of the capability model, §5.9).\nimport { protocolRequest, sendMessage, addListener } from './sandboxUtils';\nimport type { StreamFrame, StreamTransport } from './protocolStream';\nimport { consumeStream } from './protocolStream';\nimport { createPushChannel } from './pushChannel';\n\n/** One advertised method, as the host generated it from its gate table. */\nexport interface ApiMethod {\n /** Catalog name, `protocol-` stripped — e.g. `spaces:share`, `contribute:run`. */\n name: string;\n /** The capability this method requires (already held — it's in your catalog). */\n capability: string;\n /** True when the method STREAMS (use {@link invokeStream}) vs. single-reply. */\n stream?: boolean;\n /**\n * JSON Schema for the method's single object argument, when the host declares one.\n * Self-describes the call so a catalog-as-tools bridge (an embedded agent) can\n * advertise the real param shape — e.g. that `authoring:typecheck` takes a nested\n * `{ files: [{ path, content }] }` array — instead of a permissive \"any object\".\n * Absent for methods the host advertises without a schema.\n */\n paramsSchema?: Record<string, unknown>;\n}\n\n// `scheme:method` → ['scheme', 'method'] (the wire protocol is `protocol-scheme`).\nconst split = (name: string): [string, string] => {\n const i = name.indexOf(':');\n if (i <= 0) throw new Error(`invalid catalog method name: ${name}`);\n return [name.slice(0, i), name.slice(i + 1)];\n};\n\n/**\n * Call a catalog method by name — `invoke('spaces:share', { spaceId, login, role })`.\n * A thin generic over the host protocol: the host validates params and gates the\n * call (an un-granted method → `forbidden`, even if you name it directly). For a\n * STREAMING method (`ApiMethod.stream`), use {@link invokeStream}.\n */\nexport const invoke = async <T = unknown>(\n name: string,\n params: Record<string, unknown> = {},\n): Promise<T> => {\n const [scheme, method] = split(name);\n // The host replies with an `{ ok, data } | { ok:false, code }` envelope; unwrap\n // it and THROW on refusal (a `.code` like `forbidden` for an off-catalog call)\n // so callers — and any agent driving `invoke` — see the gate's verdict.\n const res = (await protocolRequest(scheme, method, [params])) as\n | { ok: true; data: unknown }\n | { ok: false; code?: string; message?: string }\n | undefined;\n if (!res || res.ok !== true) {\n const err = new Error(res?.message ?? `${name} failed`) as Error & { code?: string };\n err.code = res?.code ?? 'unknown';\n throw err;\n }\n return res.data as T;\n};\n\n// Stream transport over the resolver (SDK_PACKAGING_SPEC §4) — sendMessage /\n// addListener route through `transport()` (injected bundler messageBus or the §4\n// global), never `bundler.messageBus` directly.\nconst streamTransport: StreamTransport = {\n send: (msg) => sendMessage(msg.type, msg as unknown as Record<string, unknown>),\n subscribe: (type, handler) =>\n addListener(type, (msg) => handler(msg as { msgId?: number; stream?: StreamFrame })),\n // Early-cancel: route a `{type, msgId, cancel:true}` frame back to the host so it\n // aborts the in-flight generation (and, for `llm:chat`, stops billing) — §3.3.\n cancel: (msg) => sendMessage(msg.type, msg as unknown as Record<string, unknown>),\n};\n\n/** Call a STREAMING catalog method by name, yielding its events. Pass `signal` to\n * abort mid-stream: the host stops generating and (for `llm:chat`) stops billing. */\nexport function invokeStream<T = unknown, R = unknown>(\n name: string,\n params: Record<string, unknown> = {},\n signal?: AbortSignal,\n): AsyncGenerator<T, R, void> {\n const [scheme, method] = split(name);\n return consumeStream<T, R>(streamTransport, `protocol-${scheme}`, method, [params], undefined, signal);\n}\n\n// The catalog list is read over the transport (§4): the host pushes `api-catalog`\n// and answers `request-api-catalog` with this app's grant-filtered methods (wire\n// format: site-main channelBridge.ts).\nconst channel = createPushChannel<ApiMethod[]>({\n pushType: 'api-catalog',\n requestType: 'request-api-catalog',\n initial: [],\n parse: (msg) => (Array.isArray(msg.methods) ? (msg.methods as ApiMethod[]) : undefined),\n});\n\n/** The methods this app may call (grant-filtered, §5.5). Poll for a one-off read;\n * use {@link onCatalogChange} / {@link useCatalog} to react. */\nexport const getCatalog = (): ApiMethod[] => channel.get();\n\n/** Subscribe to catalog changes (e.g. a grant added/revoked). Invoked immediately\n * with the current catalog, then on every change. Returns an unsubscribe fn. */\nexport const onCatalogChange = (listener: (catalog: ApiMethod[]) => void): (() => void) =>\n channel.onChange(listener);\n\n/** React hook returning this app's method catalog, re-rendering on change. Hand\n * it to an embedded agent as its tool list to confine the agent to the app's\n * authority (§5.9). */\nexport const useCatalog = (): ApiMethod[] => channel.use();\n"],"mappings":"AAKA,SAAS,iBAAiB,aAAa,mBAAmB;AAE1D,SAAS,qBAAqB;AAC9B,SAAS,yBAAyB;AAqBlC,MAAM,QAAQ,CAAC,SAAmC;AAChD,QAAM,IAAI,KAAK,QAAQ,GAAG;AAC1B,MAAI,KAAK,EAAG,OAAM,IAAI,MAAM,gCAAgC,IAAI,EAAE;AAClE,SAAO,CAAC,KAAK,MAAM,GAAG,CAAC,GAAG,KAAK,MAAM,IAAI,CAAC,CAAC;AAC7C;AAQO,MAAM,SAAS,OACpB,MACA,SAAkC,CAAC,MACpB;AACf,QAAM,CAAC,QAAQ,MAAM,IAAI,MAAM,IAAI;AAInC,QAAM,MAAO,MAAM,gBAAgB,QAAQ,QAAQ,CAAC,MAAM,CAAC;AAI3D,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,GAAG,IAAI,SAAS;AACtD,QAAI,OAAO,KAAK,QAAQ;AACxB,UAAM;AAAA,EACR;AACA,SAAO,IAAI;AACb;AAKA,MAAM,kBAAmC;AAAA,EACvC,MAAM,CAAC,QAAQ,YAAY,IAAI,MAAM,GAAyC;AAAA,EAC9E,WAAW,CAAC,MAAM,YAChB,YAAY,MAAM,CAAC,QAAQ,QAAQ,GAA+C,CAAC;AAAA;AAAA;AAAA,EAGrF,QAAQ,CAAC,QAAQ,YAAY,IAAI,MAAM,GAAyC;AAClF;AAIO,SAAS,aACd,MACA,SAAkC,CAAC,GACnC,QAC4B;AAC5B,QAAM,CAAC,QAAQ,MAAM,IAAI,MAAM,IAAI;AACnC,SAAO,cAAoB,iBAAiB,YAAY,MAAM,IAAI,QAAQ,CAAC,MAAM,GAAG,QAAW,MAAM;AACvG;AAKA,MAAM,UAAU,kBAA+B;AAAA,EAC7C,UAAU;AAAA,EACV,aAAa;AAAA,EACb,SAAS,CAAC;AAAA,EACV,OAAO,CAAC,QAAS,MAAM,QAAQ,IAAI,OAAO,IAAK,IAAI,UAA0B;AAC/E,CAAC;AAIM,MAAM,aAAa,MAAmB,QAAQ,IAAI;AAIlD,MAAM,kBAAkB,CAAC,aAC9B,QAAQ,SAAS,QAAQ;AAKpB,MAAM,aAAa,MAAmB,QAAQ,IAAI;","names":[]}
@@ -1,4 +1,3 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
2
1
  import * as react from 'react';
3
2
  import { EvaluationContext } from '../sandboxTypes.cjs';
4
3
  import { defaultLoadingComponent, defaultErrorComponent } from './defaults.cjs';
@@ -16,7 +15,7 @@ declare const RenderExportedComponentContext: react.Context<RenderFileContextTyp
16
15
  declare const RenderExportedComponent: ({ evaluationContextPromise, exportedSymbol, }: {
17
16
  evaluationContextPromise: Promise<EvaluationContext>;
18
17
  exportedSymbol: string;
19
- }) => react_jsx_runtime.JSX.Element;
18
+ }) => react.JSX.Element;
20
19
  /** Render another repo file's exported component inline, resolving + evaluating it
21
20
  * through the module cache (with Suspense + an error boundary). */
22
21
  declare const Include: ({ filename, exportedSymbol, LoadingComponent, ErrorComponent, baseModule, }: {
@@ -25,6 +24,6 @@ declare const Include: ({ filename, exportedSymbol, LoadingComponent, ErrorCompo
25
24
  LoadingComponent?: typeof defaultLoadingComponent;
26
25
  ErrorComponent?: typeof defaultErrorComponent;
27
26
  baseModule?: EvaluationContext;
28
- }) => react_jsx_runtime.JSX.Element;
27
+ }) => react.JSX.Element;
29
28
 
30
29
  export { Include, RenderExportedComponent, RenderExportedComponentContext, type RenderFileContextType };
@@ -1,4 +1,3 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
2
1
  import * as react from 'react';
3
2
  import { EvaluationContext } from '../sandboxTypes.js';
4
3
  import { defaultLoadingComponent, defaultErrorComponent } from './defaults.js';
@@ -16,7 +15,7 @@ declare const RenderExportedComponentContext: react.Context<RenderFileContextTyp
16
15
  declare const RenderExportedComponent: ({ evaluationContextPromise, exportedSymbol, }: {
17
16
  evaluationContextPromise: Promise<EvaluationContext>;
18
17
  exportedSymbol: string;
19
- }) => react_jsx_runtime.JSX.Element;
18
+ }) => react.JSX.Element;
20
19
  /** Render another repo file's exported component inline, resolving + evaluating it
21
20
  * through the module cache (with Suspense + an error boundary). */
22
21
  declare const Include: ({ filename, exportedSymbol, LoadingComponent, ErrorComponent, baseModule, }: {
@@ -25,6 +24,6 @@ declare const Include: ({ filename, exportedSymbol, LoadingComponent, ErrorCompo
25
24
  LoadingComponent?: typeof defaultLoadingComponent;
26
25
  ErrorComponent?: typeof defaultErrorComponent;
27
26
  baseModule?: EvaluationContext;
28
- }) => react_jsx_runtime.JSX.Element;
27
+ }) => react.JSX.Element;
29
28
 
30
29
  export { Include, RenderExportedComponent, RenderExportedComponentContext, type RenderFileContextType };
@@ -1,16 +1,16 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
1
+ import * as react from 'react';
2
2
  import { defaultLoadingComponent, defaultErrorComponent } from './defaults.cjs';
3
3
  import 'react-error-boundary';
4
4
 
5
5
  declare const MainContentRedirect: ({ filename }: {
6
6
  filename: string;
7
- }) => react_jsx_runtime.JSX.Element;
7
+ }) => react.JSX.Element;
8
8
  declare const MainContentInner: ({ candidatesExistPromise, }: {
9
9
  candidatesExistPromise: Promise<[string, boolean][]>;
10
- }) => react_jsx_runtime.JSX.Element;
10
+ }) => react.JSX.Element;
11
11
  declare const MainContent: ({ LoadingComponent, ErrorComponent, }?: {
12
12
  LoadingComponent?: typeof defaultLoadingComponent;
13
13
  ErrorComponent?: typeof defaultErrorComponent;
14
- }) => react_jsx_runtime.JSX.Element;
14
+ }) => react.JSX.Element;
15
15
 
16
16
  export { MainContent, MainContentInner, MainContentRedirect };
@@ -1,16 +1,16 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
1
+ import * as react from 'react';
2
2
  import { defaultLoadingComponent, defaultErrorComponent } from './defaults.js';
3
3
  import 'react-error-boundary';
4
4
 
5
5
  declare const MainContentRedirect: ({ filename }: {
6
6
  filename: string;
7
- }) => react_jsx_runtime.JSX.Element;
7
+ }) => react.JSX.Element;
8
8
  declare const MainContentInner: ({ candidatesExistPromise, }: {
9
9
  candidatesExistPromise: Promise<[string, boolean][]>;
10
- }) => react_jsx_runtime.JSX.Element;
10
+ }) => react.JSX.Element;
11
11
  declare const MainContent: ({ LoadingComponent, ErrorComponent, }?: {
12
12
  LoadingComponent?: typeof defaultLoadingComponent;
13
13
  ErrorComponent?: typeof defaultErrorComponent;
14
- }) => react_jsx_runtime.JSX.Element;
14
+ }) => react.JSX.Element;
15
15
 
16
16
  export { MainContent, MainContentInner, MainContentRedirect };
@@ -1,4 +1,4 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
1
+ import * as react from 'react';
2
2
  import { ImgHTMLAttributes, ReactNode } from 'react';
3
3
  import { SandboxMount } from '../mounts.cjs';
4
4
  import '../tasks.cjs';
@@ -37,6 +37,6 @@ interface MountImageProps extends Omit<ImgHTMLAttributes<HTMLImageElement>, 'src
37
37
  * />
38
38
  * ```
39
39
  */
40
- declare function MountImage({ mount, relPath, type, placeholder, fallback, alt, ...imgProps }: MountImageProps): react_jsx_runtime.JSX.Element;
40
+ declare function MountImage({ mount, relPath, type, placeholder, fallback, alt, ...imgProps }: MountImageProps): react.JSX.Element;
41
41
 
42
42
  export { MountImage, type MountImageProps };
@@ -1,4 +1,4 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
1
+ import * as react from 'react';
2
2
  import { ImgHTMLAttributes, ReactNode } from 'react';
3
3
  import { SandboxMount } from '../mounts.js';
4
4
  import '../tasks.js';
@@ -37,6 +37,6 @@ interface MountImageProps extends Omit<ImgHTMLAttributes<HTMLImageElement>, 'src
37
37
  * />
38
38
  * ```
39
39
  */
40
- declare function MountImage({ mount, relPath, type, placeholder, fallback, alt, ...imgProps }: MountImageProps): react_jsx_runtime.JSX.Element;
40
+ declare function MountImage({ mount, relPath, type, placeholder, fallback, alt, ...imgProps }: MountImageProps): react.JSX.Element;
41
41
 
42
42
  export { MountImage, type MountImageProps };
@@ -1,4 +1,4 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
1
+ import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
3
  import { RouteComponent } from '../RoutingSpec.cjs';
4
4
 
@@ -29,6 +29,6 @@ declare const Route: (_props: RouteProps) => null;
29
29
  declare const Routes: ({ children, fallback, }: {
30
30
  children?: ReactNode;
31
31
  fallback?: ReactNode;
32
- }) => react_jsx_runtime.JSX.Element;
32
+ }) => react.JSX.Element;
33
33
 
34
34
  export { Route, type RouteProps, Routes };
@@ -1,4 +1,4 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
1
+ import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
3
  import { RouteComponent } from '../RoutingSpec.js';
4
4
 
@@ -29,6 +29,6 @@ declare const Route: (_props: RouteProps) => null;
29
29
  declare const Routes: ({ children, fallback, }: {
30
30
  children?: ReactNode;
31
31
  fallback?: ReactNode;
32
- }) => react_jsx_runtime.JSX.Element;
32
+ }) => react.JSX.Element;
33
33
 
34
34
  export { Route, type RouteProps, Routes };
@@ -1,4 +1,4 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
1
+ import * as react from 'react';
2
2
  import { ErrorBoundaryPropsWithRender } from 'react-error-boundary';
3
3
 
4
4
  /**
@@ -13,7 +13,7 @@ import { ErrorBoundaryPropsWithRender } from 'react-error-boundary';
13
13
  * subtle centred spinner that inherits the app's own text colour. The host themes
14
14
  * the iframe canvas behind it (sandbox `themeCanvas.ts`), so there is no white.
15
15
  */
16
- declare const defaultLoadingComponent: () => react_jsx_runtime.JSX.Element;
16
+ declare const defaultLoadingComponent: () => react.JSX.Element;
17
17
  declare const defaultErrorComponent: ErrorBoundaryPropsWithRender["fallbackRender"];
18
18
 
19
19
  export { defaultErrorComponent, defaultLoadingComponent };
@@ -1,4 +1,4 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
1
+ import * as react from 'react';
2
2
  import { ErrorBoundaryPropsWithRender } from 'react-error-boundary';
3
3
 
4
4
  /**
@@ -13,7 +13,7 @@ import { ErrorBoundaryPropsWithRender } from 'react-error-boundary';
13
13
  * subtle centred spinner that inherits the app's own text colour. The host themes
14
14
  * the iframe canvas behind it (sandbox `themeCanvas.ts`), so there is no white.
15
15
  */
16
- declare const defaultLoadingComponent: () => react_jsx_runtime.JSX.Element;
16
+ declare const defaultLoadingComponent: () => react.JSX.Element;
17
17
  declare const defaultErrorComponent: ErrorBoundaryPropsWithRender["fallbackRender"];
18
18
 
19
19
  export { defaultErrorComponent, defaultLoadingComponent };
@@ -1,5 +1,5 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
1
+ import * as react from 'react';
2
2
 
3
- declare const ErrorNotFound: () => react_jsx_runtime.JSX.Element;
3
+ declare const ErrorNotFound: () => react.JSX.Element;
4
4
 
5
5
  export { ErrorNotFound };
@@ -1,5 +1,5 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
1
+ import * as react from 'react';
2
2
 
3
- declare const ErrorNotFound: () => react_jsx_runtime.JSX.Element;
3
+ declare const ErrorNotFound: () => react.JSX.Element;
4
4
 
5
5
  export { ErrorNotFound };
package/dist/index.d.cts CHANGED
@@ -34,7 +34,7 @@ export { StreamError, StreamFrame, StreamTransport, consumeStream, protocolStrea
34
34
  export { EvaluationContext, FileQueryResult, FilesMetadata, Metadata, MetadataQueryEntry, MetadataQueryFunction, MetadataQueryResult, ModuleExports } from './sandboxTypes.cjs';
35
35
  export { SafeContent, SafeContentProps } from './safeContent/SafeContent.cjs';
36
36
  export { RenderMdastOptions, SafeContentComponents, renderMdast } from './safeContent/renderMdast.cjs';
37
- export { SafeMdastNode, SafeMdxAttribute, parseSafeMdast } from './safeContent/parseSafeMdast.cjs';
37
+ export { ParseSafeMdastOptions, SafeMdastNode, SafeMdxAttribute, parseSafeMdast } from './safeContent/parseSafeMdast.cjs';
38
38
  export { sanitizeUrl } from './safeContent/sanitizeUrl.cjs';
39
39
  export { WikiLinkToken, WikiPart, parseWikiInner, splitWikiLinks } from './safeContent/wikilink.cjs';
40
40
  export { Admonition, AdmonitionType } from './components/Admonition.cjs';
@@ -46,6 +46,5 @@ export { WikiLink } from './components/WikiLink.cjs';
46
46
  import 'react';
47
47
  import './TinkerableContext.cjs';
48
48
  import './RoutingSpec.cjs';
49
- import 'react/jsx-runtime';
50
49
  import './components/defaults.cjs';
51
50
  import 'react-error-boundary';
package/dist/index.d.ts CHANGED
@@ -34,7 +34,7 @@ export { StreamError, StreamFrame, StreamTransport, consumeStream, protocolStrea
34
34
  export { EvaluationContext, FileQueryResult, FilesMetadata, Metadata, MetadataQueryEntry, MetadataQueryFunction, MetadataQueryResult, ModuleExports } from './sandboxTypes.js';
35
35
  export { SafeContent, SafeContentProps } from './safeContent/SafeContent.js';
36
36
  export { RenderMdastOptions, SafeContentComponents, renderMdast } from './safeContent/renderMdast.js';
37
- export { SafeMdastNode, SafeMdxAttribute, parseSafeMdast } from './safeContent/parseSafeMdast.js';
37
+ export { ParseSafeMdastOptions, SafeMdastNode, SafeMdxAttribute, parseSafeMdast } from './safeContent/parseSafeMdast.js';
38
38
  export { sanitizeUrl } from './safeContent/sanitizeUrl.js';
39
39
  export { WikiLinkToken, WikiPart, parseWikiInner, splitWikiLinks } from './safeContent/wikilink.js';
40
40
  export { Admonition, AdmonitionType } from './components/Admonition.js';
@@ -46,6 +46,5 @@ export { WikiLink } from './components/WikiLink.js';
46
46
  import 'react';
47
47
  import './TinkerableContext.js';
48
48
  import './RoutingSpec.js';
49
- import 'react/jsx-runtime';
50
49
  import './components/defaults.js';
51
50
  import 'react-error-boundary';
package/dist/llm.cjs CHANGED
@@ -27,7 +27,12 @@ module.exports = __toCommonJS(llm_exports);
27
27
  var import_catalog = require("./catalog");
28
28
  var import_pushChannel = require("./pushChannel");
29
29
  function chat(req) {
30
- return (0, import_catalog.invokeStream)("llm:chat", req);
30
+ const { signal, ...params } = req;
31
+ return (0, import_catalog.invokeStream)(
32
+ "llm:chat",
33
+ params,
34
+ signal
35
+ );
31
36
  }
32
37
  const channel = (0, import_pushChannel.createPushChannel)({
33
38
  pushType: "llm-provider",
package/dist/llm.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/llm.ts"],"sourcesContent":["// Provider-agnostic LLM chat — the `llm.chat@1` slot (SERVICE_PROVIDERS_SPEC;\n// LLM_AND_AGENTS_SPEC §8 D5).\n//\n// An app calls ONE chat slot and never worries about which provider the user has a\n// key for: the HOST resolves which vendor answers from the key the user holds\n// (`SecretView.boundOrigin`) plus their `preferredImplementation` choice, normalizes\n// the wire format, injects the key host-side at the §6 net:fetch point (the\n// look-at-nothing proxy), and streams normalized deltas back. The app never names a\n// vendor, never sees the key, and needs NO `net:fetch`/`secrets` grant of its own —\n// only the `llm:chat` capability (elevated, app-scoped: a fork earns it by consent).\n//\n// Inert until the host implements `protocol-llm` (the `chat` stream) + the\n// `llm-provider` describe channel; the contract ships here so apps (the file-explorer\n// summarize fork) can be written against it — exactly how `secrets.ts` shipped ahead\n// of `protocol-secrets`.\nimport { invokeStream } from './catalog';\nimport { createPushChannel } from './pushChannel';\n\n/** Who authored a {@link ChatMessage}. */\nexport type ChatRole = 'system' | 'user' | 'assistant' | 'tool';\n\n/** A part of a message. `image` is only honored when the resolved provider\n * advertises `features.vision` (§2.5); `tool-use`/`tool-result` only when it\n * advertises `features.tools` — branch on {@link describeChat} first. */\nexport type ContentPart =\n | { type: 'text'; text: string }\n | { type: 'image'; mimeType: string; data: string } // data: base64, no data: URL prefix\n // A tool call the model emitted on a prior `assistant` turn — replay it in the\n // conversation so a follow-up request carries the agentic history. Pairs with the\n // streamed `tool-call` {@link ChatDelta} that first surfaced it.\n | { type: 'tool-use'; id: string; name: string; input: Record<string, unknown> }\n // The result of executing a `tool-use`, fed back so the model can continue. Carried\n // on a `user`/`tool`-role message; `toolCallId` matches the `tool-use` `id`.\n | { type: 'tool-result'; toolCallId: string; content: string; isError?: boolean };\n\n/** One message in a {@link ChatRequest}: a role plus its content parts. */\nexport interface ChatMessage {\n role: ChatRole;\n content: ContentPart[];\n}\n\n/** A tool the model may call — honored only when `features.tools`. */\nexport interface ToolDef {\n name: string;\n description?: string;\n /** JSON-Schema for the tool's arguments. */\n inputSchema: Record<string, unknown>;\n}\n\n/** A host-brokered chat completion request: the messages plus optional tools,\n * response format, and model hint (each honored per the provider's features). */\nexport interface ChatRequest {\n messages: ChatMessage[];\n /** Honored only when the resolved provider advertises `features.tools`. */\n tools?: ToolDef[];\n /** `'json'` honored only when `features.jsonMode`. Defaults to `'text'`. */\n responseFormat?: 'text' | 'json';\n maxTokens?: number;\n /** An ABSTRACT tier hint, never a vendor model id — the host maps it to a concrete\n * model on the resolved provider. Omit to take the provider's default. */\n modelHint?: 'fast' | 'smart';\n}\n\n/** One streamed chunk. Consumers typically accumulate `text-delta`s. */\nexport type ChatDelta =\n | { type: 'text-delta'; text: string }\n | { type: 'tool-call'; id: string; name: string; input: unknown }\n | { type: 'usage'; inputTokens: number; outputTokens: number };\n\n/** Why generation stopped: natural `end`, `length` cap, a `tool` call, or content `filtered`. */\nexport type ChatStopReason = 'end' | 'length' | 'tool' | 'filtered';\n\n/** The terminal value of the {@link chat} stream. */\nexport interface ChatResult {\n stopReason: ChatStopReason;\n}\n\n/**\n * Stream a chat completion from whichever provider the user has configured.\n *\n * ```ts\n * let summary = '';\n * for await (const d of chat({ messages: [{ role: 'user', content: [{ type: 'text', text }] }] })) {\n * if (d.type === 'text-delta') summary += d.text;\n * }\n * ```\n *\n * Requires the `llm:chat` capability. If no provider is bound the host fails the\n * stream into the SP-7 connect-me prompt (the user adds a key) — the generator\n * throws with `code: 'auth-required'`; an un-granted call throws `forbidden`.\n */\nexport function chat(req: ChatRequest): AsyncGenerator<ChatDelta, ChatResult, void> {\n return invokeStream<ChatDelta, ChatResult>('llm:chat', req as unknown as Record<string, unknown>);\n}\n\n/** The resolved provider's advertised abilities (SERVICE_PROVIDERS_SPEC §2.5) — read\n * to branch/degrade (offer image upload only when `vision`). */\nexport interface ChatFeatures {\n vision: boolean;\n tools: boolean;\n jsonMode: boolean;\n maxContextTokens: number;\n}\n\n/** Info about the provider the host resolved for this app. `null` when no provider\n * is bound (SP-7: prompt the user to add a key before calling {@link chat}). */\nexport interface ChatProviderInfo {\n /** Opaque provider id, e.g. `llm.chat.anthropic` — never a vendor secret or model id. */\n providerId: string;\n /** True for Host-proxied providers (host-vouched, SP-9); false for app-level ones,\n * whose `features` are an untrusted claim. */\n hostVouched: boolean;\n features: ChatFeatures;\n}\n\n// The `llm-provider` describe channel (Recipe A): the host pushes the resolved\n// provider info on change and replays it on register-frame, gated by `llm:chat`.\n// A message with no `provider` key is ignored; an explicit `null` means \"no provider\n// bound\" (distinct from \"not yet answered\", which keeps the `initial` null).\nconst channel = createPushChannel<ChatProviderInfo | null>({\n pushType: 'llm-provider',\n requestType: 'request-llm-provider',\n initial: null,\n parse: (msg) =>\n 'provider' in msg ? (msg.provider as ChatProviderInfo | null) : undefined,\n});\n\n/** The provider the host resolved for this app (or `null` if none bound). Poll for a\n * one-off read; use {@link onChatProviderChange}/{@link useChatProvider} to react. */\nexport const describeChat = (): ChatProviderInfo | null => channel.get();\n\n/** Subscribe to provider changes (key added/revoked, preference changed). Invoked\n * immediately with the current value, then on every change. Returns unsubscribe. */\nexport const onChatProviderChange = (\n listener: (provider: ChatProviderInfo | null) => void,\n): (() => void) => channel.onChange(listener);\n\n/** React hook returning the resolved chat provider (or `null`), re-rendering on\n * change — gate the summarize affordance on `provider !== null`. */\nexport const useChatProvider = (): ChatProviderInfo | null => channel.use();\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAeA,qBAA6B;AAC7B,yBAAkC;AA2E3B,SAAS,KAAK,KAA+D;AAClF,aAAO,6BAAoC,YAAY,GAAyC;AAClG;AA0BA,MAAM,cAAU,sCAA2C;AAAA,EACzD,UAAU;AAAA,EACV,aAAa;AAAA,EACb,SAAS;AAAA,EACT,OAAO,CAAC,QACN,cAAc,MAAO,IAAI,WAAuC;AACpE,CAAC;AAIM,MAAM,eAAe,MAA+B,QAAQ,IAAI;AAIhE,MAAM,uBAAuB,CAClC,aACiB,QAAQ,SAAS,QAAQ;AAIrC,MAAM,kBAAkB,MAA+B,QAAQ,IAAI;","names":[]}
1
+ {"version":3,"sources":["../src/llm.ts"],"sourcesContent":["// Provider-agnostic LLM chat — the `llm.chat@1` slot (SERVICE_PROVIDERS_SPEC;\n// LLM_AND_AGENTS_SPEC §8 D5).\n//\n// An app calls ONE chat slot and never worries about which provider the user has a\n// key for: the HOST resolves which vendor answers from the key the user holds\n// (`SecretView.boundOrigin`) plus their `preferredImplementation` choice, normalizes\n// the wire format, injects the key host-side at the §6 net:fetch point (the\n// look-at-nothing proxy), and streams normalized deltas back. The app never names a\n// vendor, never sees the key, and needs NO `net:fetch`/`secrets` grant of its own —\n// only the `llm:chat` capability (elevated, app-scoped: a fork earns it by consent).\n//\n// Inert until the host implements `protocol-llm` (the `chat` stream) + the\n// `llm-provider` describe channel; the contract ships here so apps (the file-explorer\n// summarize fork) can be written against it — exactly how `secrets.ts` shipped ahead\n// of `protocol-secrets`.\nimport { invokeStream } from './catalog';\nimport { createPushChannel } from './pushChannel';\n\n/** Who authored a {@link ChatMessage}. */\nexport type ChatRole = 'system' | 'user' | 'assistant' | 'tool';\n\n/** A part of a message. `image` is only honored when the resolved provider\n * advertises `features.vision` (§2.5); `tool-use`/`tool-result` only when it\n * advertises `features.tools` — branch on {@link describeChat} first. */\nexport type ContentPart =\n | { type: 'text'; text: string }\n | { type: 'image'; mimeType: string; data: string } // data: base64, no data: URL prefix\n // A tool call the model emitted on a prior `assistant` turn — replay it in the\n // conversation so a follow-up request carries the agentic history. Pairs with the\n // streamed `tool-call` {@link ChatDelta} that first surfaced it.\n | { type: 'tool-use'; id: string; name: string; input: Record<string, unknown> }\n // The result of executing a `tool-use`, fed back so the model can continue. Carried\n // on a `user`/`tool`-role message; `toolCallId` matches the `tool-use` `id`.\n | { type: 'tool-result'; toolCallId: string; content: string; isError?: boolean };\n\n/** One message in a {@link ChatRequest}: a role plus its content parts. */\nexport interface ChatMessage {\n role: ChatRole;\n content: ContentPart[];\n}\n\n/** A tool the model may call — honored only when `features.tools`. */\nexport interface ToolDef {\n name: string;\n description?: string;\n /** JSON-Schema for the tool's arguments. */\n inputSchema: Record<string, unknown>;\n}\n\n/** A host-brokered chat completion request: the messages plus optional tools,\n * response format, and model hint (each honored per the provider's features). */\nexport interface ChatRequest {\n messages: ChatMessage[];\n /** Honored only when the resolved provider advertises `features.tools`. */\n tools?: ToolDef[];\n /** `'json'` honored only when `features.jsonMode`. Defaults to `'text'`. */\n responseFormat?: 'text' | 'json';\n maxTokens?: number;\n /** An ABSTRACT tier hint, never a vendor model id — the host maps it to a concrete\n * model on the resolved provider. Omit to take the provider's default. */\n modelHint?: 'fast' | 'smart';\n /** Abort the completion mid-stream. When it fires, the SDK sends the host a cancel\n * frame so the host aborts the upstream provider request and STOPS BILLING the\n * user's key — not merely stops the app-side iterator (LLM_AND_AGENTS_SPEC §3.3\n * \"abort the in-flight LLM request\", R3-224). Not sent over the wire (an\n * `AbortSignal` isn't serializable); handled SDK-side. */\n signal?: AbortSignal;\n}\n\n/** One streamed chunk. Consumers typically accumulate `text-delta`s. */\nexport type ChatDelta =\n | { type: 'text-delta'; text: string }\n | { type: 'tool-call'; id: string; name: string; input: unknown }\n | { type: 'usage'; inputTokens: number; outputTokens: number };\n\n/** Why generation stopped: natural `end`, `length` cap, a `tool` call, or content `filtered`. */\nexport type ChatStopReason = 'end' | 'length' | 'tool' | 'filtered';\n\n/** The terminal value of the {@link chat} stream. */\nexport interface ChatResult {\n stopReason: ChatStopReason;\n}\n\n/**\n * Stream a chat completion from whichever provider the user has configured.\n *\n * ```ts\n * let summary = '';\n * for await (const d of chat({ messages: [{ role: 'user', content: [{ type: 'text', text }] }] })) {\n * if (d.type === 'text-delta') summary += d.text;\n * }\n * ```\n *\n * Requires the `llm:chat` capability. If no provider is bound the host fails the\n * stream into the SP-7 connect-me prompt (the user adds a key) — the generator\n * throws with `code: 'auth-required'`; an un-granted call throws `forbidden`.\n */\nexport function chat(req: ChatRequest): AsyncGenerator<ChatDelta, ChatResult, void> {\n // Peel `signal` out of the request before it becomes wire params — an AbortSignal\n // can't cross the postMessage boundary as data; it drives the SDK-side cancel frame.\n const { signal, ...params } = req;\n return invokeStream<ChatDelta, ChatResult>(\n 'llm:chat',\n params as unknown as Record<string, unknown>,\n signal,\n );\n}\n\n/** The resolved provider's advertised abilities (SERVICE_PROVIDERS_SPEC §2.5) — read\n * to branch/degrade (offer image upload only when `vision`). */\nexport interface ChatFeatures {\n vision: boolean;\n tools: boolean;\n jsonMode: boolean;\n maxContextTokens: number;\n}\n\n/** Info about the provider the host resolved for this app. `null` when no provider\n * is bound (SP-7: prompt the user to add a key before calling {@link chat}). */\nexport interface ChatProviderInfo {\n /** Opaque provider id, e.g. `llm.chat.anthropic` — never a vendor secret or model id. */\n providerId: string;\n /** True for Host-proxied providers (host-vouched, SP-9); false for app-level ones,\n * whose `features` are an untrusted claim. */\n hostVouched: boolean;\n features: ChatFeatures;\n}\n\n// The `llm-provider` describe channel (Recipe A): the host pushes the resolved\n// provider info on change and replays it on register-frame, gated by `llm:chat`.\n// A message with no `provider` key is ignored; an explicit `null` means \"no provider\n// bound\" (distinct from \"not yet answered\", which keeps the `initial` null).\nconst channel = createPushChannel<ChatProviderInfo | null>({\n pushType: 'llm-provider',\n requestType: 'request-llm-provider',\n initial: null,\n parse: (msg) =>\n 'provider' in msg ? (msg.provider as ChatProviderInfo | null) : undefined,\n});\n\n/** The provider the host resolved for this app (or `null` if none bound). Poll for a\n * one-off read; use {@link onChatProviderChange}/{@link useChatProvider} to react. */\nexport const describeChat = (): ChatProviderInfo | null => channel.get();\n\n/** Subscribe to provider changes (key added/revoked, preference changed). Invoked\n * immediately with the current value, then on every change. Returns unsubscribe. */\nexport const onChatProviderChange = (\n listener: (provider: ChatProviderInfo | null) => void,\n): (() => void) => channel.onChange(listener);\n\n/** React hook returning the resolved chat provider (or `null`), re-rendering on\n * change — gate the summarize affordance on `provider !== null`. */\nexport const useChatProvider = (): ChatProviderInfo | null => channel.use();\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAeA,qBAA6B;AAC7B,yBAAkC;AAiF3B,SAAS,KAAK,KAA+D;AAGlF,QAAM,EAAE,QAAQ,GAAG,OAAO,IAAI;AAC9B,aAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AA0BA,MAAM,cAAU,sCAA2C;AAAA,EACzD,UAAU;AAAA,EACV,aAAa;AAAA,EACb,SAAS;AAAA,EACT,OAAO,CAAC,QACN,cAAc,MAAO,IAAI,WAAuC;AACpE,CAAC;AAIM,MAAM,eAAe,MAA+B,QAAQ,IAAI;AAIhE,MAAM,uBAAuB,CAClC,aACiB,QAAQ,SAAS,QAAQ;AAIrC,MAAM,kBAAkB,MAA+B,QAAQ,IAAI;","names":[]}
package/dist/llm.d.cts CHANGED
@@ -45,6 +45,12 @@ interface ChatRequest {
45
45
  /** An ABSTRACT tier hint, never a vendor model id — the host maps it to a concrete
46
46
  * model on the resolved provider. Omit to take the provider's default. */
47
47
  modelHint?: 'fast' | 'smart';
48
+ /** Abort the completion mid-stream. When it fires, the SDK sends the host a cancel
49
+ * frame so the host aborts the upstream provider request and STOPS BILLING the
50
+ * user's key — not merely stops the app-side iterator (LLM_AND_AGENTS_SPEC §3.3
51
+ * "abort the in-flight LLM request", R3-224). Not sent over the wire (an
52
+ * `AbortSignal` isn't serializable); handled SDK-side. */
53
+ signal?: AbortSignal;
48
54
  }
49
55
  /** One streamed chunk. Consumers typically accumulate `text-delta`s. */
50
56
  type ChatDelta = {
package/dist/llm.d.ts CHANGED
@@ -45,6 +45,12 @@ interface ChatRequest {
45
45
  /** An ABSTRACT tier hint, never a vendor model id — the host maps it to a concrete
46
46
  * model on the resolved provider. Omit to take the provider's default. */
47
47
  modelHint?: 'fast' | 'smart';
48
+ /** Abort the completion mid-stream. When it fires, the SDK sends the host a cancel
49
+ * frame so the host aborts the upstream provider request and STOPS BILLING the
50
+ * user's key — not merely stops the app-side iterator (LLM_AND_AGENTS_SPEC §3.3
51
+ * "abort the in-flight LLM request", R3-224). Not sent over the wire (an
52
+ * `AbortSignal` isn't serializable); handled SDK-side. */
53
+ signal?: AbortSignal;
48
54
  }
49
55
  /** One streamed chunk. Consumers typically accumulate `text-delta`s. */
50
56
  type ChatDelta = {
package/dist/llm.js CHANGED
@@ -1,7 +1,12 @@
1
1
  import { invokeStream } from "./catalog";
2
2
  import { createPushChannel } from "./pushChannel";
3
3
  function chat(req) {
4
- return invokeStream("llm:chat", req);
4
+ const { signal, ...params } = req;
5
+ return invokeStream(
6
+ "llm:chat",
7
+ params,
8
+ signal
9
+ );
5
10
  }
6
11
  const channel = createPushChannel({
7
12
  pushType: "llm-provider",
package/dist/llm.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/llm.ts"],"sourcesContent":["// Provider-agnostic LLM chat — the `llm.chat@1` slot (SERVICE_PROVIDERS_SPEC;\n// LLM_AND_AGENTS_SPEC §8 D5).\n//\n// An app calls ONE chat slot and never worries about which provider the user has a\n// key for: the HOST resolves which vendor answers from the key the user holds\n// (`SecretView.boundOrigin`) plus their `preferredImplementation` choice, normalizes\n// the wire format, injects the key host-side at the §6 net:fetch point (the\n// look-at-nothing proxy), and streams normalized deltas back. The app never names a\n// vendor, never sees the key, and needs NO `net:fetch`/`secrets` grant of its own —\n// only the `llm:chat` capability (elevated, app-scoped: a fork earns it by consent).\n//\n// Inert until the host implements `protocol-llm` (the `chat` stream) + the\n// `llm-provider` describe channel; the contract ships here so apps (the file-explorer\n// summarize fork) can be written against it — exactly how `secrets.ts` shipped ahead\n// of `protocol-secrets`.\nimport { invokeStream } from './catalog';\nimport { createPushChannel } from './pushChannel';\n\n/** Who authored a {@link ChatMessage}. */\nexport type ChatRole = 'system' | 'user' | 'assistant' | 'tool';\n\n/** A part of a message. `image` is only honored when the resolved provider\n * advertises `features.vision` (§2.5); `tool-use`/`tool-result` only when it\n * advertises `features.tools` — branch on {@link describeChat} first. */\nexport type ContentPart =\n | { type: 'text'; text: string }\n | { type: 'image'; mimeType: string; data: string } // data: base64, no data: URL prefix\n // A tool call the model emitted on a prior `assistant` turn — replay it in the\n // conversation so a follow-up request carries the agentic history. Pairs with the\n // streamed `tool-call` {@link ChatDelta} that first surfaced it.\n | { type: 'tool-use'; id: string; name: string; input: Record<string, unknown> }\n // The result of executing a `tool-use`, fed back so the model can continue. Carried\n // on a `user`/`tool`-role message; `toolCallId` matches the `tool-use` `id`.\n | { type: 'tool-result'; toolCallId: string; content: string; isError?: boolean };\n\n/** One message in a {@link ChatRequest}: a role plus its content parts. */\nexport interface ChatMessage {\n role: ChatRole;\n content: ContentPart[];\n}\n\n/** A tool the model may call — honored only when `features.tools`. */\nexport interface ToolDef {\n name: string;\n description?: string;\n /** JSON-Schema for the tool's arguments. */\n inputSchema: Record<string, unknown>;\n}\n\n/** A host-brokered chat completion request: the messages plus optional tools,\n * response format, and model hint (each honored per the provider's features). */\nexport interface ChatRequest {\n messages: ChatMessage[];\n /** Honored only when the resolved provider advertises `features.tools`. */\n tools?: ToolDef[];\n /** `'json'` honored only when `features.jsonMode`. Defaults to `'text'`. */\n responseFormat?: 'text' | 'json';\n maxTokens?: number;\n /** An ABSTRACT tier hint, never a vendor model id — the host maps it to a concrete\n * model on the resolved provider. Omit to take the provider's default. */\n modelHint?: 'fast' | 'smart';\n}\n\n/** One streamed chunk. Consumers typically accumulate `text-delta`s. */\nexport type ChatDelta =\n | { type: 'text-delta'; text: string }\n | { type: 'tool-call'; id: string; name: string; input: unknown }\n | { type: 'usage'; inputTokens: number; outputTokens: number };\n\n/** Why generation stopped: natural `end`, `length` cap, a `tool` call, or content `filtered`. */\nexport type ChatStopReason = 'end' | 'length' | 'tool' | 'filtered';\n\n/** The terminal value of the {@link chat} stream. */\nexport interface ChatResult {\n stopReason: ChatStopReason;\n}\n\n/**\n * Stream a chat completion from whichever provider the user has configured.\n *\n * ```ts\n * let summary = '';\n * for await (const d of chat({ messages: [{ role: 'user', content: [{ type: 'text', text }] }] })) {\n * if (d.type === 'text-delta') summary += d.text;\n * }\n * ```\n *\n * Requires the `llm:chat` capability. If no provider is bound the host fails the\n * stream into the SP-7 connect-me prompt (the user adds a key) — the generator\n * throws with `code: 'auth-required'`; an un-granted call throws `forbidden`.\n */\nexport function chat(req: ChatRequest): AsyncGenerator<ChatDelta, ChatResult, void> {\n return invokeStream<ChatDelta, ChatResult>('llm:chat', req as unknown as Record<string, unknown>);\n}\n\n/** The resolved provider's advertised abilities (SERVICE_PROVIDERS_SPEC §2.5) — read\n * to branch/degrade (offer image upload only when `vision`). */\nexport interface ChatFeatures {\n vision: boolean;\n tools: boolean;\n jsonMode: boolean;\n maxContextTokens: number;\n}\n\n/** Info about the provider the host resolved for this app. `null` when no provider\n * is bound (SP-7: prompt the user to add a key before calling {@link chat}). */\nexport interface ChatProviderInfo {\n /** Opaque provider id, e.g. `llm.chat.anthropic` — never a vendor secret or model id. */\n providerId: string;\n /** True for Host-proxied providers (host-vouched, SP-9); false for app-level ones,\n * whose `features` are an untrusted claim. */\n hostVouched: boolean;\n features: ChatFeatures;\n}\n\n// The `llm-provider` describe channel (Recipe A): the host pushes the resolved\n// provider info on change and replays it on register-frame, gated by `llm:chat`.\n// A message with no `provider` key is ignored; an explicit `null` means \"no provider\n// bound\" (distinct from \"not yet answered\", which keeps the `initial` null).\nconst channel = createPushChannel<ChatProviderInfo | null>({\n pushType: 'llm-provider',\n requestType: 'request-llm-provider',\n initial: null,\n parse: (msg) =>\n 'provider' in msg ? (msg.provider as ChatProviderInfo | null) : undefined,\n});\n\n/** The provider the host resolved for this app (or `null` if none bound). Poll for a\n * one-off read; use {@link onChatProviderChange}/{@link useChatProvider} to react. */\nexport const describeChat = (): ChatProviderInfo | null => channel.get();\n\n/** Subscribe to provider changes (key added/revoked, preference changed). Invoked\n * immediately with the current value, then on every change. Returns unsubscribe. */\nexport const onChatProviderChange = (\n listener: (provider: ChatProviderInfo | null) => void,\n): (() => void) => channel.onChange(listener);\n\n/** React hook returning the resolved chat provider (or `null`), re-rendering on\n * change — gate the summarize affordance on `provider !== null`. */\nexport const useChatProvider = (): ChatProviderInfo | null => channel.use();\n"],"mappings":"AAeA,SAAS,oBAAoB;AAC7B,SAAS,yBAAyB;AA2E3B,SAAS,KAAK,KAA+D;AAClF,SAAO,aAAoC,YAAY,GAAyC;AAClG;AA0BA,MAAM,UAAU,kBAA2C;AAAA,EACzD,UAAU;AAAA,EACV,aAAa;AAAA,EACb,SAAS;AAAA,EACT,OAAO,CAAC,QACN,cAAc,MAAO,IAAI,WAAuC;AACpE,CAAC;AAIM,MAAM,eAAe,MAA+B,QAAQ,IAAI;AAIhE,MAAM,uBAAuB,CAClC,aACiB,QAAQ,SAAS,QAAQ;AAIrC,MAAM,kBAAkB,MAA+B,QAAQ,IAAI;","names":[]}
1
+ {"version":3,"sources":["../src/llm.ts"],"sourcesContent":["// Provider-agnostic LLM chat — the `llm.chat@1` slot (SERVICE_PROVIDERS_SPEC;\n// LLM_AND_AGENTS_SPEC §8 D5).\n//\n// An app calls ONE chat slot and never worries about which provider the user has a\n// key for: the HOST resolves which vendor answers from the key the user holds\n// (`SecretView.boundOrigin`) plus their `preferredImplementation` choice, normalizes\n// the wire format, injects the key host-side at the §6 net:fetch point (the\n// look-at-nothing proxy), and streams normalized deltas back. The app never names a\n// vendor, never sees the key, and needs NO `net:fetch`/`secrets` grant of its own —\n// only the `llm:chat` capability (elevated, app-scoped: a fork earns it by consent).\n//\n// Inert until the host implements `protocol-llm` (the `chat` stream) + the\n// `llm-provider` describe channel; the contract ships here so apps (the file-explorer\n// summarize fork) can be written against it — exactly how `secrets.ts` shipped ahead\n// of `protocol-secrets`.\nimport { invokeStream } from './catalog';\nimport { createPushChannel } from './pushChannel';\n\n/** Who authored a {@link ChatMessage}. */\nexport type ChatRole = 'system' | 'user' | 'assistant' | 'tool';\n\n/** A part of a message. `image` is only honored when the resolved provider\n * advertises `features.vision` (§2.5); `tool-use`/`tool-result` only when it\n * advertises `features.tools` — branch on {@link describeChat} first. */\nexport type ContentPart =\n | { type: 'text'; text: string }\n | { type: 'image'; mimeType: string; data: string } // data: base64, no data: URL prefix\n // A tool call the model emitted on a prior `assistant` turn — replay it in the\n // conversation so a follow-up request carries the agentic history. Pairs with the\n // streamed `tool-call` {@link ChatDelta} that first surfaced it.\n | { type: 'tool-use'; id: string; name: string; input: Record<string, unknown> }\n // The result of executing a `tool-use`, fed back so the model can continue. Carried\n // on a `user`/`tool`-role message; `toolCallId` matches the `tool-use` `id`.\n | { type: 'tool-result'; toolCallId: string; content: string; isError?: boolean };\n\n/** One message in a {@link ChatRequest}: a role plus its content parts. */\nexport interface ChatMessage {\n role: ChatRole;\n content: ContentPart[];\n}\n\n/** A tool the model may call — honored only when `features.tools`. */\nexport interface ToolDef {\n name: string;\n description?: string;\n /** JSON-Schema for the tool's arguments. */\n inputSchema: Record<string, unknown>;\n}\n\n/** A host-brokered chat completion request: the messages plus optional tools,\n * response format, and model hint (each honored per the provider's features). */\nexport interface ChatRequest {\n messages: ChatMessage[];\n /** Honored only when the resolved provider advertises `features.tools`. */\n tools?: ToolDef[];\n /** `'json'` honored only when `features.jsonMode`. Defaults to `'text'`. */\n responseFormat?: 'text' | 'json';\n maxTokens?: number;\n /** An ABSTRACT tier hint, never a vendor model id — the host maps it to a concrete\n * model on the resolved provider. Omit to take the provider's default. */\n modelHint?: 'fast' | 'smart';\n /** Abort the completion mid-stream. When it fires, the SDK sends the host a cancel\n * frame so the host aborts the upstream provider request and STOPS BILLING the\n * user's key — not merely stops the app-side iterator (LLM_AND_AGENTS_SPEC §3.3\n * \"abort the in-flight LLM request\", R3-224). Not sent over the wire (an\n * `AbortSignal` isn't serializable); handled SDK-side. */\n signal?: AbortSignal;\n}\n\n/** One streamed chunk. Consumers typically accumulate `text-delta`s. */\nexport type ChatDelta =\n | { type: 'text-delta'; text: string }\n | { type: 'tool-call'; id: string; name: string; input: unknown }\n | { type: 'usage'; inputTokens: number; outputTokens: number };\n\n/** Why generation stopped: natural `end`, `length` cap, a `tool` call, or content `filtered`. */\nexport type ChatStopReason = 'end' | 'length' | 'tool' | 'filtered';\n\n/** The terminal value of the {@link chat} stream. */\nexport interface ChatResult {\n stopReason: ChatStopReason;\n}\n\n/**\n * Stream a chat completion from whichever provider the user has configured.\n *\n * ```ts\n * let summary = '';\n * for await (const d of chat({ messages: [{ role: 'user', content: [{ type: 'text', text }] }] })) {\n * if (d.type === 'text-delta') summary += d.text;\n * }\n * ```\n *\n * Requires the `llm:chat` capability. If no provider is bound the host fails the\n * stream into the SP-7 connect-me prompt (the user adds a key) — the generator\n * throws with `code: 'auth-required'`; an un-granted call throws `forbidden`.\n */\nexport function chat(req: ChatRequest): AsyncGenerator<ChatDelta, ChatResult, void> {\n // Peel `signal` out of the request before it becomes wire params — an AbortSignal\n // can't cross the postMessage boundary as data; it drives the SDK-side cancel frame.\n const { signal, ...params } = req;\n return invokeStream<ChatDelta, ChatResult>(\n 'llm:chat',\n params as unknown as Record<string, unknown>,\n signal,\n );\n}\n\n/** The resolved provider's advertised abilities (SERVICE_PROVIDERS_SPEC §2.5) — read\n * to branch/degrade (offer image upload only when `vision`). */\nexport interface ChatFeatures {\n vision: boolean;\n tools: boolean;\n jsonMode: boolean;\n maxContextTokens: number;\n}\n\n/** Info about the provider the host resolved for this app. `null` when no provider\n * is bound (SP-7: prompt the user to add a key before calling {@link chat}). */\nexport interface ChatProviderInfo {\n /** Opaque provider id, e.g. `llm.chat.anthropic` — never a vendor secret or model id. */\n providerId: string;\n /** True for Host-proxied providers (host-vouched, SP-9); false for app-level ones,\n * whose `features` are an untrusted claim. */\n hostVouched: boolean;\n features: ChatFeatures;\n}\n\n// The `llm-provider` describe channel (Recipe A): the host pushes the resolved\n// provider info on change and replays it on register-frame, gated by `llm:chat`.\n// A message with no `provider` key is ignored; an explicit `null` means \"no provider\n// bound\" (distinct from \"not yet answered\", which keeps the `initial` null).\nconst channel = createPushChannel<ChatProviderInfo | null>({\n pushType: 'llm-provider',\n requestType: 'request-llm-provider',\n initial: null,\n parse: (msg) =>\n 'provider' in msg ? (msg.provider as ChatProviderInfo | null) : undefined,\n});\n\n/** The provider the host resolved for this app (or `null` if none bound). Poll for a\n * one-off read; use {@link onChatProviderChange}/{@link useChatProvider} to react. */\nexport const describeChat = (): ChatProviderInfo | null => channel.get();\n\n/** Subscribe to provider changes (key added/revoked, preference changed). Invoked\n * immediately with the current value, then on every change. Returns unsubscribe. */\nexport const onChatProviderChange = (\n listener: (provider: ChatProviderInfo | null) => void,\n): (() => void) => channel.onChange(listener);\n\n/** React hook returning the resolved chat provider (or `null`), re-rendering on\n * change — gate the summarize affordance on `provider !== null`. */\nexport const useChatProvider = (): ChatProviderInfo | null => channel.use();\n"],"mappings":"AAeA,SAAS,oBAAoB;AAC7B,SAAS,yBAAyB;AAiF3B,SAAS,KAAK,KAA+D;AAGlF,QAAM,EAAE,QAAQ,GAAG,OAAO,IAAI;AAC9B,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AA0BA,MAAM,UAAU,kBAA2C;AAAA,EACzD,UAAU;AAAA,EACV,aAAa;AAAA,EACb,SAAS;AAAA,EACT,OAAO,CAAC,QACN,cAAc,MAAO,IAAI,WAAuC;AACpE,CAAC;AAIM,MAAM,eAAe,MAA+B,QAAQ,IAAI;AAIhE,MAAM,uBAAuB,CAClC,aACiB,QAAQ,SAAS,QAAQ;AAIrC,MAAM,kBAAkB,MAA+B,QAAQ,IAAI;","names":[]}
@@ -1,4 +1,4 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
1
+ import * as react from 'react';
2
2
  import { ReactNode, CSSProperties } from 'react';
3
3
 
4
4
  /** Loading timing constants, matching the host (LOADING_UX_SPEC §3, the 2026-06-22
@@ -18,14 +18,14 @@ declare function SkeletonRow({ width, height, style, }: {
18
18
  width?: number | string;
19
19
  height?: number | string;
20
20
  style?: CSSProperties;
21
- }): react_jsx_runtime.JSX.Element;
21
+ }): react.JSX.Element;
22
22
  /** A shaped, in-app skeleton matching the host archetypes — for an app's own lazy
23
23
  * region (e.g. `<Suspense fallback={<Skeleton archetype="panel.list" />}>`).
24
24
  * Decorative (`aria-hidden`); pair it with `aria-busy` on the region it stands in. */
25
25
  declare function Skeleton({ archetype, style, }: {
26
26
  archetype?: SkeletonArchetype;
27
27
  style?: CSSProperties;
28
- }): react_jsx_runtime.JSX.Element;
28
+ }): react.JSX.Element;
29
29
  /** An indeterminate spinner for waits where a shaped skeleton doesn't fit (a pending
30
30
  * button, a small inline fetch). Wired to the host's ~150 ms-before-spin rule: it
31
31
  * renders nothing until the threshold, so a fast wait shows no flash. Reduced motion
@@ -34,7 +34,7 @@ declare function Spinner({ size, thresholdMs, label, }: {
34
34
  size?: number;
35
35
  thresholdMs?: number;
36
36
  label?: string;
37
- }): react_jsx_runtime.JSX.Element | null;
37
+ }): react.JSX.Element | null;
38
38
  /** Wrap an in-app region whose content is still loading: shows a centered spinner
39
39
  * (past the 150 ms floor) with `aria-busy`, then reveals `children`. For a shaped
40
40
  * wait, pass a `<Skeleton>` as `fallback` instead. App a11y only — not host chrome. */
@@ -43,6 +43,6 @@ declare function LoadingRegion({ loading, fallback, label, children, }: {
43
43
  fallback?: ReactNode;
44
44
  label?: string;
45
45
  children?: ReactNode;
46
- }): react_jsx_runtime.JSX.Element;
46
+ }): react.JSX.Element;
47
47
 
48
48
  export { LOADING_TIMINGS, LoadingRegion, Skeleton, type SkeletonArchetype, SkeletonRow, Spinner };