@cairnvibe/sdk 0.2.7 → 0.2.8
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.
- package/dist/cairn-widget.js +2 -2
- package/dist/element-ladder.d.ts +15 -0
- package/dist/element-ladder.js +41 -0
- package/dist/index.js +102 -20
- package/dist/realtime-server.d.ts +3 -2
- package/dist/realtime-server.js +132 -34
- package/dist/runtime-scan.js +33 -3
- package/dist/server.d.ts +2 -1
- package/dist/server.js +75 -14
- package/dist/verb-executor.d.ts +24 -0
- package/dist/verb-executor.js +87 -0
- package/dist/webmcp-client.d.ts +13 -0
- package/dist/webmcp-client.js +70 -0
- package/package.json +1 -1
- package/src/element-ladder.ts +41 -0
- package/src/index.tsx +106 -20
- package/src/realtime-server.ts +134 -34
- package/src/runtime-scan.ts +33 -3
- package/src/server.ts +85 -15
- package/src/verb-executor.ts +104 -1
- package/src/webmcp-client.ts +79 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Discovers and calls real tools a page has registered via WebMCP
|
|
2
|
+
// (https://webmachinelearning.github.io/webmcp/) — an in-progress web
|
|
3
|
+
// standard letting a site expose its own functions as typed, parameterized
|
|
4
|
+
// tools ("document.modelContext.registerTool(...)"), running in the page's
|
|
5
|
+
// own JS with the user's real session. This is the highest-trust action
|
|
6
|
+
// source there is: a real function the app's own developer wrote, with a
|
|
7
|
+
// real return value — not a click simulated from static analysis. Almost
|
|
8
|
+
// no site has adopted it yet, so this is deliberately a no-op (empty list,
|
|
9
|
+
// nothing to call) everywhere it isn't present, not a hard dependency.
|
|
10
|
+
|
|
11
|
+
import type { WebMcpTool } from "@cairnvibe/core";
|
|
12
|
+
|
|
13
|
+
interface ModelContextTool {
|
|
14
|
+
name: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
inputSchema?: Record<string, unknown>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface ModelContext {
|
|
20
|
+
getTools?: () => Promise<ModelContextTool[]> | ModelContextTool[];
|
|
21
|
+
executeTool?: (tool: ModelContextTool, args: unknown) => Promise<unknown>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getModelContext(): ModelContext | null {
|
|
25
|
+
if (typeof document === "undefined") return null;
|
|
26
|
+
return (document as unknown as { modelContext?: ModelContext }).modelContext ?? null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Bounded the same way LiveElementSchema/CopilotRequestSchema bound the
|
|
30
|
+
* live DOM scan — a hard cap on both count and description length, so a
|
|
31
|
+
* page that registers an unreasonable number of tools (or one with a huge
|
|
32
|
+
* description) can't blow the request payload or the prompt. */
|
|
33
|
+
const MAX_TOOLS = 30;
|
|
34
|
+
const MAX_DESCRIPTION_LENGTH = 500;
|
|
35
|
+
|
|
36
|
+
export async function discoverWebMcpTools(): Promise<WebMcpTool[]> {
|
|
37
|
+
const modelContext = getModelContext();
|
|
38
|
+
if (!modelContext?.getTools) return [];
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const tools = await modelContext.getTools();
|
|
42
|
+
if (!Array.isArray(tools)) return [];
|
|
43
|
+
return tools.slice(0, MAX_TOOLS).map((tool) => ({
|
|
44
|
+
name: String(tool.name),
|
|
45
|
+
description: String(tool.description ?? "").slice(0, MAX_DESCRIPTION_LENGTH),
|
|
46
|
+
inputSchema: tool.inputSchema,
|
|
47
|
+
}));
|
|
48
|
+
} catch {
|
|
49
|
+
// A page's own registerTool()/getTools() implementation throwing is
|
|
50
|
+
// that page's bug, not Cairn's — degrade to "no WebMCP tools" rather
|
|
51
|
+
// than breaking the rest of the turn.
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Calls a real WebMCP tool by name — `name` must be one the model was
|
|
58
|
+
* actually shown this turn (server.ts only ever includes tools from this
|
|
59
|
+
* exact request's own discoverWebMcpTools() call), never invented.
|
|
60
|
+
* Returns a plain-text observation for the agent loop to reason about
|
|
61
|
+
* next, the same shape a click/fill/read result already takes.
|
|
62
|
+
*/
|
|
63
|
+
export async function executeWebMcpTool(name: string, args: Record<string, unknown> | undefined): Promise<{ ok: boolean; observation: string }> {
|
|
64
|
+
const modelContext = getModelContext();
|
|
65
|
+
if (!modelContext?.getTools || !modelContext.executeTool) {
|
|
66
|
+
return { ok: false, observation: "This page no longer has that tool available." };
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
const tools = await modelContext.getTools();
|
|
70
|
+
const tool = Array.isArray(tools) ? tools.find((t) => t.name === name) : undefined;
|
|
71
|
+
if (!tool) return { ok: false, observation: `No tool named "${name}" is available on this page right now.` };
|
|
72
|
+
|
|
73
|
+
const result = await modelContext.executeTool(tool, args ?? {});
|
|
74
|
+
const observation = typeof result === "string" ? result : JSON.stringify(result ?? null);
|
|
75
|
+
return { ok: true, observation: observation.slice(0, 2000) };
|
|
76
|
+
} catch (err) {
|
|
77
|
+
return { ok: false, observation: `That tool failed: ${err instanceof Error ? err.message : "unknown error"}` };
|
|
78
|
+
}
|
|
79
|
+
}
|