Skip to content

高级模式与最佳实践

共享参数模式

当多个工具共享相同的参数(如凭证、分页、排序),将它们提取为共享常量:

typescript
// _shared-parameters/credential.ts
export const credentialParameter: PropertyCredentialId = {
  name: "credential_id",
  type: "credential_id",
  credential_name: "notion",
  required: true,
}

// tools/create-page.ts
import { credentialParameter } from "../_shared-parameters/credential"

const createPageTool: ToolDefinition = {
  name: "create-page",
  parameters: [
    credentialParameter,
    // ... 其他参数
  ],
  invoke: async ({
    args,
  }: {
    args: { parameters: { [key: string]: any }; credentials: Record<string, { api_key: string }> }
  }) => {
    const { parameters, credentials } = args
    const credential = credentials[parameters.credential_id]
    // 使用凭证调用 Notion API
    return await notionApi.createPage(parameters, credential)
  },
}

### 表达式支持

允许用户输入动态表达式(引用上游节点数据):

```typescript
{
  name: "message",
  type: "string",
  ui: {
    component: "textarea",
    support_expression: true  // 开启表达式模式
  }
}

invoke 接收的参数示例(支持表达式后的值已被解析):

typescript
// 用户输入支持表达式的值:{{upstream_node.content}}
// 系统自动将表达式解析,invoke 接收实际值

const params = {
  message: "Hello, the page title is: Example Page",
}

invoke: async ({ args }) => {
  const { parameters } = args
  // parameters.message 包含已解析的内容
  console.log(parameters.message)
  return { sent: true }
}

常量字段(constant)

将字段设为只读的固定值,常用于 discriminated union:

typescript
{
  name: "type",
  type: "string",
  constant: "webhook",      // 值固定为 "webhook"
  display_name: { en_US: "Webhook", zh_Hans: "Webhook" }
}

设置了 constant 后:

  • 字段显示为只读
  • 值在初始化时自动填充
  • 不可被用户修改

嵌套 Discriminated Union

支持多层嵌套的 discriminated union:

typescript
{
  name: "action",
  type: "discriminated_union",
  discriminator: "type",
  any_of: [
    {
      name: "click",
      type: "object",
      properties: [
        { name: "type", type: "string", constant: "click" },
        // click 有自己的子 discriminated union
        {
          name: "target",
          type: "discriminated_union",
          discriminator: "method",
          any_of: [
            {
              name: "css",
              type: "object",
              properties: [
                { name: "method", type: "string", constant: "css" },
                { name: "selector", type: "string" }
              ]
            },
            {
              name: "xpath",
              type: "object",
              properties: [
                { name: "method", type: "string", constant: "xpath" },
                { name: "expression", type: "string" }
              ]
            }
          ]
        }
      ]
    }
  ]
}

invoke 参数示例(嵌套判别)

typescript
const params = {
  action: {
    type: "click",
    target: {
      method: "css",
      selector: ".submit-button",
    },
  },
}

// 或者

const params2 = {
  action: {
    type: "click",
    target: {
      method: "xpath",
      expression: "//button[@id='submit']",
    },
  },
}

invoke: async ({ args }) => {
  const { parameters } = args
  const action = parameters.action
  if (action.type === "click") {
    const selector =
      action.target.method === "css" ? action.target.selector : action.target.expression
    await clickElement(selector, action.target.method)
  }
  return { success: true }
}

数组元素为 Discriminated Union

数组的每一项可以是不同形态的对象:

typescript
{
  name: "actions",
  type: "array",
  display_name: { en_US: "Actions", zh_Hans: "动作" },
  items: {
    name: "action",
    type: "discriminated_union",
    discriminator: "type",
    any_of: [
      {
        name: "wait",
        type: "object",
        properties: [
          { name: "type", type: "string", constant: "wait", display_name: { en_US: "Wait", zh_Hans: "等待" } },
          { name: "milliseconds", type: "integer", default: 1000 }
        ]
      },
      {
        name: "click",
        type: "object",
        properties: [
          { name: "type", type: "string", constant: "click", display_name: { en_US: "Click", zh_Hans: "点击" } },
          { name: "selector", type: "string", display_name: { en_US: "Selector", zh_Hans: "选择器" } }
        ]
      },
      {
        name: "scroll",
        type: "object",
        properties: [
          { name: "type", type: "string", constant: "scroll", display_name: { en_US: "Scroll", zh_Hans: "滚动" } },
          { name: "direction", type: "string", enum: ["down", "up"], default: "down" }
        ]
      }
    ]
  }
}

invoke 参数示例(数组中的混合类型)

typescript
const params = {
  actions: [
    {
      type: "wait",
      milliseconds: 2000,
    },
    {
      type: "click",
      selector: ".next-button",
    },
    {
      type: "scroll",
      direction: "down",
    },
    {
      type: "wait",
      milliseconds: 1000,
    },
    {
      type: "click",
      selector: ".load-more",
    },
  ],
}

invoke: async ({ args }) => {
  const { parameters } = args
  for (const action of parameters.actions) {
    switch (action.type) {
      case "wait":
        await sleep(action.milliseconds)
        break
      case "click":
        await clickElement(action.selector)
        break
      case "scroll":
        await scrollPage(action.direction)
        break
    }
  }
  return { success: true, actionsExecuted: parameters.actions.length }
}

Section UI 布局

使用 section 在对象顶部显示标题,子属性从上到下依次渲染并缩进:

typescript
{
  name: "location",
  type: "object",
  display_name: { en_US: "Location", zh_Hans: "位置" },
  ui: {
    component: "section"
  },
  properties: [
    { name: "country", type: "string", display_name: { en_US: "Country", zh_Hans: "国家" } },
    { name: "languages", type: "array", items: { name: "l", type: "string" }, ui: { component: "tag-input" } }
  ]
}

invoke 参数示例

typescript
const params = {
  location: {
    country: "US",
    languages: ["English", "Spanish"],
  },
}

invoke: async ({ args }) => {
  const { parameters } = args
  const { country, languages } = parameters.location
  // Section 的数据结构与普通 object 一致,但 UI 渲染也不同
  console.log(`Selected country: ${country}`)
  console.log(`Languages available: ${languages.join(", ")}`)
  return { success: true }
}