npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@kanyun-ai-infra/pilot

v0.1.1

Published

Typed TypeScript client for the Pilot REST API

Readme

@kanyun-ai-infra/pilot

类型完备的 Pilot REST API TypeScript 客户端。 在 Node 20+ 与现代浏览器(原生 fetch + SSE)里都能跑。

适合谁用:

  • Node 脚本 / 后端服务 — 把 Pilot 嵌进自己的工具链或 Webhook handler。
  • CI 流水线 — 触发 Pilot 跑 Agent,再把结果回写到 PR 评论里。
  • 业务前端 — 在 Web App 里直接接消息流(推荐用 @ai-sdk/react,本 SDK 暴露的 SSE 也兼容)。

只想在终端里跑命令?请用 @kanyun-ai-infra/pilot-cli


安装

npm install @kanyun-ai-infra/pilot
# pnpm
pnpm add @kanyun-ai-infra/pilot
# yarn
yarn add @kanyun-ai-infra/pilot

无额外 peer dependency。Zod 已作为运行时依赖一并下发。


3 步上手

1. 拿到 Token

三种方式任选其一(推荐 ② / ③):

| 方式 | 适用场景 | 操作 | |---|---|---| | ① CLI 浏览器登录 | 本地开发 | pilot login --base-url https://pilot.example.com,Token 写到 ~/.pilot/config.json | | ② Web UI 签发长期 Token | CI / 自动化 | 打开 /settings/tokens/new,选 CI 自动化 模板(默认 90 天),复制一次性 plaintext | | ③ 已有 Token 注入环境变量 | 容器 / Serverless | export PILOT_TOKEN=… PILOT_BASE_URL=… |

2. 创建客户端

import { PilotClient } from "@kanyun-ai-infra/pilot";

const pilot = new PilotClient({
  baseUrl: "https://pilot.example.com",  // 或省略,由 PILOT_BASE_URL 提供
  token:   process.env.PILOT_TOKEN,      // 或省略,由 PILOT_TOKEN 提供
});

构造函数 不发起任何网络请求 —— 第一次 IO 发生在你调具体方法时。

3. 触发一次 Run 并看输出

// 一行同时建好 Agent + 它的第一个 Run
const { agentId, runId } = await pilot.agents.create({
  projectId: "00000000-0000-0000-0000-000000000000",
  prompt: "Add a /health endpoint that returns { status: 'ok' }",
});

// 流式消费 SSE,每个 chunk 是 contracts 里的标准 SDKMessage
for await (const msg of pilot.runs.stream(agentId, runId)) {
  if (msg.type === "text-delta") process.stdout.write(msg.text ?? "");
  if (msg.type === "done") break;
}

// 阻塞直到 Run 进入终态(10 分钟超时),返回 PR 链接 / 错误信息等
const result = await pilot.runs.wait(agentId, runId, { timeoutMs: 600_000 });
console.log(result.status, result.run?.pullRequestUrl);

完整端到端示例(含错误处理):见 docs/sdk-quickstart.md


配置

new PilotClient(config: PilotClientConfig) 接受:

| 字段 | 类型 | 说明 | |---|---|---| | baseUrl | string | Pilot Web 地址。省略时回退到 process.env.PILOT_BASE_URL。必须是 http(s)。 | | token | string | 平台 Token (JWT)。省略时回退到 process.env.PILOT_TOKEN。两者都缺会抛 UnauthenticatedError。 | | fetch | typeof fetch | 注入自定义 fetch,多用于测试 / 老 Runtime(注 undici)。 | | signal | AbortSignal | 默认 AbortSignal,会和单次请求的 signal 合并。React Query / 取消传播场景常用。 | | defaultHeaders | Record<string, string> | 每个请求的额外 header。不能覆盖 Authorization(SDK 强制注入 Bearer)。 | | userAgent | string | 服务端日志归因用。默认 @kanyun-ai-infra/pilot/<version>。 | | onVersionWarning | (message) => void | 当服务端响应缺 x-pilot-api-version header 时触发一次警告。默认 console.warn,传 noop 可静默。 |


API 速查

所有方法都是 PromiseAsyncIterable,签名完全 typed。

client.agents

| 方法 | 说明 | |---|---| | agents.list(query?, options?) | GET /api/agents 列出当前 Token 可见的 Agent。 | | agents.get(agentId, options?) | GET /api/agents/:id 拉取详情(嵌套 runs)。 | | agents.create(request, options?) | POST /api/agents 一次性建 Agent + 第一个 Run。最常用入口。 | | agents.update(agentId, request, options?) | PATCH /api/agents/:id 改名 / 关闭。 | | agents.delete(agentId, options?) | DELETE /api/agents/:id 软删。 | | agents.merge(agentId, request, options?) | POST /api/agents/:id/merge 合并 Agent 最近一个 Run 产出的 PR/MR。需要 runs:merge scope,高危。 |

client.runs

| 方法 | 说明 | |---|---| | runs.create(request, options?) | POST /api/runs 创建 Run(可绑定到已有 Agent)。 | | runs.abort(agentId, runId, request?, options?) | POST /api/agents/:agentId/runs/:runId/abort 让服务端真停止。 | | runs.stream(agentId, runId, options?) | AsyncIterable<SDKMessage>,自动重连。详见下文【流式细节】。 | | runs.wait(agentId, runId, options?) | 阻塞直到终态。先走流(快路径),失败回退轮询。返回 { status, run, terminalMessage }。 |

client.projects

| 方法 | 说明 | |---|---| | projects.list(options?) | GET /api/projects 列出可见项目。 | | projects.create(request, options?) | POST /api/projects 创建通用项目(kind: "general")。 | | projects.get(projectId, options?) | 从 list 里挑出单个项目(服务端暂无单查路由)。 | | projects.getSettings(projectId, options?) | 读取自动化配置(Webhook / WeCom / Jira)。 | | projects.updateSettings(projectId, request, options?) | 部分更新自动化配置。 |


Cookbook

Non-coding tasks(无仓库任务)

const { projectId } = await pilot.projects.create({
  name: "Weekly research",
  kind: "general",
  description: "Analyst workspace",
});

const { agentId, runId } = await pilot.agents.create({
  projectId,
  prompt: "整理本周 AI Infra 新闻,生成一份 markdown 周报",
});

const result = await pilot.runs.wait(agentId, runId, { timeoutMs: 600_000 });
console.log(result.status);

general 项目不绑定 Git 仓库,交付通过 artifacts 返回;coding 项目继续走 Diff + PR/MR 闭环。

触发后等结果,CI 一键脚本

import { PilotClient } from "@kanyun-ai-infra/pilot";

const pilot = new PilotClient(); // 读取 PILOT_BASE_URL / PILOT_TOKEN

const { agentId, runId } = await pilot.agents.create({
  projectId: process.env.PROJECT_ID!,
  prompt: process.env.PROMPT!,
});

const result = await pilot.runs.wait(agentId, runId, { timeoutMs: 30 * 60_000 });

if (result.status === "completed" || result.status === "finalized") {
  console.log(`✅ Done. PR: ${result.run?.pullRequestUrl ?? "(none)"}`);
  process.exit(0);
} else if (result.status === "failed") {
  console.error(`❌ Failed: ${result.run?.errorMessage ?? "unknown"}`);
  process.exit(1);
} else {
  console.warn(`⚠️  Needs human attention: ${result.status}`);
  process.exit(2);
}

流式输出 + 用户主动取消

const ac = new AbortController();
process.on("SIGINT", () => ac.abort());

try {
  for await (const msg of pilot.runs.stream(agentId, runId, { signal: ac.signal })) {
    switch (msg.type) {
      case "text-delta":   process.stdout.write(msg.text ?? ""); break;
      case "tool-use":     console.log(`\n🔧 ${msg.tool?.name}`); break;
      case "tool-result":  console.log(`✅ ${msg.tool?.name}`); break;
      case "tool-error":   console.warn(`⚠️  ${msg.tool?.name}: ${msg.tool?.error}`); break;
      case "error":        throw new Error(msg.error?.message ?? "stream error");
      case "done":         return;
    }
  }
} catch (err) {
  if ((err as Error).name === "AbortError") {
    console.warn("(detached — run still executing on server)");
    // 真要停服务端,调一次 abort:
    await pilot.runs.abort(agentId, runId, { reason: "user-cancelled" });
  } else {
    throw err;
  }
}

根据 LDAP 列出我能访问的项目

const { items } = await pilot.projects.list();
for (const p of items) {
  console.log(`${p.id}  ${p.name}`);
}

用断点续传跨进程恢复 SSE

let lastCursor = await readCursorFromDisk(); // 业务自己持久化

for await (const msg of pilot.runs.stream(agentId, runId, {
  lastCursor,
  onCursor: (c) => writeCursorToDisk(c),
})) {
  // 处理 msg…
}

错误处理

每个请求都会抛 typed error。用 instanceof 分支即可,code 字段直通服务端 JSON code

| 类 | HTTP | 何时出现 / 怎么处理 | |---|---|---| | PilotApiError | 任意 | 基类,含 .status .code .requestId .message。 | | UnauthenticatedError | 401 | Token 缺 / 过期 / 已吊销。引导用户重新 pilot login 或换 Token。 | | ForbiddenError | 403 | 常见 code:INSUFFICIENT_SCOPE(Token 模板不够)/ PROJECT_NOT_IN_TOKEN_SCOPE(项目不在白名单)。 | | NotFoundError | 404 | 资源不存在或当前 Token 不可见。 | | RateLimitError | 429 | .retryAfterSeconds 来自 Retry-After header。指数退避后重试。 | | ServerError | 5xx | 服务端临时抖动。带退避重试通常能恢复。 | | VersionMismatchError | — | 服务端 API 版本与 SDK 不兼容。.clientVersion / .serverVersion升级 SDK 解决。 |

import { PilotApiError, ForbiddenError, RateLimitError } from "@kanyun-ai-infra/pilot";

try {
  await pilot.agents.create({ projectId, prompt });
} catch (err) {
  if (err instanceof ForbiddenError && err.code === "INSUFFICIENT_SCOPE") {
    // 引导用户去 /settings/tokens/new 重新签发 Token
  } else if (err instanceof RateLimitError) {
    await sleep((err.retryAfterSeconds ?? 5) * 1000);
    // …重试
  } else if (err instanceof PilotApiError) {
    console.error(`Pilot ${err.code} (request ${err.requestId}): ${err.message}`);
  } else {
    throw err;
  }
}

需要自己包一层 fetch?工厂函数 pilotErrorFromResponse 也对外导出。


流式细节

runs.stream(agentId, runId, options?) 返回 AsyncIterable<SDKMessage>SDKMessage@pilot/contracts re-export,常见 type

| type | 含义 | |---|---| | text-delta | 模型文本增量。 | | reasoning | 思维链 / 推理文本。 | | tool-use | 工具调用,含 { tool: { name, callId, input } }。 | | tool-result / tool-error | 工具执行结果 / 失败。 | | step-start / step-end | 步骤边界,通常忽略。 | | finalize-progress | 终结化阶段进度。 | | done | 流终止 chunk。不一定包含最终 RunStatus,要拿请用 runs.wait() / agents.get()。 | | error | 流错误终止,含 error.message。 |

自动重连 — SDK 用 Last-Event-ID 透明重连断开的 SSE,默认最多 6 次(1s → 2s → 4s → 8s → 15s → 30s)。重连用尽后:

  1. 上一次失败原因 以 throw 形式抛给 for await(一般是 PilotApiError 形态);或
  2. 服务端干净 EOF 但没下发终止 chunk —— 迭代器静默结束,不会合成 error SDKMessage。

两条结论:

  • 在意错误的话,把 for await 包进 try / catch
  • 看到 done 不能等价于"Run 跑完了"。要确认终态请调 runs.wait() —— 它会在 SSE 拿不到时自动回退到轮询 Agent 详情。

可调参数:

pilot.runs.stream(agentId, runId, {
  signal: ac.signal,                 // 取消本地流(不会停服务端)
  lastCursor: 1234,                  // 跨进程断点续传
  maxReconnectAttempts: 6,           // SSE 重连预算
  maxStreamReadyAttempts: 90,        // 等 Agent 启动的轮询次数(默认 90s 窗口)
  streamReadyPollIntervalMs: 1000,   // 等待间隔
  onCursor: (c) => persist(c),       // 每个 id: 行触发一次
  onReconnect: ({ attempt, delay }) => log(`重连 ${attempt} 次,等 ${delay}ms`),
});

stream vs abortstream(...) 接受 AbortSignal 只是 断本地读 — 服务端 Run 继续。要让服务端真停止请调 runs.abort(agentId, runId)。CLI pilot run stream 的 Ctrl-C 行为也是同样语义。


API 版本握手

每个响应都带 x-pilot-api-version: v1 header。SDK 拒绝消费 major 不兼容的服务端响应,会抛 VersionMismatchError。如果服务端完全没下发这个 header(老部署或代理剥掉了),SDK 在每个 client 实例上只警告一次(通过 config.onVersionWarning 透出,默认 console.warn),后续静默以避免日志噪声。详见 spec §流式契约版本化。


类型导出

主入口 @kanyun-ai-infra/pilot 重导出了所有需要的类型,不要直接 import @pilot/contracts

import type {
  // Agents / Runs
  SdkCreateAgentRequest, SdkCreateAgentResponse,
  SdkCreateRunRequest,   SdkCreateRunResponse,
  SdkAbortRunRequest,    SdkRunSummary, SdkAgentSummary,
  SdkPatchAgentRequest,  SdkMergeAgentRequest, SdkMergeAgentResponse,
  // Projects
  SdkProjectSummary, SdkProjectSettings, UpdateProjectSettingsRequest,
  ListProjectsResponse, ListAgentsQuery, ListAgentsResponse,
  // Stream
  SDKMessage, SDKMessageType,
  // Primitives
  RunStatus, TriggerSource, ProviderType,
  // Auth
  PlatformScope, ScopeTemplate, PlatformTokenPayload,
  // MCP / Skill enable lists
  EnabledMcpEntry, EnabledSkillEntry,
} from "@kanyun-ai-infra/pilot";

相关文档

以下文档放在 Pilot 内部 GitHub 仓库(kanyun-inc/pilot),需公司员工权限访问。

License

公司内部使用,详见仓库根 LICENSE