跳到內容

Tools API

function defineTool<
TArgs = unknown,
TResult = unknown,
const TName extends string = string,
>(
tool: ToolDefinition<TArgs, TResult, TName>,
): ToolDefinition<TArgs, TResult, TName>

原樣回傳 definition,同時保留 TypeScript generics 與 literal name type。

const updateCounter = defineTool<
{ delta: number },
{ value: number }
>({
name: "update_counter",
description: "Update the visible counter by delta.",
argsSchema: {
type: "object",
properties: {
delta: { type: "integer" },
},
required: ["delta"],
},
handler: ({ delta }) => ({ value: counter.add(delta) }),
});

defineTool() 本身不做 validation;createSession() 與 pedelec.webmcp() 會 normalize/validate tools。

使用情境與心智模型請見 Runtime Modules;以下保留精確的宣告與準備契約。

Vite 應用使用同一個 authoring shape 描述 local 或 package-backed Runtime module。Minimal declaration 可以給 workspace.run() 使用:

import { defineRuntimeModule } from "@kaoruisaac/pedelec";
const sceneTools = defineRuntimeModule({
name: "scene-tools",
entry: "./deno/scene-tools.ts",
});
const result = await workspace.run(
`import { buildScene } from "scene-tools";\nawait buildScene();`,
{ runtimeModules: [sceneTools] },
);

同一個 declaration 若也要給 skills.runtimeModules 使用,可以加入 Agent metadata:

import { defineRuntimeModule } from "@kaoruisaac/pedelec";
const spriteTools = defineRuntimeModule({
name: "sprite-tools",
description: "Sprite authoring and preview utilities.",
entry: "./agent/sprite-tools.ts",
usage: `import { previewActorSource } from "sprite-tools";`,
preferStdinExecution: true,
});
const skills = {
guidance: "Use the module when sprite preview is needed.",
tools: [],
runtimeModules: [spriteTools],
};

defineRuntimeModule() 保留 concrete declaration shape,包括 optional metadata 是否真的有提供。pedelecVitePlugin() 會在 vite build 與 vite dev 解析 entry、打包 self-contained ESM runtime,並 roll-up 獨立的 public declaration artifact。createSession() 會自動傳送 prepared artifact,Core 在 session 回傳前建立 immutable、thread-scoped package 與 import map。workspace.run() 使用同一種 prepared artifact format,但會按 Workspace 與 authorized SDK origin cache packages,再為每次 run 只對 requested names 建立 exact import map。

只使用 root SDK、從不 import @kaoruisaac/pedelec/vite 的應用不需要 Vite;Vite peer 是 optional。使用 plugin 的應用必須安裝支援的 Vite 8:

Terminal window
npm install @kaoruisaac/pedelec vite
vite.config.ts
import { defineConfig } from "vite";
import { pedelecVitePlugin } from "@kaoruisaac/pedelec/vite";
export default defineConfig({
plugins: [pedelecVitePlugin()],
});

entry 必須是靜態字串。相對路徑從宣告所在 source file 解析。Vite string alias 與已安裝 package specifier(含 package.json exports subpath)會走應用程式的 Vite resolver(runtime)與 TypeScript bundler resolution(public types)。Remote URL、computed value、inline source 與 imported module object 都不支援。

Plugin 會在轉換後的 module object 上附加 private、non-enumerable 的 runtime/types artifact,並移除原始 authoring entry path。它不會寫入應用程式的 Pedelec workspace,也不會 mutate 既有 session。

usage 請保持精簡:它是常見情境的 agent hint;需要檢查 enum 或 option shape 時,以 generated declaration bundle 為準。

Agent 會在以 pedelec-ts-runtime 執行的 JavaScript/TypeScript script 中,以 logical name import module,不會以 private filesystem path 執行 module。Host Context 會列出 name、description、usage,以及 workspace-relative 的 index.d.ts pointer,供精確 API discovery;generated index.mjs 是 implementation fallback,不是一般 API reference。

preferStdinExecution 是主要用於短 script 的公開 optional authoring metadata。設為 true 時,Host Context 會以 preferredExecution: "stdin" 表示;Agent 應優先使用全域 runStdinCommand。Host Context 不會暴露 raw authoring flag,共用 contract 也不包含 provider-specific shell syntax。這是 preference 而非限制,file-backed execution 仍然有效;省略或設為 false 時不會出現 preferredExecution,也不會影響 bundling 或 artifact contents。

entry 支援 local relative file、Vite alias,以及已安裝的 bare/package exports specifier。它只在 build-time 使用;Desktop 不會解析它,Sandbox runtime 不能 fetch npm、JSR 或 network。Imported module 使用所屬 Thread 的 mode:預設 Sandbox,或明確授權的 Full Access。

只要 Core 仍知道該 thread,end/resume 會保留同一份 module snapshot。pedelec.resumeSession(sessionId) 會重新 attach 到既有 snapshot;要使用重建後的 artifact,請建立新 session。skills.tools 是分開的 App Tool capability:呼叫走 pedelec-cli --thread-id ... tool-spec 與 tool-call,Runtime Modules 則留在 pedelec-ts-runtime script 內。

type ToolDefinition<
TArgs = unknown,
TResult = unknown,
TName extends string = string,
> = {
name: TName;
description: string;
argsSchema: ToolArgsSchema;
timeoutMs?: number;
handler?: ToolSpecificHandler<TArgs, TResult>;
};
Field Required 說明
name Yes Agent-facing name;letter 開頭,只能包含 letter、digit、_、.、-。
description Yes Non-empty 行為、side effect 與選擇條件說明。
argsSchema Yes Pedelec Tool Args Schema;root 必須 object。
timeoutMs No Positive integer runtime wait limit;Core 目前預設 60 秒。
handler No SDK 保存在 browser、不送到 Core 的 inline handler。pedelec.webmcp() 必須提供。

同一 skills manifest 的 names 必須 unique。

type ToolSpecificHandler<TArgs = unknown, TResult = unknown> = (
args: TArgs,
ctx: ToolCallContext,
) => TResult | Promise<TResult>;

Handler 在 browser page context 執行。Type parameters 只有 compile-time,runtime args 仍要 validate。

Return value 必須符合 bridge JSON data model。

type AgentModuleDefinition<TName extends string = string> =
RuntimeModuleDefinition<TName> & {
description: string;
usage: string;
};
type SkillsInput<
TTools extends readonly ToolDefinition[] = readonly ToolDefinition[],
> = {
guidance: string;
tools: TTools;
runtimeModules?: readonly AgentModuleDefinition[];
/** @deprecated Use runtimeModules. Do not provide both fields. */
denoModules?: readonly AgentModuleDefinition[];
};
const tools = [getCurrentPage, updateCounter] as const;
const skills = {
guidance: "Read current state before making changes.",
tools,
} satisfies SkillsInput<typeof tools>;

guidance 必須是 string,tools 必須是 array。runtimeModules 是 optional;Session/Agent modules 需要 name、description、entry、usage,且 Agent metadata 必須是 non-empty。Workspace-only 的 workspace.run() declaration 只需要 name、entry 與 prepared Vite artifact。preferStdinExecution 是 optional boolean preference,對 Workspace execution 沒有作用。Session artifacts 會在 createSession() transport;Workspace artifacts 在需要時準備,並在 Core runtime cache 有效期間重用。

type ToolNameOf<TTools extends readonly ToolDefinition[]> = Extract<
TTools[number]["name"],
string
>;

從 readonly definitions array 取出 tool name union。

const tools = [readPage, updateCounter] as const;
type AppToolName = ToolNameOf<typeof tools>;
// "get_current_page" | "update_counter"

createSession() 用它收窄 named session.onTool()。

type SerializableToolManifest = {
name: string;
description: string;
argsSchema: ToolArgsSchema;
timeoutMs?: number;
};

送到 Core 的 tool shape,刻意不包含 handler。

type SerializableSkillsManifest = {
guidance: string;
tools: SerializableToolManifest[];
denoModules?: Array<{
name: string;
description: string;
usage: string;
preferStdinExecution?: boolean;
}>;
};

經 normalize 後送進 bridge 的 skills metadata。Prepared runtime 與 declaration source 會走內部 session setup path 傳送;entry 不會送到 Core。

createSession() 期間:

  1. 檢查 tool entries;
  2. 透過 JSON serialization clone argsSchema;
  3. 依 name 將 inline handlers 保存在 browser-side map;
  4. 將 serializable definitions 送到 Core。

Duplicate tool name reject INVALID_INPUT。

Legacy input shorthand 已不支援,請使用 argsSchema。

Incoming tool call 依序:

  1. session named handler;
  2. original definition inline handler;
  3. generic fallback;
  4. SDK-created TOOL_HANDLER_NOT_FOUND result。

Handler exception 變成 TOOL_HANDLER_ERROR result;delivery failure emit SUBMIT_TOOL_RESULT_FAILED。

async webmcp<const TTools extends readonly ToolDefinition[]>(
input: WebMcpInput<TTools>,
): Promise<WebMcpBinding>

當 document.modelContext.registerTool 存在時,把既有 App Tool definitions 註冊成 browser WebMCP tools。這個 method 不會建立 Pedelec Session,也不會呼叫 Extension、Desktop、Provider 或 Core。

type WebMcpSkillsInput<
TTools extends readonly ToolDefinition[] = readonly ToolDefinition[],
> = {
tools: TTools;
};
type WebMcpInput<
TTools extends readonly ToolDefinition[] = readonly ToolDefinition[],
> = {
skills: WebMcpSkillsInput<TTools>;
};
type WebMcpBinding = {
available: boolean;
dispose(): void;
};

skills.tools 是唯一的 skills 欄位。guidance 與 runtimeModules 屬於 Session,不在這個 input。傳給 webmcp() 的每個 tool 都必須有 inline handler;這條 binding 沒有 session.onTool()。

const tools = [getCurrentPage] as const;
const binding = await pedelec.webmcp({
skills: { tools },
});
if (binding.available) {
// Browser WebMCP client 可以呼叫這些 tools,直到 dispose()。
}
binding.dispose();

同一份 tools 也可以再傳給 createSession()。

App Tool WebMCP registration
name name
description description
normalized argsSchema inputSchema
inline handler execute

timeoutMs 仍只是 Pedelec Session 的等待上限。WebMCP registration 沒有 per-tool timeout,這個 binding 也不另外加 timer。Registration 不包含 title、annotations、exposedTo、handler、guidance 或 runtimeModules。

Name 沿用 Pedelec 規則:letter 開頭,之後只能是 letter、digit、_、.、-。WebMCP 另外要求 name 最多 128 個字元。inputSchema 是與 createSession() 相同的 normalized argsSchema clone。Pedelec 不會自動加入 additionalProperties,也不會改寫 required、enum 或 oneOf。

webmcp() 先驗證 input,再檢查 document.modelContext.registerTool。無效 tool、重複 name、空白 description、超過 128 字元的 name,或缺少 inline handler,仍會以 INVALID_INPUT reject。缺少 handler 的 message 是 WebMCP tool handler must be a function,details 會帶 toolName。

沒有 window 或 document,或 registerTool 不是 function 時,method resolve:

{ available: false, dispose() {} }

不支援的 browser 不會 throw,也不會載入 polyfill,也不使用 navigator.modelContext 作為註冊 API。Pedelec 只在 document.modelContext.registerTool 可用時綁定 tools;否則 available 為 false。

一次 webmcp() 建立一個 AbortController,並把同一個 signal 傳給每一次 registerTool。dispose() 會 abort 該 signal。它是同步的,可以重複呼叫,不會結束 Session,也不會聯絡 Core。Dispose 一個 binding 不會移除另一次 webmcp() 或其他 page script 註冊的 tools。

如果註冊進行到一半失敗,webmcp() 會 abort 這個共用 signal 並 reject,不會回傳 binding。除非失敗本身已經是 PedelecError,否則 error code 是 WEBMCP_REGISTRATION_FAILED;既有的 PedelecError 會保留。其他 script 已經註冊的同名 tool 以 registerTool() 的結果為準;Pedelec 不會改名,也不會 unregister 別人的 tool。

WebMCP 執行不具備 Pedelec Session 的 ToolCallContext;要跨 Pedelec / WebMCP 共用的 inline handler 應只依賴 args 與一般 application/browser state。

Adapter 只呼叫 handler(args),不會製造 sessionId、provider、turnId 或 toolRequestId,也不會把 WebMCP execution AbortSignal 傳進既有 handler。會讀取 ctx.sessionId 的 handler 仍可用於 Pedelec Session,但它不是可移植的 WebMCP handler。

Root alias:

type ToolArgsSchema = ToolArgsObjectSchema;

Node union:

type ToolArgsSchemaNode =
| ToolArgsStringSchema
| ToolArgsNumberSchema
| ToolArgsIntegerSchema
| ToolArgsBooleanSchema
| ToolArgsArraySchema
| ToolArgsObjectSchema
| ToolArgsOneOfSchema;

所有欄位見 Types,使用方式見 Tool Args Schema。

DenoModuleDefinition、defineDenoModule() 與 denoModules 仍可使用,TypeScript 以 @deprecated 提示。請改用 RuntimeModuleDefinition、defineRuntimeModule() 與 runtimeModules。同一個 skills/options 物件只能指定其中一種欄位;即使是空陣列,同時帶兩個欄位也會回 INVALID_INPUT。內部 SerializableSkillsManifest.denoModules wire 欄位維持不變。

import { defineDenoModule, defineRuntimeModule, type DenoModuleDefinition } from "@kaoruisaac/pedelec";
const legacy: DenoModuleDefinition = defineDenoModule({ name: "project-tools", entry: "./agent/project-tools.ts" });
await workspace.run('import "project-tools";', { denoModules: [legacy] });
const modern = defineRuntimeModule({ name: "project-tools", entry: "./agent/project-tools.ts" });
await workspace.run('import "project-tools";', { runtimeModules: [modern] });