实战示例
10.1 基础:字符串参数
typescript
const urlParameter: PropertyString = {
name: "url",
type: "string",
display_name: { en_US: "URL", zh_Hans: "URL" },
required: true,
ui: {
component: "input",
placeholder: { en_US: "https://example.com", zh_Hans: "https://example.com" },
hint: { en_US: "The URL of the web page to scrape", zh_Hans: "要抓取的网页 URL" },
},
}用户输入示例及对应的 invoke 参数:
typescript
// 用户填入 URL
const params = {
url: "https://example.com",
}
// tool.invoke({ args }) 接收到的参数
invoke: async ({ args }) => {
const { parameters } = args
console.log(parameters.url) // "https://example.com"
// 使用 parameters.url 调用 API
return { success: true, content: "..." }
}10.2 带枚举的下拉选择
typescript
const formatParameter: PropertyString = {
name: "format",
type: "string",
display_name: { en_US: "Format", zh_Hans: "格式" },
enum: ["markdown", "html", "rawHtml", "screenshot", "links"],
default: "markdown",
ui: {
component: "select",
searchable: true,
options: [
{ label: { en_US: "Markdown", zh_Hans: "Markdown" }, value: "markdown" },
{ label: { en_US: "HTML", zh_Hans: "HTML" }, value: "html" },
{ label: { en_US: "Raw HTML", zh_Hans: "原始 HTML" }, value: "rawHtml" },
{ label: { en_US: "Screenshot", zh_Hans: "截图" }, value: "screenshot" },
{ label: { en_US: "Links", zh_Hans: "链接" }, value: "links" },
],
},
}用户选择示例及对应的 invoke 参数:
typescript
// 用户从下拉菜单选择 "html"
const params = {
format: "html",
}
// invoke 接收到的参数
invoke: async ({ args }) => {
const { parameters } = args
switch (parameters.format) {
case "markdown":
return { content: "# Title\n..." }
case "html":
return { content: "<h1>Title</h1>..." }
case "screenshot":
return { content: "base64://..." }
default:
return { content: "" }
}
}10.3 嵌套对象 + 可折叠面板
typescript
const locationParameter: PropertyObject = {
name: "location",
type: "object",
display_name: { en_US: "Location", zh_Hans: "位置" },
ui: {
component: "collapsible-panel",
default_collapsed: true,
},
properties: [
{
name: "country",
type: "string",
display_name: { en_US: "Country", zh_Hans: "国家" },
default: "US",
ui: {
component: "select",
options: [
{ label: { en_US: "United States", zh_Hans: "美国" }, value: "US" },
{ label: { en_US: "China", zh_Hans: "中国" }, value: "CN" },
{ label: { en_US: "Japan", zh_Hans: "日本" }, value: "JP" },
],
},
enum: ["US", "CN", "JP"],
},
{
name: "languages",
type: "array",
display_name: { en_US: "Languages", zh_Hans: "语言" },
items: { name: "lang", type: "string" },
ui: { component: "tag-input" },
},
],
}用户填入示例及对应的 invoke 参数:
typescript
// 用户展开可折叠面板,选择国家和输入语言标签
const params = {
location: {
country: "CN",
languages: ["Mandarin", "Cantonese", "English"],
},
}
// invoke 接收到嵌套对象
invoke: async ({ args }) => {
const { parameters } = args
const { country, languages } = parameters.location
console.log(country) // "CN"
console.log(languages) // ["Mandarin", "Cantonese", "English"]
return { success: true }
}10.4 数组 + 键值对编辑器
typescript
const headersParameter: PropertyArray = {
name: "headers",
type: "array",
display_name: { en_US: "Headers", zh_Hans: "请求头" },
items: {
name: "header",
type: "object",
properties: [
{
name: "key",
type: "string",
display_name: { en_US: "Key", zh_Hans: "键" },
},
{
name: "value",
type: "string",
display_name: { en_US: "Value", zh_Hans: "值" },
},
],
},
ui: { component: "key-value-editor" },
}用户添加键值对示例及对应的 invoke 参数:
typescript
// 用户在键值对编辑器中添加多条 HTTP Header
const params = {
headers: [
{ key: "Authorization", value: "Bearer token123" },
{ key: "Content-Type", value: "application/json" },
{ key: "User-Agent", value: "MyApp/1.0" },
],
}
// invoke 处理数组
invoke: async ({ args }) => {
const { parameters } = args
const headerObject = {}
parameters.headers.forEach((h) => {
headerObject[h.key] = h.value
})
// headerObject = { Authorization: "Bearer token123", ... }
const response = await fetch(url, { headers: headerObject })
return { success: true }
}10.5 Discriminated Union — 根据选择切换参数
这是一个完整的实际场景:根据不同的 scrape format 展示不同配置。
typescript
const formatsParameter: PropertyDiscriminatedUnion = {
name: "output",
type: "discriminated_union",
display_name: { en_US: "Output", zh_Hans: "输出" },
discriminator: "type",
discriminator_ui: { component: "select" },
any_of: [
// 变体 1: Markdown
{
name: "markdown_variant",
type: "object",
properties: [
{
name: "type",
type: "string",
constant: "markdown",
display_name: { en_US: "Markdown", zh_Hans: "Markdown" },
},
// Markdown 无额外参数
],
},
// 变体 2: JSON (Extract)
{
name: "extract_variant",
type: "object",
properties: [
{
name: "type",
type: "string",
constant: "extract",
display_name: { en_US: "Structured Extract", zh_Hans: "结构化提取" },
},
{
name: "schema",
type: "object",
display_name: { en_US: "Schema", zh_Hans: "数据结构" },
ui: { component: "code-editor", language: "json" },
properties: [],
},
{
name: "system_prompt",
type: "string",
display_name: { en_US: "System Prompt", zh_Hans: "系统提示" },
ui: { component: "textarea" },
},
],
},
// 变体 3: Screenshot
{
name: "screenshot_variant",
type: "object",
properties: [
{
name: "type",
type: "string",
constant: "screenshot",
display_name: { en_US: "Screenshot", zh_Hans: "截图" },
},
{
name: "full_page",
type: "boolean",
display_name: { en_US: "Full Page", zh_Hans: "全页" },
default: false,
},
],
},
],
}用户选择不同变体的 invoke 参数示例:
typescript
// 变体 1:用户选择 "markdown" - 无额外参数
const params1 = {
output: {
type: "markdown",
},
}
// 变体 2:用户选择 "extract" - 包含 schema 和 system_prompt
const params2 = {
output: {
type: "extract",
schema: {
type: "object",
properties: {
title: { type: "string" },
price: { type: "number" },
},
},
system_prompt: "Extract product information accurately",
},
}
// 变体 3:用户选择 "screenshot" - 包含 full_page 选项
const params3 = {
output: {
type: "screenshot",
full_page: true,
},
}
// invoke 根据 type 字段切换处理逻辑
invoke: async ({ args }) => {
const { parameters } = args
const { type, ...rest } = parameters.output
switch (type) {
case "markdown":
return { content: "# Title\n..." }
case "extract":
const schema = rest.schema
return { data: { title: "...", price: 99.9 } }
case "screenshot":
const fullPage = rest.full_page
return { image: "base64://..." }
}
}10.6 条件显示:联动参数
typescript
const parameters: Property[] = [
{
name: "mode",
type: "string",
display_name: { en_US: "Mode", zh_Hans: "模式" },
enum: ["simple", "advanced"],
default: "simple",
ui: { component: "radio-group" },
},
// 仅在 Advanced 模式下显示
{
name: "custom_headers",
type: "object",
display_name: { en_US: "Custom Headers", zh_Hans: "自定义请求头" },
additional_properties: true,
properties: [],
display: {
show: { mode: "advanced" },
},
},
// 仅在 Advanced 模式下显示
{
name: "retry_count",
type: "integer",
display_name: { en_US: "Retry Count", zh_Hans: "重试次数" },
default: 3,
minimum: 0,
maximum: 10,
display: {
show: { mode: "advanced" },
},
},
]用户选择不同模式的 invoke 参数示例:
typescript
// 场景 1:Simple 模式 - 不会包含 custom_headers 和 retry_count
const paramsSimple = {
mode: "simple",
}
// 场景 2:Advanced 模式 - 包含隐藏字段
const paramsAdvanced = {
mode: "advanced",
custom_headers: {
"X-Custom-Header": "value1",
"X-Another-Header": "value2",
},
retry_count: 5,
}
// invoke 逻辑
invoke: async ({ args }) => {
const { parameters } = args
if (parameters.mode === "simple") {
// 使用默认配置
return { success: true, retry_count: 3 }
} else {
// 使用用户自定义的高级选项
return {
success: true,
headers: parameters.custom_headers,
retry_count: parameters.retry_count,
}
}
}10.7 凭证参数
typescript
const credentialParameter: PropertyCredentialId = {
name: "credential_id",
type: "credential_id",
display_name: { en_US: "Credential", zh_Hans: "凭证" },
credential_name: "firecrawl",
required: true,
}invoke 接收到的凭证 ID 示例:
typescript
interface ToolArgs {
parameters: { credential_id: string; url?: string }
credentials: Record<string, { api_key: string }>
}
const params = {
credential_id: "cred_65f3a2b9d8e1c4f7a9b2e5d1",
}
// invoke 使用凭证直接进行 API 调用
invoke: async ({ args }: { args: ToolArgs }) => {
const { parameters, credentials } = args
// 根据 credential_id 从 credentials 中获取对应的凭证
const credentialId = parameters.credential_id
const credential = credentials[credentialId]
// credential = { api_key: "fc-xxxxx..." }
// 使用凭证调用外部 API
const response = await firecrawlApi.scrape(
{
url: parameters.url,
},
{
auth: credential.api_key,
},
)
return { success: true, data: response.data }
}10.8 完整工具定义示例
以下展示一个类似 Firecrawl Scrape 工具的完整定义:
typescript
const scrapeTool: ToolDefinition = {
name: "scrape-a-url",
display_name: { en_US: "Scrape", zh_Hans: "网页抓取" },
description: { en_US: "Scrape web pages and extract content", zh_Hans: "抓取网页并提取内容" },
icon: "🔥",
parameters: [
// 凭证
{
name: "credential_id",
type: "credential_id",
credential_name: "firecrawl",
required: true,
},
// 基础参数
{
name: "url",
type: "string",
display_name: { en_US: "URL", zh_Hans: "URL" },
required: true,
ui: {
component: "input",
placeholder: { en_US: "https://example.com", zh_Hans: "https://example.com" },
},
},
// 输出格式(多选)
{
name: "formats",
type: "array",
display_name: { en_US: "Formats", zh_Hans: "格式" },
items: {
name: "format",
type: "string",
enum: ["markdown", "html", "screenshot"],
},
default: ["markdown"],
ui: { component: "multi-select" },
},
// 高级选项(可折叠)
{
name: "options",
type: "object",
display_name: { en_US: "Options", zh_Hans: "选项" },
ui: {
component: "collapsible-panel",
default_collapsed: true,
},
properties: [
{
name: "include_tags",
type: "array",
display_name: { en_US: "Include Tags", zh_Hans: "包含标签" },
items: { name: "tag", type: "string" },
ui: { component: "tag-input" },
ai: { llm_description: "HTML tags to include in the output" },
},
{
name: "exclude_tags",
type: "array",
display_name: { en_US: "Exclude Tags", zh_Hans: "排除标签" },
items: { name: "tag", type: "string" },
ui: { component: "tag-input" },
},
{
name: "wait_for",
type: "integer",
display_name: { en_US: "Wait For", zh_Hans: "等待时间" },
default: 0,
minimum: 0,
ui: {
hint: { en_US: "Wait time in milliseconds before scraping", zh_Hans: "抓取前的等待时间(毫秒)" },
},
},
{
name: "timeout",
type: "integer",
display_name: { en_US: "Timeout", zh_Hans: "超时" },
default: 30000,
},
{
name: "only_main_content",
type: "boolean",
display_name: { en_US: "Only Main Content", zh_Hans: "仅主要内容" },
default: true,
ui: { component: "switch" },
},
],
},
// HTTP Headers(带额外属性的对象)
{
name: "headers",
type: "object",
display_name: { en_US: "Headers", zh_Hans: "请求头" },
additional_properties: true,
properties: [],
ui: {
component: "collapsible-panel",
default_collapsed: true,
},
},
],
invoke: async ({
args,
}: {
args: { parameters: any; credentials: Record<string, { api_key: string }> }
}) => {
// 实际调用逻辑
const { parameters, credentials } = args
const { credential_id, url, formats, options, headers } = parameters
// 构建 API 请求参数
const requestParams = {
url: url,
formats: formats, // ["markdown", "html"]
include_tags: options.include_tags,
exclude_tags: options.exclude_tags,
wait_for: options.wait_for,
timeout: options.timeout,
only_main_content: options.only_main_content,
headers: headers, // { "Authorization": "...", ... }
}
// 根据 credential_id 从 credentials 中获取凭证后调用 API
const credential = credentials[credential_id]
const response = await firecrawlApi.scrape(requestParams, credential.api_key)
return {
success: true,
content: response.data,
formats: formats,
}
},
}10.9 级联资源定位器
一个任务管理工具,用户先选择工作空间,再选择该工作空间内的项目,最后选择该项目内的任务。每一级均依赖于其上一级。
参数定义:
typescript
import { extractResourceLocator } from "@choiceopen/atomemo-plugin-sdk-js"
const parameters: Array<Property> = [
{
name: "credential_id",
type: "credential_id",
credential_name: "my-service",
required: true,
},
// 第 1 级:工作空间——无依赖
{
name: "workspace",
type: "resource_locator",
display_name: { en_US: "Workspace", zh_Hans: "工作空间" },
required: true,
modes: [
{ type: "list", search_list_method: "search_workspaces", searchable: true },
{ type: "id", placeholder: { en_US: "Enter workspace ID", zh_Hans: "输入工作空间 ID" } },
],
},
// 第 2 级:项目——工作空间变更时重置
{
name: "project",
type: "resource_locator",
display_name: { en_US: "Project", zh_Hans: "项目" },
required: true,
depends_on: ["workspace"],
modes: [
{ type: "list", search_list_method: "search_projects", searchable: true },
{ type: "id", placeholder: { en_US: "Enter project ID", zh_Hans: "输入项目 ID" } },
],
},
// 第 3 级:任务——工作空间或项目任意变更时重置
{
name: "task",
type: "resource_locator",
display_name: { en_US: "Task", zh_Hans: "任务" },
required: true,
depends_on: ["workspace", "project"],
modes: [
{ type: "list", search_list_method: "search_tasks", searchable: true },
{
type: "url",
placeholder: { en_US: "https://app.example.com/tasks/...", zh_Hans: "https://app.example.com/tasks/..." },
extract_value: {
type: "regex",
regex: "https://app\\.example\\.com/tasks/([A-Za-z0-9_-]+)",
},
},
{ type: "id", placeholder: { en_US: "Enter task ID", zh_Hans: "输入任务 ID" } },
],
},
]locator_list 回调:
typescript
locator_list: {
search_workspaces: async ({ credentials, filter }) => {
const token = credentials["my-service"].api_key
const workspaces = await apiClient.listWorkspaces(token)
return {
results: workspaces
.filter(w => !filter || w.name.toLowerCase().includes(filter.toLowerCase()))
.map(w => ({ label: w.name, value: w.id, url: w.url })),
}
},
search_projects: async ({ parameters, credentials, filter }) => {
const workspaceId = extractResourceLocator(parameters.workspace)
if (!workspaceId) return { results: [] }
const token = credentials["my-service"].api_key
const projects = await apiClient.listProjects(token, workspaceId)
return {
results: projects
.filter(p => !filter || p.name.toLowerCase().includes(filter.toLowerCase()))
.map(p => ({ label: p.name, value: p.id })),
}
},
search_tasks: async ({ parameters, credentials, filter }) => {
const projectId = extractResourceLocator(parameters.project)
if (!projectId) return { results: [] }
const token = credentials["my-service"].api_key
const tasks = await apiClient.listTasks(token, projectId)
return {
results: tasks
.filter(t => !filter || t.title.toLowerCase().includes(filter.toLowerCase()))
.map(t => ({ label: t.title, value: t.id })),
}
},
},invoke — 提取值并调用 API:
typescript
invoke: async ({ args }) => {
const { parameters, credentials } = args
const token = credentials["my-service"].api_key
const workspaceId = extractResourceLocator(parameters.workspace)
const projectId = extractResourceLocator(parameters.project)
// url 模式时,传入正则表达式从 URL 中提取 ID
const taskId = extractResourceLocator(
parameters.task,
/https:\/\/app\.example\.com\/tasks\/([A-Za-z0-9_-]+)/,
)
const task = await apiClient.getTask(token, workspaceId, projectId, taskId)
return { task }
}用户选择示例及对应 invoke 参数:
typescript
// 用户通过下拉框选择(list 模式)
const params = {
workspace: {
__type__: "resource_locator",
mode_name: "list",
value: "ws_abc123",
cached_result_label: "工程团队",
},
project: {
__type__: "resource_locator",
mode_name: "list",
value: "proj_xyz789",
cached_result_label: "Q2 路线图",
},
// 用户粘贴 URL(url 模式)
task: {
__type__: "resource_locator",
mode_name: "url",
value: "https://app.example.com/tasks/task_def456",
},
}
// extractResourceLocator(params.task, /...\/tasks\/([A-Za-z0-9_-]+)/)
// → "task_def456"10.10 资源映射器——动态字段映射
一个记录创建工具,动态加载目标表格的列定义,让用户为每个字段填入对应的值。当用户切换所选表格时,可用字段会自动更新。
参数定义:
typescript
import { extractResourceLocator, extractResourceMapper } from "@choiceopen/atomemo-plugin-sdk-js"
const parameters: Array<Property> = [
{
name: "credential_id",
type: "credential_id",
credential_name: "my-service",
required: true,
},
{
name: "workspace",
type: "resource_locator",
display_name: { en_US: "Workspace", zh_Hans: "工作空间" },
required: true,
modes: [{ type: "list", search_list_method: "search_workspaces", searchable: true }],
},
{
name: "table",
type: "resource_locator",
display_name: { en_US: "Table", zh_Hans: "表格" },
required: true,
depends_on: ["workspace"],
modes: [{ type: "list", search_list_method: "search_tables", searchable: true }],
},
// 字段映射器——依赖 workspace + table,任意变更时重新拉取字段
{
name: "fields",
type: "resource_mapper",
display_name: { en_US: "Fields", zh_Hans: "字段" },
required: true,
depends_on: ["workspace", "table"],
mapping_method: "map_table_fields",
},
]resource_mapping 回调:
typescript
resource_mapping: {
map_table_fields: async ({ args }) => {
const token = args.credentials["my-service"].api_key
const tableId = extractResourceLocator(args.parameters.table)
if (!tableId) {
return {
fields: [],
empty_fields_notice: { en_US: "Select a table to see available fields.", zh_Hans: "请先选择一张表格以查看可用字段。" },
}
}
const schema = await apiClient.getTableSchema(token, tableId)
return {
fields: schema.columns.map(col => ({
id: col.id,
display_name: { en_US: col.name },
type: col.dataType, // "string" | "number" | "boolean" 等
required: col.required,
ui: {
hint: col.description ? { en_US: col.description } : null,
},
})),
}
},
},invoke — 提取映射并创建记录:
typescript
invoke: async ({ args }) => {
const { parameters, credentials } = args
const token = credentials["my-service"].api_key
const tableId = extractResourceLocator(parameters.table)
// 返回 Record<string, unknown> | null
const fieldValues = extractResourceMapper(parameters.fields)
const record = await apiClient.createRecord(token, tableId, fieldValues ?? {})
return { record_id: record.id }
}用户输入示例及对应 invoke 参数:
typescript
// 用户在手动模式下填写 name、priority、due_date 字段
const params = {
table: {
__type__: "resource_locator",
mode_name: "list",
value: "tbl_tasks",
cached_result_label: "任务",
},
fields: {
__type__: "resource_mapper",
mapping_mode: "manual",
value: {
name: "修复登录 Bug",
priority: 1,
due_date: "2026-04-01",
completed: false,
},
},
}
// extractResourceMapper(params.fields)
// → { name: "修复登录 Bug", priority: 1, due_date: "2026-04-01", completed: false }10.11 文件引用参数
typescript
const fileParameter: PropertyFileReference = {
name: "file",
type: "file_ref",
display_name: { en_US: "File", zh_Hans: "文件" },
required: true,
}用户输入示例及对应的 invoke 行为:
typescript
// 用户在上游节点(例如「文件上传」节点)中产生文件引用
const params = {
file: $('File Upload').file, // 表达式解析后得到 file_ref 值
}
invoke: async ({ args, context }) => {
const { parameters } = args
// 将 parameters.file 视为不透明引用,通过 context.files 解析
const fileRef = context.files.parseFileRef(parameters.file)
const downloaded = await context.files.download(fileRef)
// 访问元数据
const filename = downloaded.filename
const mimeType = downloaded.mime_type
// 原始字节以 base64 编码形式存储在 downloaded.content 中
const bytes = Buffer.from(downloaded.content ?? "", "base64")
// ...使用 bytes 调用外部 API 或做文件处理
return { success: true, filename, mimeType }
}