carapace-plugin-sdk 2.0.1 → 3.0.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.
- package/README.md +7 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.js +10 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -63,11 +63,17 @@ Nothing else to write. No registration boilerplate, no result wrapping, no manif
|
|
|
63
63
|
|
|
64
64
|
| You write | SDK handles |
|
|
65
65
|
|-----------|-------------|
|
|
66
|
-
| `execute()` returning a plain object | Wrapping in the OpenClaw result format |
|
|
66
|
+
| `execute()` returning a plain object | Wrapping in the OpenClaw result format: JSON text in `content` for the model, the structured value in `details` for Code Mode scripts |
|
|
67
67
|
| `configSchema` TypeBox schema | JSON Schema for the manifest + OpenClaw settings UI (defaults to an empty object schema if omitted) |
|
|
68
68
|
| Tool names | `contracts.tools` list in the manifest — auto-discovered even for raw `register()` plugins |
|
|
69
69
|
| `src/plugin.ts` | `dist/adapter.js`, `dist/bin/*.js`, `openclaw.plugin.json` |
|
|
70
70
|
|
|
71
|
+
### Result grading (v3)
|
|
72
|
+
|
|
73
|
+
Since v3, the value you return is also the result's `details`. OpenClaw grades a tool call from `details`, so a top-level `error` (truthy), `ok: false`, `success: false`, `timedOut: true`, nonzero `exitCode`, or a failure-word `status` (`"failed"`, `"unavailable"`, `"disabled"`, `"cancelled"`, `"invalid"`, …) marks the call failed. Return those keys only to signal failure; put domain values that use these names under a wrapper key (`{ shipment: { status } }`).
|
|
74
|
+
|
|
75
|
+
Upgrading from v2 needs no code changes unless a tool returns one of those keys as ordinary data.
|
|
76
|
+
|
|
71
77
|
## Build setup
|
|
72
78
|
|
|
73
79
|
Add to `package.json`:
|
package/dist/index.d.ts
CHANGED
|
@@ -173,15 +173,21 @@ interface PluginEntry {
|
|
|
173
173
|
* For advanced plugins that implement `register()` directly, wrap your
|
|
174
174
|
* execute return values with this function.
|
|
175
175
|
*
|
|
176
|
+
* Both fields carry the result: `content` is the text the model reads on a
|
|
177
|
+
* direct tool call, and `details` is the structured value. OpenClaw Code Mode
|
|
178
|
+
* hands `details` (not `content`) to the calling script, and grades the call
|
|
179
|
+
* from it — so a top-level `ok: false`, truthy `error`, or failure-word
|
|
180
|
+
* `status` (e.g. "failed", "unavailable") marks the call failed.
|
|
181
|
+
*
|
|
176
182
|
* @param data - Anything JSON-serialisable, or a plain string.
|
|
177
|
-
* @returns `{ content: [{ type: "text", text: "<json>" }], details:
|
|
183
|
+
* @returns `{ content: [{ type: "text", text: "<json>" }], details: <data> }`
|
|
178
184
|
*/
|
|
179
185
|
declare function formatResult(data: unknown): {
|
|
180
186
|
content: {
|
|
181
187
|
type: "text";
|
|
182
188
|
text: string;
|
|
183
189
|
}[];
|
|
184
|
-
details:
|
|
190
|
+
details: unknown;
|
|
185
191
|
};
|
|
186
192
|
/**
|
|
187
193
|
* Create the plugin's OpenClaw adapter export.
|
package/dist/index.js
CHANGED
|
@@ -31,18 +31,26 @@ function definePlugin(def) {
|
|
|
31
31
|
}
|
|
32
32
|
function formatResult(data) {
|
|
33
33
|
let text;
|
|
34
|
+
let details = {};
|
|
34
35
|
if (typeof data === "string") {
|
|
35
36
|
text = data;
|
|
37
|
+
details = data;
|
|
36
38
|
} else {
|
|
37
39
|
try {
|
|
38
|
-
|
|
40
|
+
const json = JSON.stringify(data);
|
|
41
|
+
if (json === void 0) {
|
|
42
|
+
text = String(data);
|
|
43
|
+
} else {
|
|
44
|
+
text = json;
|
|
45
|
+
details = JSON.parse(json);
|
|
46
|
+
}
|
|
39
47
|
} catch {
|
|
40
48
|
text = String(data);
|
|
41
49
|
}
|
|
42
50
|
}
|
|
43
51
|
return {
|
|
44
52
|
content: [{ type: "text", text }],
|
|
45
|
-
details
|
|
53
|
+
details
|
|
46
54
|
};
|
|
47
55
|
}
|
|
48
56
|
function createAdapter(entry, callerUrl) {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * carapace-plugin-sdk — Core types and helpers for OpenClaw plugins.\n *\n * This is the main entry point. Import `definePlugin` to author a plugin\n * with full TypeScript inference — typed config and typed tool parameters\n * with no boilerplate.\n *\n * Quick-start:\n *\n * src/plugin.ts — the only file you write; export `createEntry` from `definePlugin`\n * dist/adapter.js — auto-generated at build time; do not write by hand\n *\n * @module carapace-plugin-sdk\n */\n\nimport { createRequire } from \"node:module\";\nimport { type TObject, type Static } from \"@sinclair/typebox\";\n\n// ---------------------------------------------------------------------------\n// definePlugin — the primary authoring API\n// ---------------------------------------------------------------------------\n\n/**\n * Internal (erased) tool definition stored at runtime.\n * The typed version lives only in TypeScript's type system via ToolFactory<TConfig>.\n */\ninterface ToolDef {\n name: string;\n label?: string;\n description: string;\n parameters: unknown;\n execute(params: Record<string, unknown>, config: unknown): Promise<unknown>;\n}\n\n/**\n * Typed tool factory injected into the `tools` callback of `definePlugin`.\n *\n * TConfig is fixed by the enclosing `definePlugin` call, so every tool in the\n * array receives the same config type. TSchema is inferred per tool from the\n * `parameters` field, giving typed `params` in `execute`.\n *\n * You never construct this directly — it is passed to you by `definePlugin`.\n */\ntype ToolFactory<TConfig> = <TSchema extends TObject>(def: {\n /** Machine-readable name, used as the CLI subcommand. snake_case recommended. */\n name: string;\n /** Human-readable label shown in OpenClaw's UI. Defaults to `name`. */\n label?: string;\n /** One-sentence description shown in --help and OpenClaw's tool inspector. */\n description: string;\n /**\n * TypeBox schema for the tool's parameters.\n * The type is inferred as `Static<TSchema>` in `execute`'s first argument.\n */\n parameters: TSchema;\n /**\n * The tool's implementation.\n *\n * @param params - Typed parameters derived from `parameters` schema. No casts needed.\n * @param config - Typed config derived from `definePlugin`'s `configSchema`. No casts needed.\n * @returns Any JSON-serialisable value. The SDK wraps it in the OpenClaw result format.\n */\n execute(params: Static<TSchema>, config: TConfig): Promise<unknown>;\n}) => ToolDef;\n\n/**\n * Define an OpenClaw plugin with full TypeScript inference.\n *\n * Returns a `createEntry` function — export it from your `src/index.ts`.\n * The SDK handles all registration, result wrapping, and config plumbing.\n *\n * Config type is inferred from `configSchema` and flows into every tool's\n * `execute(params, config)` without any manual type annotations.\n *\n * @example\n * // src/index.ts — the entire plugin\n * import { definePlugin } from \"carapace-plugin-sdk\";\n * import { Type } from \"@sinclair/typebox\";\n *\n * export const createEntry = definePlugin({\n * id: \"my-plugin\",\n * name: \"My Plugin\",\n * configSchema: Type.Object({\n * apiKey: Type.Optional(Type.String({ description: \"API key.\" })),\n * }),\n * tools: (tool) => [\n * tool({\n * name: \"do_thing\",\n * description: \"Does the thing.\",\n * parameters: Type.Object({\n * input: Type.String({ description: \"Input value.\" }),\n * }),\n * execute: async ({ input }, config) => {\n * // input: string ✓ config.apiKey: string | undefined ✓\n * return { result: input };\n * },\n * }),\n * ],\n * });\n */\nexport function definePlugin<TConfigSchema extends TObject = TObject>(def: {\n /** Unique plugin id. Lowercase alphanumeric with hyphens. Used as the CLI binary name. */\n id: string;\n /** Human-readable display name. */\n name: string;\n /** One-sentence description of what the plugin does. */\n description?: string;\n /** When to load the plugin. Defaults to `{ onStartup: true }`. */\n activation?: { onStartup?: boolean };\n /**\n * TypeBox schema for the plugin's config block.\n *\n * Used for three things simultaneously:\n * 1. Runtime JSON Schema for the OpenClaw manifest (validated before register())\n * 2. TypeScript type inference for `config` in every tool's `execute`\n * 3. Environment variable mapping for the standalone CLI\n */\n configSchema?: TConfigSchema;\n /**\n * Declare your tools here. Receives a typed `tool()` factory as its argument.\n *\n * Using a callback (rather than a plain array) is what allows TypeScript to\n * thread the config type through to each tool's `execute` function.\n */\n tools: (tool: ToolFactory<Static<TConfigSchema>>) => ToolDef[];\n}): () => PluginEntry {\n return () => {\n // The factory is identity at runtime — all type magic is compile-time only.\n const toolFactory = ((toolDef: unknown) => toolDef) as ToolFactory<Static<TConfigSchema>>;\n const toolDefs = def.tools(toolFactory);\n\n return {\n id: def.id,\n name: def.name,\n description: def.description,\n activation: def.activation ?? { onStartup: true },\n // Derive contracts from the declared tools so the manifest is always accurate.\n contracts: { tools: toolDefs.map((t) => t.name) },\n // TypeBox TObject is valid JSON Schema — pass through for the manifest generator.\n configSchema: def.configSchema as unknown as PluginEntry[\"configSchema\"],\n register(api: PluginApi) {\n // OpenClaw validates pluginConfig against configSchema before calling register(),\n // so this cast is safe. Fall back to empty object if config is not yet set.\n const config = (api.pluginConfig ?? {}) as Static<TConfigSchema>;\n\n for (const toolDef of toolDefs) {\n api.registerTool({\n name: toolDef.name,\n label: toolDef.label ?? toolDef.name,\n description: toolDef.description,\n parameters: toolDef.parameters,\n // Wrap the result automatically — execute() returns plain values, not formatResult().\n execute: async (_toolCallId: string, params: Record<string, unknown>) =>\n formatResult(await toolDef.execute(params, config)),\n });\n }\n },\n };\n };\n}\n\n// ---------------------------------------------------------------------------\n// Low-level types — the plugin contract\n//\n// These are used by the generated adapter and by advanced plugins that need\n// more control than definePlugin provides (e.g. dynamic tool registration).\n// ---------------------------------------------------------------------------\n\n/**\n * The API object passed to your plugin's `register()` function.\n *\n * When using `definePlugin`, you never see this directly — the SDK handles it.\n * It is exported for advanced use cases and for the generated adapter.\n */\nexport type PluginApi = {\n /** Register a tool with OpenClaw. Call once per tool inside register(). */\n registerTool: (tool: unknown) => void;\n /**\n * The user's config values for this plugin, keyed by field name.\n * Validated against configSchema by OpenClaw before register() is called.\n * May be undefined if the user has not configured the plugin.\n */\n pluginConfig?: Record<string, unknown>;\n};\n\n/**\n * The object returned by `createEntry()` — the plugin's public contract.\n *\n * When using `definePlugin`, this is constructed automatically.\n * Exported for advanced plugins that build it manually.\n */\nexport interface PluginEntry {\n /** Unique plugin id. Used as the CLI binary name and OpenClaw config key. */\n id: string;\n /** Human-readable display name. */\n name: string;\n /** One-sentence description. */\n description?: string;\n /** Tool names this plugin promises to register. Derived automatically by `definePlugin`. */\n contracts?: { tools: string[] };\n /** When to load the plugin. Defaults to `{ onStartup: true }`. */\n activation?: { onStartup?: boolean };\n /**\n * JSON Schema for the plugin's config block.\n * Pass a TypeBox `Type.Object(...)` — it is valid JSON Schema and gives you type inference.\n */\n configSchema?: unknown;\n /** Called once by OpenClaw at startup. Use `definePlugin` instead of implementing this directly. */\n register(api: PluginApi): void;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Wrap a value in the standard OpenClaw tool result format.\n *\n * When using `definePlugin`, you do NOT call this yourself — the SDK calls it\n * automatically after your `execute` function returns.\n *\n * For advanced plugins that implement `register()` directly, wrap your\n * execute return values with this function.\n *\n * @param data - Anything JSON-serialisable, or a plain string.\n * @returns `{ content: [{ type: \"text\", text: \"<json>\" }], details: {} }`\n */\nexport function formatResult(data: unknown) {\n let text: string;\n if (typeof data === \"string\") {\n text = data;\n } else {\n try {\n text = JSON.stringify(data) ?? String(data);\n } catch {\n text = String(data);\n }\n }\n return {\n content: [{ type: \"text\" as const, text }],\n details: {},\n };\n}\n\n// ---------------------------------------------------------------------------\n// Adapter factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create the plugin's OpenClaw adapter export.\n *\n * **You never call this yourself.** It is called by the generated `dist/adapter.js`.\n *\n * Attempts to load the optional `openclaw` peer dependency and wrap the plugin\n * entry with the host's `definePluginEntry()`. Falls back to the raw entry if\n * `openclaw` is not installed (standalone CLI mode).\n *\n * @param entry - The object returned by `createEntry()`.\n * @param callerUrl - Pass `import.meta.url` from the generated adapter file.\n */\nexport function createAdapter(entry: PluginEntry, callerUrl: string): unknown {\n // v2: contracts.tools is required — plugins must declare tools via definePlugin\n // or set contracts.tools explicitly. The ensureContracts fallback has been removed.\n if (!entry.contracts?.tools?.length) {\n throw new Error(\n `Plugin \"${entry.id ?? entry.name}\" is missing contracts.tools. ` +\n `Migrate to definePlugin() from carapace-plugin-sdk — see https://github.com/JeffSteinbok/carapace-plugin-sdk`,\n );\n }\n\n const req = createRequire(callerUrl);\n\n try {\n const sdk = req(\"openclaw/plugin-sdk/plugin-entry\") as {\n definePluginEntry?: (e: unknown) => unknown;\n };\n\n if (typeof sdk.definePluginEntry !== \"function\") {\n throw new Error(\n \"OpenClaw SDK loaded but did not export `definePluginEntry`. Upgrade the `openclaw` package.\",\n );\n }\n\n const defined = sdk.definePluginEntry(entry) as Record<string, unknown>;\n // definePluginEntry may strip contracts — preserve them from the entry.\n if (entry.contracts && !defined.contracts) {\n defined.contracts = entry.contracts;\n }\n return defined;\n } catch (err: unknown) {\n if (isModuleNotFoundError(err)) return entry;\n throw err;\n }\n}\n\nfunction isModuleNotFoundError(err: unknown): boolean {\n if (!(err instanceof Error)) return false;\n return (\n \"code\" in err &&\n ((err as { code: string }).code === \"MODULE_NOT_FOUND\" ||\n (err as { code: string }).code === \"ERR_MODULE_NOT_FOUND\")\n );\n}\n"],"mappings":";AAeA,SAAS,qBAAqB;AAqFvB,SAAS,aAAsD,KAyBhD;AACpB,SAAO,MAAM;AAEX,UAAM,eAAe,CAAC,YAAqB;AAC3C,UAAM,WAAW,IAAI,MAAM,WAAW;AAEtC,WAAO;AAAA,MACL,IAAI,IAAI;AAAA,MACR,MAAM,IAAI;AAAA,MACV,aAAa,IAAI;AAAA,MACjB,YAAY,IAAI,cAAc,EAAE,WAAW,KAAK;AAAA;AAAA,MAEhD,WAAW,EAAE,OAAO,SAAS,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE;AAAA;AAAA,MAEhD,cAAc,IAAI;AAAA,MAClB,SAAS,KAAgB;AAGvB,cAAM,SAAU,IAAI,gBAAgB,CAAC;AAErC,mBAAW,WAAW,UAAU;AAC9B,cAAI,aAAa;AAAA,YACf,MAAM,QAAQ;AAAA,YACd,OAAO,QAAQ,SAAS,QAAQ;AAAA,YAChC,aAAa,QAAQ;AAAA,YACrB,YAAY,QAAQ;AAAA;AAAA,YAEpB,SAAS,OAAO,aAAqB,WACnC,aAAa,MAAM,QAAQ,QAAQ,QAAQ,MAAM,CAAC;AAAA,UACtD,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAoEO,SAAS,aAAa,MAAe;AAC1C,MAAI;AACJ,MAAI,OAAO,SAAS,UAAU;AAC5B,WAAO;AAAA,EACT,OAAO;AACL,QAAI;AACF,aAAO,KAAK,UAAU,IAAI,KAAK,OAAO,IAAI;AAAA,IAC5C,QAAQ;AACN,aAAO,OAAO,IAAI;AAAA,IACpB;AAAA,EACF;AACA,SAAO;AAAA,IACL,SAAS,CAAC,EAAE,MAAM,QAAiB,KAAK,CAAC;AAAA,IACzC,SAAS,CAAC;AAAA,EACZ;AACF;AAkBO,SAAS,cAAc,OAAoB,WAA4B;AAG5E,MAAI,CAAC,MAAM,WAAW,OAAO,QAAQ;AACnC,UAAM,IAAI;AAAA,MACR,WAAW,MAAM,MAAM,MAAM,IAAI;AAAA,IAEnC;AAAA,EACF;AAEA,QAAM,MAAM,cAAc,SAAS;AAEnC,MAAI;AACF,UAAM,MAAM,IAAI,kCAAkC;AAIlD,QAAI,OAAO,IAAI,sBAAsB,YAAY;AAC/C,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,UAAM,UAAU,IAAI,kBAAkB,KAAK;AAE3C,QAAI,MAAM,aAAa,CAAC,QAAQ,WAAW;AACzC,cAAQ,YAAY,MAAM;AAAA,IAC5B;AACA,WAAO;AAAA,EACT,SAAS,KAAc;AACrB,QAAI,sBAAsB,GAAG,EAAG,QAAO;AACvC,UAAM;AAAA,EACR;AACF;AAEA,SAAS,sBAAsB,KAAuB;AACpD,MAAI,EAAE,eAAe,OAAQ,QAAO;AACpC,SACE,UAAU,QACR,IAAyB,SAAS,sBACjC,IAAyB,SAAS;AAEzC;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * carapace-plugin-sdk — Core types and helpers for OpenClaw plugins.\n *\n * This is the main entry point. Import `definePlugin` to author a plugin\n * with full TypeScript inference — typed config and typed tool parameters\n * with no boilerplate.\n *\n * Quick-start:\n *\n * src/plugin.ts — the only file you write; export `createEntry` from `definePlugin`\n * dist/adapter.js — auto-generated at build time; do not write by hand\n *\n * @module carapace-plugin-sdk\n */\n\nimport { createRequire } from \"node:module\";\nimport { type TObject, type Static } from \"@sinclair/typebox\";\n\n// ---------------------------------------------------------------------------\n// definePlugin — the primary authoring API\n// ---------------------------------------------------------------------------\n\n/**\n * Internal (erased) tool definition stored at runtime.\n * The typed version lives only in TypeScript's type system via ToolFactory<TConfig>.\n */\ninterface ToolDef {\n name: string;\n label?: string;\n description: string;\n parameters: unknown;\n execute(params: Record<string, unknown>, config: unknown): Promise<unknown>;\n}\n\n/**\n * Typed tool factory injected into the `tools` callback of `definePlugin`.\n *\n * TConfig is fixed by the enclosing `definePlugin` call, so every tool in the\n * array receives the same config type. TSchema is inferred per tool from the\n * `parameters` field, giving typed `params` in `execute`.\n *\n * You never construct this directly — it is passed to you by `definePlugin`.\n */\ntype ToolFactory<TConfig> = <TSchema extends TObject>(def: {\n /** Machine-readable name, used as the CLI subcommand. snake_case recommended. */\n name: string;\n /** Human-readable label shown in OpenClaw's UI. Defaults to `name`. */\n label?: string;\n /** One-sentence description shown in --help and OpenClaw's tool inspector. */\n description: string;\n /**\n * TypeBox schema for the tool's parameters.\n * The type is inferred as `Static<TSchema>` in `execute`'s first argument.\n */\n parameters: TSchema;\n /**\n * The tool's implementation.\n *\n * @param params - Typed parameters derived from `parameters` schema. No casts needed.\n * @param config - Typed config derived from `definePlugin`'s `configSchema`. No casts needed.\n * @returns Any JSON-serialisable value. The SDK wraps it in the OpenClaw result format.\n */\n execute(params: Static<TSchema>, config: TConfig): Promise<unknown>;\n}) => ToolDef;\n\n/**\n * Define an OpenClaw plugin with full TypeScript inference.\n *\n * Returns a `createEntry` function — export it from your `src/index.ts`.\n * The SDK handles all registration, result wrapping, and config plumbing.\n *\n * Config type is inferred from `configSchema` and flows into every tool's\n * `execute(params, config)` without any manual type annotations.\n *\n * @example\n * // src/index.ts — the entire plugin\n * import { definePlugin } from \"carapace-plugin-sdk\";\n * import { Type } from \"@sinclair/typebox\";\n *\n * export const createEntry = definePlugin({\n * id: \"my-plugin\",\n * name: \"My Plugin\",\n * configSchema: Type.Object({\n * apiKey: Type.Optional(Type.String({ description: \"API key.\" })),\n * }),\n * tools: (tool) => [\n * tool({\n * name: \"do_thing\",\n * description: \"Does the thing.\",\n * parameters: Type.Object({\n * input: Type.String({ description: \"Input value.\" }),\n * }),\n * execute: async ({ input }, config) => {\n * // input: string ✓ config.apiKey: string | undefined ✓\n * return { result: input };\n * },\n * }),\n * ],\n * });\n */\nexport function definePlugin<TConfigSchema extends TObject = TObject>(def: {\n /** Unique plugin id. Lowercase alphanumeric with hyphens. Used as the CLI binary name. */\n id: string;\n /** Human-readable display name. */\n name: string;\n /** One-sentence description of what the plugin does. */\n description?: string;\n /** When to load the plugin. Defaults to `{ onStartup: true }`. */\n activation?: { onStartup?: boolean };\n /**\n * TypeBox schema for the plugin's config block.\n *\n * Used for three things simultaneously:\n * 1. Runtime JSON Schema for the OpenClaw manifest (validated before register())\n * 2. TypeScript type inference for `config` in every tool's `execute`\n * 3. Environment variable mapping for the standalone CLI\n */\n configSchema?: TConfigSchema;\n /**\n * Declare your tools here. Receives a typed `tool()` factory as its argument.\n *\n * Using a callback (rather than a plain array) is what allows TypeScript to\n * thread the config type through to each tool's `execute` function.\n */\n tools: (tool: ToolFactory<Static<TConfigSchema>>) => ToolDef[];\n}): () => PluginEntry {\n return () => {\n // The factory is identity at runtime — all type magic is compile-time only.\n const toolFactory = ((toolDef: unknown) => toolDef) as ToolFactory<Static<TConfigSchema>>;\n const toolDefs = def.tools(toolFactory);\n\n return {\n id: def.id,\n name: def.name,\n description: def.description,\n activation: def.activation ?? { onStartup: true },\n // Derive contracts from the declared tools so the manifest is always accurate.\n contracts: { tools: toolDefs.map((t) => t.name) },\n // TypeBox TObject is valid JSON Schema — pass through for the manifest generator.\n configSchema: def.configSchema as unknown as PluginEntry[\"configSchema\"],\n register(api: PluginApi) {\n // OpenClaw validates pluginConfig against configSchema before calling register(),\n // so this cast is safe. Fall back to empty object if config is not yet set.\n const config = (api.pluginConfig ?? {}) as Static<TConfigSchema>;\n\n for (const toolDef of toolDefs) {\n api.registerTool({\n name: toolDef.name,\n label: toolDef.label ?? toolDef.name,\n description: toolDef.description,\n parameters: toolDef.parameters,\n // Wrap the result automatically — execute() returns plain values, not formatResult().\n execute: async (_toolCallId: string, params: Record<string, unknown>) =>\n formatResult(await toolDef.execute(params, config)),\n });\n }\n },\n };\n };\n}\n\n// ---------------------------------------------------------------------------\n// Low-level types — the plugin contract\n//\n// These are used by the generated adapter and by advanced plugins that need\n// more control than definePlugin provides (e.g. dynamic tool registration).\n// ---------------------------------------------------------------------------\n\n/**\n * The API object passed to your plugin's `register()` function.\n *\n * When using `definePlugin`, you never see this directly — the SDK handles it.\n * It is exported for advanced use cases and for the generated adapter.\n */\nexport type PluginApi = {\n /** Register a tool with OpenClaw. Call once per tool inside register(). */\n registerTool: (tool: unknown) => void;\n /**\n * The user's config values for this plugin, keyed by field name.\n * Validated against configSchema by OpenClaw before register() is called.\n * May be undefined if the user has not configured the plugin.\n */\n pluginConfig?: Record<string, unknown>;\n};\n\n/**\n * The object returned by `createEntry()` — the plugin's public contract.\n *\n * When using `definePlugin`, this is constructed automatically.\n * Exported for advanced plugins that build it manually.\n */\nexport interface PluginEntry {\n /** Unique plugin id. Used as the CLI binary name and OpenClaw config key. */\n id: string;\n /** Human-readable display name. */\n name: string;\n /** One-sentence description. */\n description?: string;\n /** Tool names this plugin promises to register. Derived automatically by `definePlugin`. */\n contracts?: { tools: string[] };\n /** When to load the plugin. Defaults to `{ onStartup: true }`. */\n activation?: { onStartup?: boolean };\n /**\n * JSON Schema for the plugin's config block.\n * Pass a TypeBox `Type.Object(...)` — it is valid JSON Schema and gives you type inference.\n */\n configSchema?: unknown;\n /** Called once by OpenClaw at startup. Use `definePlugin` instead of implementing this directly. */\n register(api: PluginApi): void;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Wrap a value in the standard OpenClaw tool result format.\n *\n * When using `definePlugin`, you do NOT call this yourself — the SDK calls it\n * automatically after your `execute` function returns.\n *\n * For advanced plugins that implement `register()` directly, wrap your\n * execute return values with this function.\n *\n * Both fields carry the result: `content` is the text the model reads on a\n * direct tool call, and `details` is the structured value. OpenClaw Code Mode\n * hands `details` (not `content`) to the calling script, and grades the call\n * from it — so a top-level `ok: false`, truthy `error`, or failure-word\n * `status` (e.g. \"failed\", \"unavailable\") marks the call failed.\n *\n * @param data - Anything JSON-serialisable, or a plain string.\n * @returns `{ content: [{ type: \"text\", text: \"<json>\" }], details: <data> }`\n */\nexport function formatResult(data: unknown) {\n let text: string;\n let details: unknown = {};\n if (typeof data === \"string\") {\n text = data;\n details = data;\n } else {\n try {\n const json = JSON.stringify(data);\n if (json === undefined) {\n text = String(data);\n } else {\n text = json;\n // Round-trip so details matches content exactly (Dates as strings, no functions).\n details = JSON.parse(json);\n }\n } catch {\n text = String(data);\n }\n }\n return {\n content: [{ type: \"text\" as const, text }],\n details,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Adapter factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create the plugin's OpenClaw adapter export.\n *\n * **You never call this yourself.** It is called by the generated `dist/adapter.js`.\n *\n * Attempts to load the optional `openclaw` peer dependency and wrap the plugin\n * entry with the host's `definePluginEntry()`. Falls back to the raw entry if\n * `openclaw` is not installed (standalone CLI mode).\n *\n * @param entry - The object returned by `createEntry()`.\n * @param callerUrl - Pass `import.meta.url` from the generated adapter file.\n */\nexport function createAdapter(entry: PluginEntry, callerUrl: string): unknown {\n // v2: contracts.tools is required — plugins must declare tools via definePlugin\n // or set contracts.tools explicitly. The ensureContracts fallback has been removed.\n if (!entry.contracts?.tools?.length) {\n throw new Error(\n `Plugin \"${entry.id ?? entry.name}\" is missing contracts.tools. ` +\n `Migrate to definePlugin() from carapace-plugin-sdk — see https://github.com/JeffSteinbok/carapace-plugin-sdk`,\n );\n }\n\n const req = createRequire(callerUrl);\n\n try {\n const sdk = req(\"openclaw/plugin-sdk/plugin-entry\") as {\n definePluginEntry?: (e: unknown) => unknown;\n };\n\n if (typeof sdk.definePluginEntry !== \"function\") {\n throw new Error(\n \"OpenClaw SDK loaded but did not export `definePluginEntry`. Upgrade the `openclaw` package.\",\n );\n }\n\n const defined = sdk.definePluginEntry(entry) as Record<string, unknown>;\n // definePluginEntry may strip contracts — preserve them from the entry.\n if (entry.contracts && !defined.contracts) {\n defined.contracts = entry.contracts;\n }\n return defined;\n } catch (err: unknown) {\n if (isModuleNotFoundError(err)) return entry;\n throw err;\n }\n}\n\nfunction isModuleNotFoundError(err: unknown): boolean {\n if (!(err instanceof Error)) return false;\n return (\n \"code\" in err &&\n ((err as { code: string }).code === \"MODULE_NOT_FOUND\" ||\n (err as { code: string }).code === \"ERR_MODULE_NOT_FOUND\")\n );\n}\n"],"mappings":";AAeA,SAAS,qBAAqB;AAqFvB,SAAS,aAAsD,KAyBhD;AACpB,SAAO,MAAM;AAEX,UAAM,eAAe,CAAC,YAAqB;AAC3C,UAAM,WAAW,IAAI,MAAM,WAAW;AAEtC,WAAO;AAAA,MACL,IAAI,IAAI;AAAA,MACR,MAAM,IAAI;AAAA,MACV,aAAa,IAAI;AAAA,MACjB,YAAY,IAAI,cAAc,EAAE,WAAW,KAAK;AAAA;AAAA,MAEhD,WAAW,EAAE,OAAO,SAAS,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE;AAAA;AAAA,MAEhD,cAAc,IAAI;AAAA,MAClB,SAAS,KAAgB;AAGvB,cAAM,SAAU,IAAI,gBAAgB,CAAC;AAErC,mBAAW,WAAW,UAAU;AAC9B,cAAI,aAAa;AAAA,YACf,MAAM,QAAQ;AAAA,YACd,OAAO,QAAQ,SAAS,QAAQ;AAAA,YAChC,aAAa,QAAQ;AAAA,YACrB,YAAY,QAAQ;AAAA;AAAA,YAEpB,SAAS,OAAO,aAAqB,WACnC,aAAa,MAAM,QAAQ,QAAQ,QAAQ,MAAM,CAAC;AAAA,UACtD,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AA0EO,SAAS,aAAa,MAAe;AAC1C,MAAI;AACJ,MAAI,UAAmB,CAAC;AACxB,MAAI,OAAO,SAAS,UAAU;AAC5B,WAAO;AACP,cAAU;AAAA,EACZ,OAAO;AACL,QAAI;AACF,YAAM,OAAO,KAAK,UAAU,IAAI;AAChC,UAAI,SAAS,QAAW;AACtB,eAAO,OAAO,IAAI;AAAA,MACpB,OAAO;AACL,eAAO;AAEP,kBAAU,KAAK,MAAM,IAAI;AAAA,MAC3B;AAAA,IACF,QAAQ;AACN,aAAO,OAAO,IAAI;AAAA,IACpB;AAAA,EACF;AACA,SAAO;AAAA,IACL,SAAS,CAAC,EAAE,MAAM,QAAiB,KAAK,CAAC;AAAA,IACzC;AAAA,EACF;AACF;AAkBO,SAAS,cAAc,OAAoB,WAA4B;AAG5E,MAAI,CAAC,MAAM,WAAW,OAAO,QAAQ;AACnC,UAAM,IAAI;AAAA,MACR,WAAW,MAAM,MAAM,MAAM,IAAI;AAAA,IAEnC;AAAA,EACF;AAEA,QAAM,MAAM,cAAc,SAAS;AAEnC,MAAI;AACF,UAAM,MAAM,IAAI,kCAAkC;AAIlD,QAAI,OAAO,IAAI,sBAAsB,YAAY;AAC/C,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,UAAM,UAAU,IAAI,kBAAkB,KAAK;AAE3C,QAAI,MAAM,aAAa,CAAC,QAAQ,WAAW;AACzC,cAAQ,YAAY,MAAM;AAAA,IAC5B;AACA,WAAO;AAAA,EACT,SAAS,KAAc;AACrB,QAAI,sBAAsB,GAAG,EAAG,QAAO;AACvC,UAAM;AAAA,EACR;AACF;AAEA,SAAS,sBAAsB,KAAuB;AACpD,MAAI,EAAE,eAAe,OAAQ,QAAO;AACpC,SACE,UAAU,QACR,IAAyB,SAAS,sBACjC,IAAyB,SAAS;AAEzC;","names":[]}
|