@touchcastllc/napster-companion-api-dev 1.0.0-alpha.73 → 1.0.0-alpha.75

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.
@@ -20,17 +20,30 @@ export interface EdgeMcpBridgeOptions {
20
20
  * false to manage the agent's function list yourself. Default: `true`.
21
21
  */
22
22
  registerInlineFunctions?: boolean;
23
+ /**
24
+ * Read tools from a same-origin child iframe's `document.modelContext` instead of
25
+ * this window's. Used by the persistent-agent shell: the avatar + bridge live in the
26
+ * top-level document while the SITE (and its tools) live in the wrapped iframe. The
27
+ * bridge re-discovers on each iframe `load` (navigation) and routes tool calls into
28
+ * it (`executeTool` runs in the iframe's realm, so the site operates itself).
29
+ */
30
+ iframe?: HTMLIFrameElement;
23
31
  }
24
32
  export declare class EdgeMcpBridge {
25
33
  private mc;
26
34
  private tools;
27
35
  private onToolChange;
28
36
  private onResourceUpdated;
37
+ private onResourceListChanged;
38
+ private onIframeLoad;
39
+ private readonly seededUris;
29
40
  private lastInlineSig;
30
41
  private sessionReady;
31
42
  private disposed;
32
43
  private readonly options;
33
44
  constructor(options: EdgeMcpBridgeOptions);
45
+ /** The document whose `modelContext` we read — the target iframe's, or this window's. */
46
+ private targetDoc;
34
47
  /**
35
48
  * Discover `document.modelContext` in the page, cache its tools, and wire up
36
49
  * the `toolchange` / `resourceupdated` listeners. Returns the model context,
@@ -38,8 +51,34 @@ export declare class EdgeMcpBridge {
38
51
  * failure is silent and non-fatal.
39
52
  */
40
53
  attach(): Promise<EdgeMcpModelContext | null>;
54
+ /** Remove listeners from the current model context and drop the ref (no state reset). */
55
+ private unbind;
56
+ /**
57
+ * Re-discover and re-bind after the target iframe navigated to a NEW document (its old
58
+ * `document.modelContext` is dead). Keeps `sessionReady` (the avatar session in the parent
59
+ * survives), but resets the per-document dedupe so tools + resources re-register for the
60
+ * new page.
61
+ */
62
+ private reattach;
41
63
  /** Attach to a known model context: cache tools and subscribe to events. */
42
64
  private bind;
65
+ /**
66
+ * Relay the current value of every registered resource to the agent once, as
67
+ * an initial state seed — batched into a SINGLE `send_message` rather than one
68
+ * per resource, so the agent gets the whole starting state in one context
69
+ * injection. Feature-detects the toolkit's resource extension (`getResources` /
70
+ * `readResource`) — a bare WebMCP surface has neither, so this is a no-op there.
71
+ * Already-seeded URIs are skipped so a `resourcelistchanged` re-seed only emits
72
+ * genuinely new resources. (Ongoing changes still relay per-resource via
73
+ * `relayResourceUpdate`, since they happen one at a time.)
74
+ */
75
+ private seedResources;
76
+ /**
77
+ * Send the whole initial state set to the agent as ONE `role: 'system'`
78
+ * message (one line per resource). `trigger_response: false` so the avatar
79
+ * doesn't speak on the seed; `delay: true` to wait for any current speech.
80
+ */
81
+ private relayInitialState;
43
82
  /** Re-read the tool catalog from the model context. */
44
83
  private refreshTools;
45
84
  /** Map the current WebMCP tools to `set_settings.inline_functions` definitions. */
@@ -1,14 +1,21 @@
1
1
  import type { EdgeMcpModelContext } from "./edge-mcp-types";
2
- /** Read `document.modelContext`, falling back to the deprecated navigator alias. */
3
- export declare function getModelContext(): EdgeMcpModelContext | null;
2
+ /**
3
+ * Read `modelContext` from a document, falling back to the deprecated navigator alias.
4
+ *
5
+ * `doc` defaults to the ambient `document`. Pass a same-origin child frame's
6
+ * `contentDocument` to read ITS surface instead (the shell targets the wrapped
7
+ * iframe this way). Cross-origin access throws — caught and returned as `null`.
8
+ */
9
+ export declare function getModelContext(doc?: Document | null): EdgeMcpModelContext | null;
4
10
  /**
5
11
  * Discover `document.modelContext`, polling briefly if it is not present yet.
6
12
  *
7
13
  * Resolves with the model context if found, or `null` after `timeoutMs` if no
8
14
  * WebMCP surface shows up. Never rejects.
9
15
  *
10
- * @param timeoutMs How long to wait for a late-installing polyfill. Default
11
- * 1500ms — long enough to cover normal hydration ordering,
12
- * short enough not to delay the SDK noticeably.
16
+ * @param timeoutMs How long to wait for a late-installing polyfill. Default 1500ms.
17
+ * @param getDoc Optional getter for the document to read (re-evaluated each poll,
18
+ * since a targeted iframe's `contentDocument` changes as it navigates).
19
+ * Omit to use the ambient `document`.
13
20
  */
14
- export declare function discoverModelContext(timeoutMs?: number): Promise<EdgeMcpModelContext | null>;
21
+ export declare function discoverModelContext(timeoutMs?: number, getDoc?: () => Document | null): Promise<EdgeMcpModelContext | null>;
@@ -12,6 +12,29 @@ export interface EdgeMcpToolInfo {
12
12
  inputSchema?: Record<string, unknown> | string;
13
13
  /** Optional human-readable label. */
14
14
  title?: string;
15
+ /**
16
+ * Standard WebMCP/MCP annotation hints, surfaced by the polyfill's
17
+ * `getTools()`. The bridge reads these to gate agent behavior — most
18
+ * importantly, `destructiveHint` ⇒ the agent must confirm with the user before
19
+ * calling. A bare WebMCP surface (or older polyfill) may omit this entirely, so
20
+ * every field is optional and the bridge treats a missing object as "no hints."
21
+ */
22
+ annotations?: EdgeMcpToolAnnotations;
23
+ }
24
+ /** The standard MCP tool annotation hints the bridge understands. */
25
+ export interface EdgeMcpToolAnnotations {
26
+ /** Tool only reads state — safe to call freely, no confirmation. */
27
+ readOnlyHint?: boolean;
28
+ /**
29
+ * Tool performs a destructive / final action. Bridge convention: treat this as
30
+ * "confirm with the user before calling" (covers additive-but-final actions
31
+ * like submit / send / place-order too).
32
+ */
33
+ destructiveHint?: boolean;
34
+ /** Safe to retry with the same args (no extra effect). */
35
+ idempotentHint?: boolean;
36
+ /** Output may contain untrusted / third-party content (injection risk). */
37
+ untrustedContentHint?: boolean;
15
38
  }
16
39
  /**
17
40
  * Payload of the toolkit's `resourceupdated` event (live-state extension).
@@ -2,4 +2,4 @@ export { EdgeMcpBridge } from "./edge-mcp-bridge";
2
2
  export type { EdgeMcpBridgeOptions } from "./edge-mcp-bridge";
3
3
  export { normalizeFunctionCallEvent } from "./normalizeFunctionCallEvent";
4
4
  export { getModelContext, discoverModelContext } from "./edge-mcp-discovery";
5
- export type { EdgeMcpModelContext, EdgeMcpToolInfo, EdgeMcpResourceUpdate, FunctionCallEvent, SendCommand, } from "./edge-mcp-types";
5
+ export type { EdgeMcpModelContext, EdgeMcpToolInfo, EdgeMcpToolAnnotations, EdgeMcpResourceUpdate, FunctionCallEvent, SendCommand, } from "./edge-mcp-types";
@@ -0,0 +1,88 @@
1
+ import type { NapsterCompanionApiConfig, NapsterCompanionApiInstance } from "../types";
2
+ export interface ShellOptions {
3
+ /**
4
+ * Returns a fresh connection token. The site implements this (typically a POST to
5
+ * its own endpoint that mints a token server-side). Required.
6
+ */
7
+ getToken: () => Promise<string>;
8
+ /**
9
+ * Persist the session across page navigation by wrapping the current page in a
10
+ * same-origin iframe on connect (needed for multi-page apps). Default: `false` —
11
+ * the avatar mounts inline in the current page and a full page navigation ends the
12
+ * session (a normal in-page widget). Turn on only when you need cross-navigation
13
+ * persistence.
14
+ */
15
+ persist?: boolean;
16
+ /**
17
+ * The ONLY URLs that break out of the iframe (open at the top window and end the session).
18
+ * A link is opened inside the iframe unless it matches this denylist — use it for URLs that
19
+ * can't be framed, e.g. an identity / SSO subdomain, or same-origin paths that redirect
20
+ * off-site. Everything not listed (including other cross-origin links) navigates in the iframe.
21
+ */
22
+ persistConfig?: {
23
+ /**
24
+ * Hostnames to force out of the iframe. Matched as the exact host OR a parent domain —
25
+ * e.g. `"napster.com"` also matches `"identity.napster.com"`.
26
+ */
27
+ domains?: string[];
28
+ /**
29
+ * URLs to force out of the iframe: an absolute `"https://…"` (exact or prefix match), or a
30
+ * same-origin path prefix like `"/signout"`.
31
+ */
32
+ urls?: string[];
33
+ };
34
+ /**
35
+ * SECOND persistence variant (no iframe). When a session is active and the user navigates
36
+ * (a normal full page load), automatically start a NEW session on the next page — so the
37
+ * avatar "follows" the user across pages. Each page is a fresh connection (the backend can
38
+ * resume the conversation by externalClientId). The intent is remembered in sessionStorage
39
+ * and cleared when the user ends the session. Ignored when `persist` (the iframe variant)
40
+ * is on. Default: `false`.
41
+ */
42
+ reconnectOnNavigation?: boolean;
43
+ /** When `persist` is on, the URL to load in the iframe. Default: the current page (`location.href`). */
44
+ siteUrl?: string;
45
+ /** Element to build the launcher/iframe into. Default: `document.body`. */
46
+ mount?: HTMLElement;
47
+ /** Avatar config forwarded to `init()`. `mountContainer`/`layout` are managed by the shell. */
48
+ avatar?: Partial<NapsterCompanionApiConfig>;
49
+ /** The iframe `allow` attribute (Permissions Policy for the site). Default: `"autoplay; clipboard-write"`. */
50
+ iframeAllow?: string;
51
+ /** Launcher button label. Default: `"Talk to an agent"`. */
52
+ launcherLabel?: string;
53
+ /** Start a session immediately instead of showing a launcher. Default: `false`. */
54
+ autoConnect?: boolean;
55
+ /**
56
+ * While a session is active, mirror the iframe's path into the address bar (so inner
57
+ * pages stay shareable/bookmarkable) and follow parent back/forward into the iframe.
58
+ * Same-origin only. Default: `true`.
59
+ */
60
+ syncHistory?: boolean;
61
+ /** Log shell lifecycle to the console. Default: `false`. */
62
+ debug?: boolean;
63
+ }
64
+ export interface ShellController {
65
+ /** `true` if this call built the launcher (top-level); `false` if it no-oped (framed content). */
66
+ isShell: boolean;
67
+ /** The live avatar instance once connected, else `null`. */
68
+ getInstance(): NapsterCompanionApiInstance | null;
69
+ /** The site iframe while a session is active, else `null`. */
70
+ getIframe(): HTMLIFrameElement | null;
71
+ /** Start a session: connect the avatar (and, with `persist`, wrap the current page in the iframe). */
72
+ connect(): Promise<void>;
73
+ /** End the session (and, if wrapped, unwrap back to the plain site). */
74
+ disconnect(): void;
75
+ /** Remove the launcher and any active session/iframe. */
76
+ destroy(): void;
77
+ }
78
+ type InitFn = (token: string, config?: Partial<NapsterCompanionApiConfig>) => Promise<NapsterCompanionApiInstance>;
79
+ /**
80
+ * Build the lazy persistent-agent shell. Returns a {@link ShellController}.
81
+ *
82
+ * A no-op (returns a controller with `isShell: false`) when run inside the shell
83
+ * iframe (framed content), so the same snippet is safe to include on every page.
84
+ *
85
+ * `init` is the SDK's `init` method, wired by the SDK.
86
+ */
87
+ export declare function mountShell(init: InitFn, options: ShellOptions): ShellController;
88
+ export {};
@@ -335,6 +335,13 @@ export interface NapsterCompanionApiConfig {
335
335
  functions?: CompanionFunction[];
336
336
  /** Enable debug logging throughout the SDK. When enabled, detailed logs will be output to the console. */
337
337
  debug?: boolean;
338
+ /**
339
+ * Read WebMCP tools from a same-origin child iframe's `document.modelContext` instead of
340
+ * this window's. Set by the persistent-agent shell (`mountShell` with `persist`) so the
341
+ * avatar in the top-level document operates the SITE running inside the wrapped iframe.
342
+ * Leave unset for a normal same-page integration.
343
+ */
344
+ edgeMcpIframe?: HTMLIFrameElement;
338
345
  /** Analytics configuration controls analytics delivery and tracking. */
339
346
  analytics?: AnalyticsConfig;
340
347
  /** Lifecycle callbacks. All are optional. */
@@ -456,6 +463,12 @@ export interface NapsterCompanionApiSDK {
456
463
  * Returns a Promise that resolves to a controllable `NapsterCompanionApiInstance`.
457
464
  */
458
465
  init(token: string, config?: Partial<NapsterCompanionApiConfig>): Promise<NapsterCompanionApiInstance>;
466
+ /**
467
+ * Mount a persistent-agent shell for multi-page apps: renders the site in a
468
+ * same-origin iframe with the avatar in the top-level shell, so the agent
469
+ * session survives navigation. No-op when run as the framed content.
470
+ */
471
+ mountShell(options: import("../shell/mount-shell").ShellOptions): import("../shell/mount-shell").ShellController;
459
472
  /** The SDK version string (useful for diagnostics). */
460
473
  version: string;
461
474
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@touchcastllc/napster-companion-api-dev",
3
- "version": "1.0.0-alpha.73",
3
+ "version": "1.0.0-alpha.75",
4
4
  "keywords": [
5
5
  "napster",
6
6
  "companion-api",