撰寫 TypeScript/JavaScript Particle
Particle 是一個單一檔案程式,它向 Particle 執行階段公開一個或多個工具(具有 JSON-Schema 輸入的命名操作)。此技能告訴您產生一個可建置和執行所需的一切知識。
1. 檔案佈局
在專案根目錄下的一個原始檔,名稱必須為:
Particlefile.ts— 首選(完整的型別檢查)Particlefile.js— 可行,但無型別
不需要其他檔案。npm 相依性使用標準的 import 語句內聯宣告;建置過程會解析它們。無需管理 package.json、tsconfig.json 或 node_modules。
2. 預設匯出 — 完整的 API
檔案的預設匯出是 Particle 宣告。結構如下:
export default {
name: "kebab-case-name", // 必填,kebab-case
description: "...", // 必填,一行
version: "0.1.0", // 必填,semver
capabilities: {
// 對外 HTTP 允許清單。未列於此的主機將在網路邊界被拒絕。
// 如果 Particle 不發送任何對外請求,則完全省略 `http`。
http: { allowedHosts: ["api.example.com"] },
// 主機目錄存取,預設關閉。請參閱下面的「檔案系統」。
// filesystem: { mounts: { ... }, temp: { ... } },
},
// 可選。按名稱宣告;使用者在設定時選擇方法。
credentials: {
example: {
hosts: ["api.example.com"], // 將替換綁定到這些主機
required: true,
methods: {
// 請參閱下面的「憑證方法」
},
},
},
// 必填。工具名稱 → { description, inputSchema, handler } 的映射。
tools: {
do_thing: {
description: "此工具的功能 — 一行,為 LLM 編寫。",
inputSchema: {
type: "object",
properties: {
name: { type: "string", description: "..." },
},
required: ["name"],
},
handler: async ({ name }: { name: string }) => {
return { result: `hello ${name}` };
},
},
},
// 可選。回傳存活/就緒狀態。由 `particle ping` 呼叫。
ping: async () => ({ status: "ok" as const, message: "alive" }),
};
工具
description:為 LLM 呼叫者編寫 — 簡短、以行動為導向。inputSchema:JSON Schema Draft 2020-12,以物件為根。主機在呼叫您的處理器之前會根據它驗證引數,因此您可以信任處理器內部的輸入形狀。使用required標記必填欄位;為可選欄位提供default。handler:同步或非同步。回傳任何 JSON 可序列化的值。拋出例外以向呼叫者顯示錯誤 — 他們會看到拋出的訊息。
Ping
可選但建議使用。回傳形狀:
{ status: "ok" | "degraded" | "unhealthy",
message?: string,
details?: string }
3. 提供給處理器的執行階段 API
這些是您可以匯入的唯一非標準庫項目。每個都是執行階段提供的主機模組 — 它們以純型別 npm 套件形式提供,實作在執行時注入。
對外 HTTP
使用全域 fetch()。它是標準的 Web Fetch API,透過主機路由,以便應用允許清單和憑證替換。
const res = await fetch("https://api.example.com/things", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ ... }),
});
if (!res.ok) throw new Error(`上游 ${res.status}`);
const data = await res.json();
不在 capabilities.http.allowedHosts 中的主機將被拒絕。單純的 fetch 不會應用憑證 — 請改用 credentials.fetcher。
檔案系統
預設關閉 — 處理器只能看到自己的套件。宣告 capabilities.filesystem 以取得主機目錄存取權限,然後使用一般的 node:fs API 進行讀寫(執行階段會透過 wasi:filesystem 路由它們)。無需匯入功能模組。
capabilities: {
filesystem: {
// *使用者*映射到真實路徑的主機目錄 — 使用 `particle mount <particle> <name> <host-path>` 持久化,
// 或每次執行時使用 `--mount <name>=<host-path>`。
mounts: {
data: {
description: "報表寫入位置", // 顯示在安裝提示中
path: "/mnt/data", // 沙箱內的絕對路徑
access: "readwrite", // "readonly" | "readwrite"
required: true, // 拒絕執行直到映射完成(預設為 false)
},
},
// 主機每次執行時新建立的暫存空間,並在退出時清除。始終為讀寫;使用者從不映射這些。
temp: {
work: { description: "暫存空間", path: "/tmp/work", maxSize: "10MB" },
},
},
},
import { readFile, writeFile } from "node:fs/promises";
const input = await readFile("/mnt/data/in.json", "utf8");
await writeFile("/mnt/data/out.json", JSON.stringify(result));
- 處理器直接開啟宣告的
path;掛載名稱永遠不會出現在沙箱內部。 - 寫入
readonly掛載,或在宣告的掛載之外的任何路徑進行讀寫,都會拋出例外。 - 使用者從未映射的
required掛載會在執行時失敗,而不是在建置/安裝時。 maxSize是位元組計數,可選KB/MB/GB後綴("10MB");超過此限制的寫入會失敗。
憑證
import { credentials } from "@partite-ai/particle-credentials";
// fetcher(name) 回傳一個 fetch 形狀的函式,在請求離開沙箱之前自動套用
// 主機配置位置(標頭、查詢參數等)的憑證。
const fetcher = await credentials.fetcher("example");
const res = await fetcher("https://api.example.com/me");
// 探索使用者設定了哪個方法("oauth", "pat", ...)。
const method = credentials.getConfiguredMethod("example"); // string | null
// 僅適用於宣告為 type: "raw" 的憑證。
const value = await credentials.getRaw("some-raw-cred");
credentials.fetcher 適用於 basic、oauth2 和 apikey 憑證。對於 signing-key,使用 signing.sign。對於 raw,使用 getRaw。
鍵/值儲存
範圍限定為每個 Particle。僅限字串 — 如果需要,對二進位資料進行 base64 編碼。在 capabilities.kv 下宣告它 — 沒有宣告,每次呼叫都會拋出 not-declared 錯誤(無需使用者核准,只需宣告):
capabilities: { kv: { enabled: true } },
import { kv } from "@partite-ai/particle-kv";
await kv.set("cursor", "abc123");
const v = await kv.get("cursor"); // string | null
await kv.delete("cursor");
const keys = await kv.list("prefix:"); // string[]
OAuth 重新整理
僅在您有 oauth2 憑證時使用。fetcher 會自動重新整理已過期的權杖;僅在上游拒絕您認為仍然有效的權杖時才呼叫此函式。
import { oauth } from "@partite-ai/particle-oauth";
await oauth.refresh("example");
簽署
僅適用於 signing-key 憑證。金鑰材料永遠不會進入 JavaScript。
import { signing } from "@partite-ai/particle-signing";
const sig = await signing.sign("webhook-key", new TextEncoder().encode(payload));
const ok = await signing.verify("webhook-key", data, sig);
4. 憑證方法
每個憑證都有一個 methods 映射。使用者在設定時選擇一個。一個 Particle 通常為同一個提供者提供多種方法。
oauth2
oauth: {
type: "oauth2",
description: "透過 OAuth 登入",
flows: ["authorization-code", "device-code"], // 從這三個中任選
scopes: ["read", "write"],
authorizationUrl: "https://provider/oauth/authorize",
tokenUrl: "https://provider/oauth/token",
deviceAuthUrl: "https://provider/oauth/device/code", // 對於 "device-code" 為必填
},
有效的流程字串:"authorization-code"、"authorization-code-pkce"、"device-code"。
apikey
pat: {
type: "apikey",
description: "使用個人存取權杖",
location: { kind: "auth-scheme", scheme: "Bearer" },
},
location.kind 是以下之一:
"header"+name:<name>: <key>"auth-scheme"+scheme:Authorization: <scheme> <key>(例如Bearer)"query-param"+name:附加為?<name>=<key>
完全省略 location 以讓匯入者提示使用者設定。
basic
basic_auth: { type: "basic", description: "使用者名稱 + 密碼" },
替換為 Authorization: Basic <base64>。
signing-key
webhook: { type: "signing-key", description: "...", algorithm: "hmac-sha256" },
有效值:"hmac-sha256"、"hmac-sha512"。透過 signing.sign 使用。
raw
api_key_blob: { type: "raw", description: "..." },
透過 credentials.getRaw(name) 使用。僅在您真正需要原始位元組時選擇此項 — 其他類型更安全,因為該值永遠不會進入您的程式碼。
5. npm 相依性
直接匯入任何純 ESM npm 套件:
import { z } from "zod";
import * as YAML from "yaml";
建置過程會進行匯入掃描並自動解析它們 — 無需 package.json。限制:
- 套件必須是純 JS/TS(無原生綁定,無僅限 Node 的 API)。
- 與瀏覽器相容的建置效果最佳。
- 避免
fs、child_process等 — 它們在執行階段不存在。
6. 關鍵規則
-
無模組範圍的主機呼叫。在檔案頂層呼叫
fetch、kv.get、credentials.fetcher等將在建置時失敗(內省階段會在陷阱儲存下執行您的模組)。請在handler或ping內部進行所有主機呼叫。// ✗ 會導致建置失敗 const fetcher = await credentials.fetcher("github"); export default { tools: { ... } }; // ✓ 在處理器內部 export default { tools: { go: { handler: async (args) => { const fetcher = await credentials.fetcher("github"); // ... }}, }, }; -
宣告您請求的每個主機。對
capabilities.http.allowedHosts中缺少的主機的請求將被拒絕。錯誤會在處理器中以拋出的例外形式呈現。 -
credentials.<name>.hosts必須是 HTTP 允許清單的子集。建置過程會拒絕綁定到 Particle 無法到達的主機的憑證。 -
name必須是 kebab-case(小寫,連字號)。註冊表鍵是(name, version)。 -
version必須是有效的 semver。0.1.0、1.2.3-rc.1等。 -
工具處理器必須回傳 JSON 可序列化的值。回傳
undefined沒問題(會變成null)。回傳類別實例、Date、BigInt或Map將導致序列化失敗。
7. 工作流程
撰寫完 Particlefile.ts 後:
particle build # 型別檢查、打包、內省、註冊
particle ping <name> # 驗證 ping(如果已定義)
particle run <name> # 列出工具
particle run <name> <tool> --help # 顯示工具的標誌
particle run <name> <tool> --foo=bar # 呼叫它
particle build 會在第一次引導使用者互動式地完成憑證設定。如果建置失敗,錯誤會指明階段(import-scan、typecheck、bundle、manifest-extract)和具體問題。
如果 Particle 宣告了檔案系統掛載,請在執行前映射它們(或在 particle run 上傳遞 --mount name=path 進行一次性執行):
particle mount <name> <mount-name> <host-path> # 儲存持久映射
particle mount <name> # 列出掛載 + 當前映射
若要將 Particle 公開為獨立的可執行檔(便於 Claude Code 技能和 shell 使用),particle link <name> ./<name> 會寫入一個啟動器,將其引數轉發給 particle run <name>。
8. 工作範例
一個公開 GitHub 儲存庫查詢工具的最小 Particle:
import { credentials } from "@partite-ai/particle-credentials";
export default {
name: "github-lookup",
description: "查詢 GitHub 儲存庫元資料。",
version: "0.1.0",
capabilities: { http: { allowedHosts: ["api.github.com"] } },
credentials: {
github: {
hosts: ["api.github.com"],
required: true,
methods: {
pat: {
type: "apikey",
description: "GitHub 個人存取權杖",
location: { kind: "auth-scheme", scheme: "Bearer" },
},
},
},
},
tools: {
get_repo: {
description: "擷取 GitHub 儲存庫的元資料。",
inputSchema: {
type: "object",
properties: {
owner: { type: "string", description: "擁有者登入名稱。" },
repo: { type: "string", description: "儲存庫名稱。" },
},
required: ["owner", "repo"],
},
handler: async ({ owner, repo }: { owner: string; repo: string }) => {
const fetcher = await credentials.fetcher("github");
const res = await fetcher(`https://api.github.com/repos/${owner}/${repo}`, {
headers: { Accept: "application/vnd.github+json" },
});
if (!res.ok) throw new Error(`GitHub ${res.status}: ${await res.text()}`);
const r = (await res.json()) as Record<string, unknown>;
return {
full_name: r.full_name,
stars: r.stargazers_count,
url: r.html_url,
};
},
},
},
};
9. 快速參考
| 需求 | 匯入 / 呼叫 |
|---|---|
| HTTP 請求 | await fetch(url, init) |
| 已驗證的 HTTP | const f = await credentials.fetcher(name); await f(url, init) |
| 讀寫已掛載的目錄 | 在宣告的 path 上使用 node:fs/promises(需要 capabilities.filesystem) |
| 每個 Particle 的狀態 | kv.get / set / delete / list |
| 強制 OAuth 重新整理 | oauth.refresh(name) |
| HMAC 簽署 / 驗證 | signing.sign(name, data) / signing.verify(name, data, sig) |
| 探索在設定時選擇的方法 | credentials.getConfiguredMethod(name) |
| 原始憑證位元組 | credentials.getRaw(name) |
匯入始終來自 @partite-ai/particle-<area> 套件 — 請勿發明其他名稱。
來源與授權
改編自 ...
[已截斷以進行翻譯]