Files
paseo/public-docs/plugins/providers.zh-CN.md
Mohamed Boudra bd15e40e94 Serve paseo.sh in Simplified Chinese (#7373)
* feat(website): serve paseo.sh in Simplified Chinese

Every page of the website now has a Simplified Chinese version under
/zh-CN: the interface, docs, blog posts, comparison pages, legal pages,
all changelog releases and the homepage mockup. English URLs are unchanged.

The URL alone decides the language. There is no browser-language redirect
and no stored preference, because either would have to redirect the
unprefixed English URLs that people share and search engines index.
Internal links carry the page's language instead, and hreflang alternates
point Chinese search results at /zh-CN.

Translated headings pin the English anchor with {#id}, so section links
work in both languages. content-translations.test.ts fails when a page
has no translation or a translation's headings drift from the English.
Changelog releases and plugin listings without a translation render
whole in English under a notice, so a release never waits on one.

Plugin manifests and the registry gain optional translations and
OVERVIEW.zh-CN.md fields; older clients and registries ignore them.

* feat(website): replace the native language select with an EN/ZH menu

The header showed an OS-native select whose popup did not match the site.
The trigger now shows the language abbreviated (EN, ZH) and opens a menu in
the app's dropdown style that names each language in itself, with its name
in the page language underneath. Rows are real links to the same page in
that language; arrows, Escape, outside presses and taps behave as a menu
button, and the menu keeps itself on screen when a narrow header pushes the
trigger toward an edge.

Also drops the stray space after the full-width comma in the Chinese
homepage subtitle.

* docs(website): translate main's new docs and 0.12.0 release notes

Main added Keep run workspaces to the schedules docs, rewrote plugin icons
around inline SVG, and promoted 0.12.0 with new entries. Bring the Chinese
pages and changelog in line so no section falls back to English.

* fix(server): leave plugin listing translations to the registry

The daemon rejected manifests whose translations the registry would refuse,
so a plugin main installs could fail to load here. Paseo never reads
translations; the registry validates them at publish. Docs now say which
checks are automated at publish and which are human review.

* docs(plugins): describe the registry's final overview translation discovery

* style(server): format manifest test

* test(app): measure sidebar rows one at a time

After a reload the developer error toast appears while expectVerticalOrder
measures every row at once. Each concurrent boundingBox runs the toast
locator handler, and only one of them resumes, so the poll timed out on
every attempt since #7424. Measuring sequentially keeps one action in flight.

* test(app): return sidebar row measuring to main

#7480 hides the developer toast instead of running a locator handler, so
measuring the rows concurrently no longer races it. Reverts 41ac23016.

* test(app): wait for the compact shell before opening compact Changes

The shell survives the breakpoint, so right after the resize the wide
Explorer could still be visible: the test skipped the Explorer toggle and
the tab then disappeared, or toggled at the wrong time. Wait for the
compact-shell marker, as the composer helpers do since #7424.

* docs(website): translate main's plugin file panel docs

* test(app): edit the code font size after the font family remount

A committed appearance change remounts the settings screen through
AppearanceStyleBoundary, and the new form starts from the stored size.
Filling the size before that remount lost the edit in CI.

* docs(website): translate main's client/ui import docs

Follows #7474: one /client/ui entry, StreamedText instead of useRevealedText,
SettingsGroup dropped from the settings table, and the new
'UI imports in v0.13' migration section.

* test(app): wait for the previous layout's file panel picker to leave

After a resize the old layout's picker animates out while the new one is
already mounted, so the Use button matched two elements and the strict
locator failed. Wait for the shell's layout and a single picker first.
2026-10-21 13:13:03 +07:00

14 KiB
Raw Permalink Blame History

title, description, nav
title description nav
构建 Provider 插件 直接把编程 Agent 接入 Paseo,或适配一个 ACP Agent。 Provider 插件

构建 Provider 插件

如果你之前没有构建过 Paseo 插件,请从插件快速上手开始。

Provider 插件把一个编程 Agent 接入 Paseo,而无需把它加入 Paseo 核心。在 npm 或 Git 仓库中发布插件,用户就可以用 paseo plugin install 安装它。

选择一种实现方式:

Agent 方式
已经实现了 ACP 用 runAcpProvider() 注册它,只添加少量针对厂商的转换器。
提供 TypeScript SDK、JSON-RPC API 或自定义进程协议 直接实现 ProviderRegistration。

示例有:

  • provider-direct:会话、设置、提示词、持久化、子会话,以及一个由 Provider 拥有的时间线渲染器;
  • provider-acp-transformer:一个 ACP 命令模板,带有一个用 Zod 校验的厂商编辑转换器。加载之前,请把 example-acp --stdio 替换为一个已安装的 ACP Agent;
  • inline-thinking:一个只包含渲染器、不实现 Provider 的插件。

注册直接实现的 Provider

添加一个服务端入口:

// index.server.ts
import type { PluginServerContext } from "@getpaseo/plugin/server";
import { createProvider } from "./server/provider";

export default function contribute(server: PluginServerContext) {
  server.registerProvider(createProvider());
  return () => {};
}

createProvider() 返回一个 ProviderRegistration:

import {
  negotiateProviderCapabilities,
  type ProviderRegistration,
} from "@getpaseo/plugin/server/provider";

const supported = ["prompt.message"] as const;

export function createProvider(): ProviderRegistration {
  return {
    id: "my-agent",
    label: "My agent",
    icon: "icon.svg",
    async connect(request) {
      const capabilities = negotiateProviderCapabilities(request.capabilities, supported);
      return createConnection(capabilities);
    },
  };
}

连接有三个操作:

  • send(input) 接收工作后立即返回。结果以事件的形式发布,而不是返回值。
  • onEvent(listener) 发布会话状态和完整的时间线快照。
  • close() 停止所有会话,并释放进程、订阅和待处理的工作。

把原生 SDK、进程和流保留在连接的实现内部。在发布之前,把它的输出转换为 ProviderEvent 对象。

启动 Provider CLI

对于进程传输和 CLI 探测,请使用 @getpaseo/plugin/server 中的 spawnProcess 和 execCommand。两者都能处理 Windows 的 .cmd/.bat 启动器,并为 shell 参数加引号。原生的 Node spawn 和 execFile 在 Windows 上会拒绝这些启动器。

daemon 在 request.launch 中提供解析后的命令、参数前缀和完整的环境变量。请显式传入这些环境变量;这些辅助函数不会应用 daemon 的环境策略。把进程生命周期和协议截止时间保留在你的 Provider 内部。正常关闭时结束 stdin;强制停止时使用 terminateProcess(child)。它在 Windows 上会结束整个进程树,包括命令脚本启动器背后的 CLI。为最后等待流关闭的过程设定上限。

import { spawnProcess, execCommand } from "@getpaseo/plugin/server";

const child = spawnProcess(launch.command, [...launch.args, "serve"], {
  env: launch.env,
  cwd,
  stdio: "pipe",
});
const { stdout } = await execCommand(launch.command, [...launch.args, "--version"], {
  env: launch.env,
  timeout: 5000,
});

返回模型、模式和思考选项

Paseo 在创建会话之前会请求目录。返回 Agent 表单需要的选项:

if (input.type === "catalog") {
  emit({
    type: "catalog",
    requestId: input.requestId,
    catalog: {
      models: [
        { id: "agent-large", label: "Agent Large" },
        { id: "agent-small", label: "Agent Small" },
      ],
      modes: [
        { id: "build", label: "Build" },
        { id: "plan", label: "Plan" },
      ],
      thinkingOptions: [
        { id: "standard", label: "Standard" },
        { id: "extended", label: "Extended" },
      ],
      defaultModel: "agent-large",
      defaultMode: "build",
      defaultThinkingOption: "standard",
    },
  });
}

对于 Agent 不提供的类别,返回空数组。选中的 model、mode 和 thinkingOption 会出现在会话配置中。

目录描述的是会话存在之前可用的选项。会话专属的控件之后通过 session.config 提供。

打开会话

session.open 包含完整的启动配置:工作目录、环境变量、系统提示词、MCP 服务器、工具策略、模型、模式、设置、不透明的 Provider 选项以及持久化偏好。

对于持久化的、作用于主机的插件设置,请使用 server.registerSettings() 返回的句柄。在目录和会话回调中读取它;当 Provider 在内存中保留配置时,订阅它。read() 和 subscribe() 的契约见设置。

用选中的目录值创建原生会话,然后发布它实际生效的状态:

const nativeSession = await sdk.createSession({
  cwd: input.config.cwd,
  model: input.config.model,
  mode: input.config.mode,
  thinking: input.config.thinkingOption,
  systemPrompt: input.config.systemPrompt,
  env: input.config.env,
  mcpServers: input.config.mcpServers,
});

emit({
  type: "session.opened",
  requestId: input.requestId,
  sessionId: input.sessionId,
  capabilities: ["prompt.message"],
  restoration: "core",
  cwd: input.config.cwd,
});
emit({
  type: "session.config",
  sessionId: input.sessionId,
  config: {
    model: nativeSession.model,
    mode: nativeSession.mode,
    thinkingOption: nativeSession.thinking,
    models,
    modes,
    thinkingOptions,
    settings: [],
  },
});
emit({ type: "session.ready", requestId: input.requestId, sessionId: input.sessionId });

session.config 包含已确定的值。请发布原生 Agent 经过规范化后实际选中的值,而不是请求的副本。

完成第一段提示词

从消息类提示词开始。声明 prompt.message,把内容传给原生 Agent,并发布由此产生的生命周期:

const turnId = crypto.randomUUID();

emit({
  type: "timeline.item",
  sessionId,
  item: {
    type: "user_message",
    id: `user:${turnId}`,
    text,
    clientMessageId: prompt.clientMessageId,
  },
});
emit({
  type: "session.prompt_result",
  sessionId,
  clientMessageId: prompt.clientMessageId,
  result: { type: "turn", turnId },
});
emit({ type: "session.turn", sessionId, turnId, state: "started" });

// Publish complete snapshots as native output changes.
emit({
  type: "timeline.item",
  sessionId,
  item: { type: "assistant_message", id: `assistant:${turnId}`, text: completeText },
});

emit({ type: "session.turn", sessionId, turnId, state: "completed" });

对每个 clientMessageId 恰好发布一个 session.prompt_result。把 clientMessageId 复制到用户的时间线条目上,以便 Paseo 替换它乐观显示的消息。每个已开始的轮次都需要一个终止的 completed、failed 或 canceled 事件。

到这一步,Provider 已经可用:用户可以选择模型、打开会话、发送消息并看到回复。

添加会话专属的 Composer 控件

只有当原生 Agent 有用户在打开会话后需要修改的设置时,才添加 session.configure。在 session.config 中以开关或选择控件的形式发布它们:

emit({
  type: "session.config",
  sessionId,
  config: {
    model: "agent-large",
    models: [{ id: "agent-large", label: "Agent Large" }],
    modes: [],
    thinkingOptions: [],
    settings: [
      {
        type: "toggle",
        id: "fast",
        label: "Fast mode",
        value: nativeSession.fast,
      },
      {
        type: "select",
        id: "approval",
        label: "Approvals",
        value: nativeSession.approval,
        options: [
          { label: "Ask", value: "ask" },
          { label: "Auto accept", value: "auto" },
        ],
      },
    ],
  },
});

Paseo 会在 Composer 中渲染这些控件。用户的修改会以 session.configure 的形式到达;应用它,并在 request.completed 之前发布已确定的 session.config。Provider 可以在运行时改变可用的控件,包括在模型变化之后。

settings 是面向用户的控件。providerOptions 是在创建会话时提供的不透明 JSON,用于 Paseo 不会渲染的 Provider 专属配置。Provider 专属的 token 预算应放在这两处之一;它不是一个专门的提示词字段。

添加持久化与重放

当原生会话可以被重新打开时,声明 session.persistence。当 session.open.persistence 不存在时,打开一个新的原生会话;存在时,恢复它所指定的原生会话。在 session.opened 或 session.persistence 中返回一个不透明的持久化值;Paseo 会保存它而不检查其内容。

当 history 为 "replay" 时,请在 session.ready 之前发布原生会话已有的 timeline.item 快照。使用 history: "skip" 可以在不重放旧记录的情况下打开会话。

Paseo 刷新 Agent 的方式是关闭它当前的 Provider 会话,然后用当前的配置和持久化值重新打开。请在 session.open 期间重新读取凭据、环境变量、全局配置和 MCP 服务器。没有单独的重新加载操作。

当 Paseo 可以从持久化值重新打开会话时,使用 restoration: "core"。由 Provider 拥有的子会话使用 restoration: "parent";把它作为另一个带有 parentSessionId 的 session.opened 事件发出,并在恢复父会话时重新创建它。

添加命令和引导

所有用户输入都通过 session.prompt 到达:

  • input.type: "message" 携带文本、图片和附件;
  • input.type: "command" 携带所选的命令及其参数;
  • delivery: "steer" 要求你把输入加入当前正在进行的轮次;
  • delivery: "auto" 允许你开始一个轮次,或者在没有轮次的情况下完成一条命令。

发布 session.commands,向 Composer 添加结构化命令。命令可以开始一个普通轮次,也可以作为副作用完成,并返回一个 completed 的提示词结果。

只声明你实现了的能力。如果不支持引导,请省略 prompt.steer;Paseo 可以改为替换当前轮次。

发布时间线条目

通过 timeline.item 发布完整的快照。更新流式文本、运行中的工具或待办列表时,复用条目的 id。Paseo 会推导出实时增量,并保持正常的时间线。

内置的条目类型涵盖用户和助手消息、推理、工具、待办事项、错误、通知和上下文压缩。当你的 Provider 有这些类型无法表达的展示方式时,使用插件条目:

emit({
  type: "timeline.item",
  sessionId,
  item: {
    type: "plugin",
    id: "review-42",
    pluginId: "my-provider-plugin",
    kind: "review-verdict",
    version: 1,
    data: { verdict: "ship", summary: "All checks passed" },
  },
});

在 index.client.tsx 中单独注册渲染器:

import type { PluginClientContext } from "@getpaseo/plugin/client";
import { z } from "zod";
import { ReviewVerdict } from "./client/review-verdict";

const reviewVerdictSchema = z.object({
  verdict: z.enum(["ship", "hold"]),
  summary: z.string(),
});

export default function contribute(client: PluginClientContext) {
  client.addTimelineRenderer({
    kind: "review-verdict",
    version: 1,
    schema: reviewVerdictSchema,
    Component: ReviewVerdict,
  });
  return () => {};
}

同一个渲染器可以显示由 Provider、客户端时间线转换器或 daemon 时间线追加发出的条目。渲染器并不需要 Provider 实现。

适配 ACP Agent

当 Agent 已经支持 ACP 时,使用 ACP 适配层:

import type { PluginServerContext } from "@getpaseo/plugin/server";
import { runAcpProvider } from "@getpaseo/plugin/server/acp";

export default function contribute(server: PluginServerContext) {
  server.registerProvider(
    runAcpProvider({
      id: "vendor-agent",
      label: "Vendor agent",
      icon: "icon.svg",
      command: ["vendor-agent", "acp"],
    }),
  );
  return () => {};
}

适配层负责 ACP 进程、能力映射、会话生命周期、提示词、权限和时间线转换。不要把这些机制复制到插件中。

只在 ACP 无法描述厂商差异时使用 transformers。用 Zod 校验厂商负载,并让格式错误或无关的值保持不变:

import type { AcpTransformer } from "@getpaseo/plugin/server/acp";
import { z } from "zod";

const editSchema = z.object({
  path: z.string(),
  before: z.string(),
  after: z.string(),
});

export const vendorEdits: AcpTransformer = {
  toolCall(toolCall) {
    if (toolCall.name !== "vendor_file_edit") return toolCall;
    const input = editSchema.safeParse(toolCall.input);
    if (!input.success) return toolCall;
    return {
      ...toolCall,
      kind: "edit",
      input: {
        filePath: input.data.path,
        oldString: input.data.before,
        newString: input.data.after,
      },
    };
  },
};

测试与发布

用真实的 Agent 测试 Provider,而不只是模拟的数据帧:

  1. 以目录来源安装插件;
  2. 确认它的 SVG、模型、模式和设置都会显示;
  3. 创建会话并完成一段真实的提示词;
  4. 在支持的情况下,试用权限、引导、取消、持久化和重放;
  5. 在桌面和移动端宽度的布局中渲染每一种自定义时间线条目;
  6. 在会话处于活动状态时重新加载和移除插件,并确认会话随之终止。

按照发布插件通过 npm 或 Git 分发 Provider。把 Agent 兼容性和版本发布保留在插件项目中。

确切的运行时和 SVG 规则见插件参考。