diff --git a/docs/.generated/plugin-sdk-api-baseline.sha256 b/docs/.generated/plugin-sdk-api-baseline.sha256 index 5188621c7ce8..37b8b61aeeee 100644 --- a/docs/.generated/plugin-sdk-api-baseline.sha256 +++ b/docs/.generated/plugin-sdk-api-baseline.sha256 @@ -1,2 +1,2 @@ -01c41d19cf15a0c2759e8f13064ecd5f00360fec467fc8fa47eb1f13907be379 plugin-sdk-api-baseline.json -c331d008ecad33627b4d0f08ddeaa6430c51878d0fcaa36c9d61b4656a5f0c78 plugin-sdk-api-baseline.jsonl +71520f048737a3bb90fb776e722334bef8e76d4439e68f064e49ff1f261c5698 plugin-sdk-api-baseline.json +8398c4a25159f6f073a7418a4bc6472c1f4c1199ca13dd3005b6e9945c5c6a3b plugin-sdk-api-baseline.jsonl diff --git a/docs/plugins/sdk-entrypoints.md b/docs/plugins/sdk-entrypoints.md index 2be835bf4599..aaf7c1ebb82a 100644 --- a/docs/plugins/sdk-entrypoints.md +++ b/docs/plugins/sdk-entrypoints.md @@ -84,6 +84,8 @@ export default defineToolPlugin({ schema and the generated manifest still includes `configSchema`. - `execute` returns a plain string or JSON-serializable value. The helper wraps it as a text tool result with `details`. +- For custom tool results, `openclaw/plugin-sdk/tool-results` exports + `textResult` and `jsonResult`. - Tool names are static. `openclaw plugins build` derives `contracts.tools` from the declared tools, so authors do not duplicate names by hand. - Runtime loading stays strict. Installed plugins still need diff --git a/extensions/feishu/src/bitable.ts b/extensions/feishu/src/bitable.ts index 94f9b96e61af..358c4fc9a3b1 100644 --- a/extensions/feishu/src/bitable.ts +++ b/extensions/feishu/src/bitable.ts @@ -3,6 +3,7 @@ import type * as Lark from "@larksuiteoapi/node-sdk"; import { optionalPositiveIntegerSchema } from "openclaw/plugin-sdk/channel-actions"; import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime"; import { readPositiveIntegerParam } from "openclaw/plugin-sdk/param-readers"; +import { jsonResult as json } from "openclaw/plugin-sdk/tool-results"; import { Type, type TSchema } from "typebox"; import type { OpenClawPluginApi } from "../runtime-api.js"; import { listEnabledFeishuAccounts } from "./accounts.js"; @@ -10,15 +11,6 @@ import { createFeishuClient } from "./client.js"; import { resolveAnyEnabledFeishuToolsConfig, resolveFeishuToolAccount } from "./tool-account.js"; import { resolveToolsConfig } from "./tools-config.js"; -// ============ Helpers ============ - -function json(data: unknown) { - return { - content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }], - details: data, - }; -} - type LarkResponse = { code?: number; msg?: string; data?: T }; type BitableRecordCreatePayload = NonNullable< Parameters[0] diff --git a/extensions/feishu/src/chat.ts b/extensions/feishu/src/chat.ts index 51fba809eda8..49339890e715 100644 --- a/extensions/feishu/src/chat.ts +++ b/extensions/feishu/src/chat.ts @@ -1,6 +1,7 @@ // Feishu plugin module implements chat behavior. import type * as Lark from "@larksuiteoapi/node-sdk"; import { readPositiveIntegerParam } from "openclaw/plugin-sdk/param-readers"; +import { jsonResult as json } from "openclaw/plugin-sdk/tool-results"; import type { OpenClawPluginApi } from "../runtime-api.js"; import { listEnabledFeishuAccounts } from "./accounts.js"; import { FeishuChatSchema, type FeishuChatParams } from "./chat-schema.js"; @@ -8,13 +9,6 @@ import { createFeishuClient } from "./client.js"; import { formatFeishuApiError } from "./comment-shared.js"; import { resolveToolsConfig } from "./tools-config.js"; -function json(data: unknown) { - return { - content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }], - details: data, - }; -} - function readChatPageSize(params: Record): number | undefined { return readPositiveIntegerParam(params, "page_size", { max: 100, diff --git a/extensions/feishu/src/docx.ts b/extensions/feishu/src/docx.ts index 54aec4a1ec53..248c05119ad3 100644 --- a/extensions/feishu/src/docx.ts +++ b/extensions/feishu/src/docx.ts @@ -7,6 +7,7 @@ import type * as Lark from "@larksuiteoapi/node-sdk"; import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime"; import { extensionForMime } from "openclaw/plugin-sdk/media-mime"; import { normalizeOptionalString, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime"; +import { jsonResult as json } from "openclaw/plugin-sdk/tool-results"; import { Type } from "typebox"; import type { OpenClawPluginApi } from "../runtime-api.js"; import { listEnabledFeishuAccounts } from "./accounts.js"; @@ -29,15 +30,6 @@ import { resolveFeishuToolAccount, } from "./tool-account.js"; -// ============ Helpers ============ - -function json(data: unknown) { - return { - content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }], - details: data, - }; -} - function resolveDocToolLocalRoots(ctx: { workspaceDir?: string; fsPolicy?: { workspaceOnly: boolean }; diff --git a/extensions/feishu/src/drive.ts b/extensions/feishu/src/drive.ts index 40ed6f2884ca..410a9e6f367f 100644 --- a/extensions/feishu/src/drive.ts +++ b/extensions/feishu/src/drive.ts @@ -1,6 +1,7 @@ // Feishu plugin module implements drive behavior. import type * as Lark from "@larksuiteoapi/node-sdk"; import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime"; +import { jsonResult } from "openclaw/plugin-sdk/tool-results"; import type { OpenClawPluginApi } from "../runtime-api.js"; import { listEnabledFeishuAccounts } from "./accounts.js"; import { cleanupAmbientCommentTypingReaction } from "./comment-reaction.js"; @@ -14,11 +15,7 @@ import { import { parseFeishuCommentTarget, type CommentFileType } from "./comment-target.js"; import { FeishuDriveSchema, type FeishuDriveParams } from "./drive-schema.js"; import { createFeishuToolClient, resolveAnyEnabledFeishuToolsConfig } from "./tool-account.js"; -import { - jsonToolResult, - toolExecutionErrorResult, - unknownToolActionResult, -} from "./tool-result.js"; +import { toolExecutionErrorResult, unknownToolActionResult } from "./tool-result.js"; // ============ Actions ============ @@ -769,33 +766,33 @@ export function registerFeishuDriveTools(api: OpenClawPluginApi) { }); switch (p.action) { case "list": - return jsonToolResult(await listFolder(client, p.folder_token)); + return jsonResult(await listFolder(client, p.folder_token)); case "info": - return jsonToolResult(await getFileInfo(client, p.file_token)); + return jsonResult(await getFileInfo(client, p.file_token)); case "create_folder": - return jsonToolResult(await createFolder(client, p.name, p.folder_token)); + return jsonResult(await createFolder(client, p.name, p.folder_token)); case "move": - return jsonToolResult(await moveFile(client, p.file_token, p.type, p.folder_token)); + return jsonResult(await moveFile(client, p.file_token, p.type, p.folder_token)); case "delete": - return jsonToolResult(await deleteFile(client, p.file_token, p.type)); + return jsonResult(await deleteFile(client, p.file_token, p.type)); case "list_comments": { const resolved = applyCommentFileTypeDefault( applyAmbientCommentDefaults(p, ctx), "list_comments", ); - return jsonToolResult(await listComments(client, resolved)); + return jsonResult(await listComments(client, resolved)); } case "list_comment_replies": { const resolved = applyCommentFileTypeDefault( applyAmbientCommentDefaults(p, ctx), "list_comment_replies", ); - return jsonToolResult(await listCommentReplies(client, resolved)); + return jsonResult(await listCommentReplies(client, resolved)); } case "add_comment": { const resolved = applyAddCommentDefaults(applyAddCommentAmbientDefaults(p, ctx)); try { - return jsonToolResult(await addComment(client, resolved)); + return jsonResult(await addComment(client, resolved)); } finally { void cleanupAmbientCommentTypingReaction({ client: getDriveInternalClient(client), @@ -809,7 +806,7 @@ export function registerFeishuDriveTools(api: OpenClawPluginApi) { "reply_comment", ); try { - return jsonToolResult(await deliverCommentThreadText(client, resolved)); + return jsonResult(await deliverCommentThreadText(client, resolved)); } finally { void cleanupAmbientCommentTypingReaction({ client: getDriveInternalClient(client), diff --git a/extensions/feishu/src/perm.ts b/extensions/feishu/src/perm.ts index 50b27f833c17..93669dc15f7a 100644 --- a/extensions/feishu/src/perm.ts +++ b/extensions/feishu/src/perm.ts @@ -1,14 +1,11 @@ // Feishu plugin module implements perm behavior. import type * as Lark from "@larksuiteoapi/node-sdk"; +import { jsonResult } from "openclaw/plugin-sdk/tool-results"; import type { OpenClawPluginApi } from "../runtime-api.js"; import { listEnabledFeishuAccounts } from "./accounts.js"; import { FeishuPermSchema, type FeishuPermParams } from "./perm-schema.js"; import { createFeishuToolClient, resolveAnyEnabledFeishuToolsConfig } from "./tool-account.js"; -import { - jsonToolResult, - toolExecutionErrorResult, - unknownToolActionResult, -} from "./tool-result.js"; +import { toolExecutionErrorResult, unknownToolActionResult } from "./tool-result.js"; type ListTokenType = | "doc" @@ -149,13 +146,13 @@ export function registerFeishuPermTools(api: OpenClawPluginApi) { }); switch (p.action) { case "list": - return jsonToolResult(await listMembers(client, p.token, p.type)); + return jsonResult(await listMembers(client, p.token, p.type)); case "add": - return jsonToolResult( + return jsonResult( await addMember(client, p.token, p.type, p.member_type, p.member_id, p.perm), ); case "remove": - return jsonToolResult( + return jsonResult( await removeMember(client, p.token, p.type, p.member_type, p.member_id), ); default: diff --git a/extensions/feishu/src/tool-result.test.ts b/extensions/feishu/src/tool-result.test.ts index 05a1de4f61c4..9a63c40f0b2f 100644 --- a/extensions/feishu/src/tool-result.test.ts +++ b/extensions/feishu/src/tool-result.test.ts @@ -1,20 +1,8 @@ // Feishu tests cover tool result plugin behavior. import { describe, expect, it } from "vitest"; -import { - jsonToolResult, - toolExecutionErrorResult, - unknownToolActionResult, -} from "./tool-result.js"; - -describe("jsonToolResult", () => { - it("formats tool result with text content and details", () => { - const payload = { ok: true, id: "abc" }; - expect(jsonToolResult(payload)).toEqual({ - content: [{ type: "text", text: JSON.stringify(payload, null, 2) }], - details: payload, - }); - }); +import { toolExecutionErrorResult, unknownToolActionResult } from "./tool-result.js"; +describe("tool result errors", () => { it("formats unknown action errors", () => { expect(unknownToolActionResult("create")).toEqual({ content: [ diff --git a/extensions/feishu/src/tool-result.ts b/extensions/feishu/src/tool-result.ts index 029147964105..1a73af38f5d5 100644 --- a/extensions/feishu/src/tool-result.ts +++ b/extensions/feishu/src/tool-result.ts @@ -1,17 +1,11 @@ // Feishu plugin module implements tool result behavior. import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime"; - -export function jsonToolResult(data: unknown) { - return { - content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }], - details: data, - }; -} +import { jsonResult } from "openclaw/plugin-sdk/tool-results"; export function unknownToolActionResult(action: unknown) { - return jsonToolResult({ error: `Unknown action: ${String(action)}` }); + return jsonResult({ error: `Unknown action: ${String(action)}` }); } export function toolExecutionErrorResult(error: unknown) { - return jsonToolResult({ error: formatErrorMessage(error) }); + return jsonResult({ error: formatErrorMessage(error) }); } diff --git a/extensions/feishu/src/wiki.ts b/extensions/feishu/src/wiki.ts index 03f0d3d055c7..c1d80a0a43ff 100644 --- a/extensions/feishu/src/wiki.ts +++ b/extensions/feishu/src/wiki.ts @@ -1,14 +1,11 @@ // Feishu plugin module implements wiki behavior. import type * as Lark from "@larksuiteoapi/node-sdk"; import { readPositiveIntegerParam } from "openclaw/plugin-sdk/param-readers"; +import { jsonResult } from "openclaw/plugin-sdk/tool-results"; import type { OpenClawPluginApi } from "../runtime-api.js"; import { listEnabledFeishuAccounts } from "./accounts.js"; import { createFeishuToolClient, resolveAnyEnabledFeishuToolsConfig } from "./tool-account.js"; -import { - jsonToolResult, - toolExecutionErrorResult, - unknownToolActionResult, -} from "./tool-result.js"; +import { toolExecutionErrorResult, unknownToolActionResult } from "./tool-result.js"; import { FeishuWikiSchema, type FeishuWikiParams } from "./wiki-schema.js"; type ObjType = "doc" | "sheet" | "mindnote" | "bitable" | "file" | "docx" | "slides"; @@ -242,12 +239,12 @@ export function registerFeishuWikiTools(api: OpenClawPluginApi) { }); switch (p.action) { case "spaces": - return jsonToolResult( + return jsonResult( await listSpaces(createClient(), readWikiPageSize(p), p.page_token), ); case "nodes": { const spaceId = requireWikiSpaceId(p.space_id, "space_id"); - return jsonToolResult( + return jsonResult( await listNodes( createClient(), spaceId, @@ -258,17 +255,17 @@ export function registerFeishuWikiTools(api: OpenClawPluginApi) { ); } case "get": - return jsonToolResult(await getNode(createClient(), p.token)); + return jsonResult(await getNode(createClient(), p.token)); case "search": optionalWikiSpaceId(p.space_id, "space_id"); createClient(); - return jsonToolResult({ + return jsonResult({ error: "Search is not available. Use feishu_wiki with action: 'nodes' to browse or action: 'get' to lookup by token.", }); case "create": { const spaceId = requireWikiSpaceId(p.space_id, "space_id"); - return jsonToolResult( + return jsonResult( await createNode( createClient(), spaceId, @@ -280,7 +277,7 @@ export function registerFeishuWikiTools(api: OpenClawPluginApi) { } case "move": { const spaceId = requireWikiSpaceId(p.space_id, "space_id"); - return jsonToolResult( + return jsonResult( await moveNode( createClient(), spaceId, @@ -292,9 +289,7 @@ export function registerFeishuWikiTools(api: OpenClawPluginApi) { } case "rename": { const spaceId = requireWikiSpaceId(p.space_id, "space_id"); - return jsonToolResult( - await renameNode(createClient(), spaceId, p.node_token, p.title), - ); + return jsonResult(await renameNode(createClient(), spaceId, p.node_token, p.title)); } default: return unknownToolActionResult((p as { action?: unknown }).action); diff --git a/extensions/google-meet/index.ts b/extensions/google-meet/index.ts index 3d32b5a3919c..a1edb9462604 100644 --- a/extensions/google-meet/index.ts +++ b/extensions/google-meet/index.ts @@ -13,6 +13,7 @@ import { import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; import { definePluginEntry, type OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry"; import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime"; +import { jsonResult as json } from "openclaw/plugin-sdk/tool-results"; import { Type } from "typebox"; import { buildGoogleMeetCalendarDayWindow, @@ -348,13 +349,6 @@ function asParamRecord(params: unknown): Record { : {}; } -function json(payload: unknown) { - return { - content: [{ type: "text" as const, text: JSON.stringify(payload, null, 2) }], - details: payload, - }; -} - function normalizeTransport(value: unknown): GoogleMeetTransport | undefined { return value === "chrome" || value === "chrome-node" || value === "twilio" ? value : undefined; } diff --git a/extensions/lobster/src/lobster-tool.ts b/extensions/lobster/src/lobster-tool.ts index ce46a334c082..fbc51db37a5f 100644 --- a/extensions/lobster/src/lobster-tool.ts +++ b/extensions/lobster/src/lobster-tool.ts @@ -7,6 +7,7 @@ import { readNonNegativeIntegerParam, readPositiveIntegerParam, } from "openclaw/plugin-sdk/param-readers"; +import { jsonResult } from "openclaw/plugin-sdk/tool-results"; import { Type } from "typebox"; import type { OpenClawPluginApi } from "../runtime-api.js"; import { @@ -194,10 +195,7 @@ function formatManagedFlowResult(result: ManagedFlowSuccessResult) { flow: result.flow, mutation: result.mutation, }; - return { - content: [{ type: "text", text: JSON.stringify(details, null, 2) }], - details, - }; + return jsonResult(details); } function requireTaskFlowRuntime(taskFlow: BoundTaskFlow | undefined, action: "run" | "resume") { @@ -315,10 +313,7 @@ export function createLobsterTool(api: OpenClawPluginApi, options?: LobsterToolO if (!envelope.ok) { throw new Error(envelope.error.message); } - return { - content: [{ type: "text", text: JSON.stringify(envelope, null, 2) }], - details: envelope, - }; + return jsonResult(envelope); }, }; } diff --git a/extensions/qqbot/src/engine/tools/channel-api.ts b/extensions/qqbot/src/engine/tools/channel-api.ts index 6dd64e40c080..14e9c6c0ce68 100644 --- a/extensions/qqbot/src/engine/tools/channel-api.ts +++ b/extensions/qqbot/src/engine/tools/channel-api.ts @@ -13,6 +13,7 @@ import { readResponseTextLimited, } from "openclaw/plugin-sdk/provider-http"; import { fetchWithSsrFGuard, type SsrFPolicy } from "openclaw/plugin-sdk/ssrf-runtime"; +import { jsonResult as json } from "openclaw/plugin-sdk/tool-results"; import { formatErrorMessage } from "../utils/format.js"; import { debugLog, debugError } from "../utils/log.js"; @@ -175,13 +176,6 @@ function validateDeleteConfirmation(params: ChannelApiParams): string | null { return null; } -function json(data: unknown) { - return { - content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }], - details: data, - }; -} - /** * Options provided by the caller when executing a channel API request. * 执行频道 API 请求时由调用方提供的选项。 diff --git a/extensions/qqbot/src/engine/tools/remind-logic.ts b/extensions/qqbot/src/engine/tools/remind-logic.ts index a55f3775ca11..864c4191e4ac 100644 --- a/extensions/qqbot/src/engine/tools/remind-logic.ts +++ b/extensions/qqbot/src/engine/tools/remind-logic.ts @@ -1,6 +1,7 @@ // Qqbot plugin module implements remind logic behavior. import { resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime"; import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime"; +import { jsonResult as json } from "openclaw/plugin-sdk/tool-results"; /** * QQBot reminder tool core logic. @@ -259,13 +260,6 @@ export function formatDelay(ms: number): string { return `${hours}h${minutes}m`; } -function json(data: unknown) { - return { - content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }], - details: data, - }; -} - function formatSchedulerError(error: unknown): string { return error instanceof Error ? error.message : String(error); } diff --git a/extensions/voice-call/index.ts b/extensions/voice-call/index.ts index c0fc23da4d9a..6a41ddf3de8b 100644 --- a/extensions/voice-call/index.ts +++ b/extensions/voice-call/index.ts @@ -3,6 +3,7 @@ import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime"; import { ErrorCodes, errorShape } from "openclaw/plugin-sdk/gateway-runtime"; import { timestampMsToIsoString } from "openclaw/plugin-sdk/number-runtime"; import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime"; +import { jsonResult as json } from "openclaw/plugin-sdk/tool-results"; import { Type } from "typebox"; import { definePluginEntry, @@ -710,11 +711,6 @@ export default definePluginEntry({ parameters: VoiceCallToolSchema, async execute(_toolCallId, params) { const rawParams = asParamRecord(params); - const json = (payload: unknown) => ({ - content: [{ type: "text" as const, text: JSON.stringify(payload, null, 2) }], - details: payload, - }); - try { const rt = await ensureRuntime(); diff --git a/extensions/zalouser/src/tool.ts b/extensions/zalouser/src/tool.ts index 1b870a87dd0a..1fc17631622f 100644 --- a/extensions/zalouser/src/tool.ts +++ b/extensions/zalouser/src/tool.ts @@ -2,6 +2,7 @@ import { stringEnum } from "openclaw/plugin-sdk/channel-actions"; import type { AnyAgentTool, OpenClawPluginToolContext } from "openclaw/plugin-sdk/core"; import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime"; +import { jsonResult as json, type AgentToolResult } from "openclaw/plugin-sdk/tool-results"; import { Type } from "typebox"; import { sendImageZalouser, sendLinkZalouser, sendMessageZalouser } from "./send.js"; import { parseZalouserOutboundTarget } from "./session-route.js"; @@ -14,11 +15,6 @@ import { const ACTIONS = ["send", "image", "link", "friends", "groups", "me", "status"] as const; -type AgentToolResult = { - content: Array<{ type: "text"; text: string }>; - details: unknown; -}; - const ZalouserToolSchema = Type.Object( { action: stringEnum(ACTIONS, { description: `Action to perform: ${ACTIONS.join(", ")}` }), @@ -44,13 +40,6 @@ type ToolParams = { type ZalouserToolContext = Pick; -function json(payload: unknown): AgentToolResult { - return { - content: [{ type: "text", text: JSON.stringify(payload, null, 2) }], - details: payload, - }; -} - function resolveAmbientZalouserTarget(context?: ZalouserToolContext): { threadId?: string; isGroup?: boolean; @@ -96,7 +85,7 @@ export async function executeZalouserTool( _signal?: AbortSignal, _onUpdate?: unknown, context?: ZalouserToolContext, -): Promise { +): Promise> { try { switch (params.action) { case "send": { diff --git a/package.json b/package.json index 564b8c777ca0..af1bdebb88a7 100644 --- a/package.json +++ b/package.json @@ -1433,6 +1433,10 @@ "types": "./dist/plugin-sdk/tool-payload.d.ts", "default": "./dist/plugin-sdk/tool-payload.js" }, + "./plugin-sdk/tool-results": { + "types": "./dist/plugin-sdk/tool-results.d.ts", + "default": "./dist/plugin-sdk/tool-results.js" + }, "./plugin-sdk/tool-send": { "types": "./dist/plugin-sdk/tool-send.d.ts", "default": "./dist/plugin-sdk/tool-send.js" diff --git a/scripts/lib/plugin-sdk-doc-metadata.ts b/scripts/lib/plugin-sdk-doc-metadata.ts index d20154335c1f..bfd50f9071bd 100644 --- a/scripts/lib/plugin-sdk-doc-metadata.ts +++ b/scripts/lib/plugin-sdk-doc-metadata.ts @@ -108,6 +108,9 @@ export const pluginSdkDocMetadata = { "message-tool-delivery-hints": { category: "runtime", }, + "tool-results": { + category: "utilities", + }, "provider-selection-runtime": { category: "provider", }, diff --git a/scripts/lib/plugin-sdk-entrypoints.json b/scripts/lib/plugin-sdk-entrypoints.json index ebb3ef87f78f..2255695448d9 100644 --- a/scripts/lib/plugin-sdk-entrypoints.json +++ b/scripts/lib/plugin-sdk-entrypoints.json @@ -329,6 +329,7 @@ "text-utility-runtime", "tool-plugin", "tool-payload", + "tool-results", "tool-send", "webhook-ingress", "webhook-targets", diff --git a/scripts/plugin-sdk-surface-report.mjs b/scripts/plugin-sdk-surface-report.mjs index 631169de89f8..16a0894d3671 100644 --- a/scripts/plugin-sdk-surface-report.mjs +++ b/scripts/plugin-sdk-surface-report.mjs @@ -201,9 +201,9 @@ let budgets; let publicDeprecatedExportsByEntrypointBudget; try { budgets = { - publicEntrypoints: readBudgetEnv("OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_ENTRYPOINTS", 323), - publicExports: readBudgetEnv("OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_EXPORTS", 10425), - publicFunctionExports: readBudgetEnv("OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_FUNCTION_EXPORTS", 5237), + publicEntrypoints: readBudgetEnv("OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_ENTRYPOINTS", 324), + publicExports: readBudgetEnv("OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_EXPORTS", 10428), + publicFunctionExports: readBudgetEnv("OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_FUNCTION_EXPORTS", 5239), publicDeprecatedExports: readBudgetEnv( "OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_DEPRECATED_EXPORTS", 3261, diff --git a/src/agents/tools/common.ts b/src/agents/tools/common.ts index b775a97c6b72..3ecf9adcfc1e 100644 --- a/src/agents/tools/common.ts +++ b/src/agents/tools/common.ts @@ -21,6 +21,9 @@ import type { AgentToolUpdateCallback, } from "../runtime/index.js"; import { sanitizeToolResultImages } from "../tool-images.js"; +import { textResult } from "./tool-results.js"; + +export { jsonResult, textResult } from "./tool-results.js"; export type AgentToolWithMeta = AgentTool< TParameters, @@ -391,18 +394,6 @@ export function stringifyToolPayload(payload: unknown): string { return String(payload); } -export function textResult(text: string, details: TDetails): AgentToolResult { - return { - content: [ - { - type: "text", - text, - }, - ], - details, - }; -} - export function failedTextResult( text: string, details: TDetails, @@ -414,10 +405,6 @@ export function payloadTextResult(payload: TDetails): AgentToolResult< return textResult(stringifyToolPayload(payload), payload); } -export function jsonResult(payload: unknown): AgentToolResult { - return textResult(JSON.stringify(payload, null, 2), payload); -} - export type PublicToolProgress = Pick; export function toolProgressResult(progress: PublicToolProgress): AgentToolResult { diff --git a/src/agents/tools/tool-results.ts b/src/agents/tools/tool-results.ts new file mode 100644 index 000000000000..67684335e509 --- /dev/null +++ b/src/agents/tools/tool-results.ts @@ -0,0 +1,12 @@ +import type { AgentToolResult } from "../runtime/index.js"; + +export function textResult(text: string, details: TDetails): AgentToolResult { + return { + content: [{ type: "text", text }], + details, + }; +} + +export function jsonResult(payload: TDetails): AgentToolResult { + return textResult(JSON.stringify(payload, null, 2), payload); +} diff --git a/src/plugin-sdk/tool-results.test.ts b/src/plugin-sdk/tool-results.test.ts new file mode 100644 index 000000000000..759d3fc3f150 --- /dev/null +++ b/src/plugin-sdk/tool-results.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, expectTypeOf, it, vi } from "vitest"; + +vi.mock("../agents/tools/common.js", () => { + throw new Error("tool-results must not load the broad agent tool helpers"); +}); + +import { jsonResult, textResult, type AgentToolResult } from "./tool-results.js"; + +describe("tool result helpers", () => { + it("preserves typed JSON details", () => { + const payload = { ok: true, messageId: "msg-1" }; + const result = jsonResult(payload); + + expectTypeOf(result).toEqualTypeOf>(); + expect(result).toEqual({ + content: [{ type: "text", text: JSON.stringify(payload, null, 2) }], + details: payload, + }); + }); + + it("keeps model text separate from typed details", () => { + const details = { ok: true, messageId: "msg-1" }; + const result = textResult("Message sent.", details); + + expectTypeOf(result).toEqualTypeOf>(); + expect(result).toEqual({ + content: [{ type: "text", text: "Message sent." }], + details, + }); + }); +}); diff --git a/src/plugin-sdk/tool-results.ts b/src/plugin-sdk/tool-results.ts new file mode 100644 index 000000000000..6b12e6a74982 --- /dev/null +++ b/src/plugin-sdk/tool-results.ts @@ -0,0 +1,2 @@ +export type { AgentToolResult } from "../agents/runtime/index.js"; +export { jsonResult, textResult } from "../agents/tools/tool-results.js";