開発ガイド
完全な最小タスクプラグインを作成し、リクエストの構築、レスポンスの送信、ポーリングステータスを検証し、フィクスチャとサンドボックスを使用してデバッグします。
開発前に知っておくべきこと
プラグインは単一ファイルで同期的なECMAScriptモジュールです。import、require、async、await、fetch、ファイルシステム、または環境変数を使用することはできません。ネットワークリクエスト、認証の解析、タスクの保存、および決済はホストによって行われます。
現在のプラグイン API v1 はまだ進化中です。型宣言と JSON Schemaを契約の根拠とし、対象のホストバージョンで検証されます。
最小限の完全な例
以下のコードを plugin.js として保存してください。これは demo-task という名前のタスクプラグインを示しています。/jobs に送信し、返された id を読み取り、/jobs/{id} を介してステータスを照会します。
サンプルアップストリーム
api.example.com とここでのリクエスト、レスポンス形式はデモンストレーション用であり、直接呼び出し可能な実際のサービスではありません。実際のベンダーに接続する際は、アップストリームアドレス、認証、プロトコル処理を置き換え、チャネルと価格を設定する必要があります。
export const meta = {
apiVersion: 1,
key: 'demo-task',
name: 'Demo Task',
version: '1.0.0',
author: { name: 'Example Author' },
description: {
en: 'A minimal asynchronous task adapter',
zh: '最小异步任务适配示例',
},
models: ['demo-model'],
fetchMode: 'per_task',
baseUrl: 'https://api.example.com',
auth: 'api_key',
usageSchema: {
requests: {
type: 'number',
unit: 'count',
description: { en: 'Number of generation requests', zh: '生成请求数量' },
},
},
usageExamples: [{ label: 'One request', facts: { requests: 1 } }],
};
export function buildSubmitRequest(ctx) {
const input = ctx.requestBody || {};
if (typeof input.prompt !== 'string' || !input.prompt.trim()) {
throw new Error('prompt must be a non-empty string');
}
return {
url: ctx.baseUrl.replace(/\/$/, '') + '/jobs',
method: 'POST',
headers: {
Authorization: ctx.authHeader,
'Content-Type': 'application/json',
},
body: {
model: ctx.upstreamModel || ctx.model,
prompt: input.prompt,
},
};
}
export function parseSubmitResponse(ctx, response) {
if (response.statusCode < 200 || response.statusCode >= 300) {
throw new Error('The upstream service rejected the task');
}
const body = response.body;
if (!body || typeof body.id !== 'string' || !body.id) {
throw new Error('The upstream response has no task id');
}
return { taskId: body.id, taskData: body };
}
export function buildQueryRequest(ctx) {
return {
url:
ctx.baseUrl.replace(/\/$/, '') +
'/jobs/' +
encodeURIComponent(ctx.taskId),
method: 'GET',
headers: { Authorization: ctx.authHeader },
};
}
export function parseTaskResult(ctx, body, response) {
const statuses = {
queued: 'QUEUED',
running: 'IN_PROGRESS',
succeeded: 'SUCCESS',
failed: 'FAILURE',
};
const status =
body && Object.hasOwn(statuses, body.status)
? statuses[body.status]
: 'UNKNOWN';
return { status };
}
export function extractUsage(ctx) {
return { requests: 1 };
}この例は、汎用インターフェース POST /v1/tasks/demo-task を介して送信され、JSONボディには model: "demo-model" と prompt が含まれます。ネイティブルート、Video/Responses プロトコル、または成果物フックは宣言されていません。これらの機能は、実際のプラグインで必要に応じて実装されます。
ライフサイクルとデータ
- ホストは認証を行い、チャネルを選択し、標準化されたリクエストを
buildSubmitRequestに渡します。 - ホストは記述子のURLを検証し、HTTPリクエストを送信し、
parseSubmitResponseを呼び出します。 - プラグインはアップストリームのタスクIDと永続化可能なデータを返します。ホストは公開タスクIDを生成し、タスクを保存します。
- ホストは、タスクが最終状態になるか、失敗してクリーンアップされるまで、
buildQueryRequestとparseTaskResultを定期的に呼び出します。 - ホストは使用量データを読み取り、保存された課金設定に従って決済を完了します。
TaskQueryContext.taskId はアップストリームIDであり、publicTaskId は New API の公開IDです。ポーリングコンテキストには requestBody がありません。ポーリング間で保持する必要があるデータは state を使用し、モジュールのグローバル変数に依存してはいけません。
Task.Data は最新のアップストリームスナップショットを保存します。各ポーリングの成功解析で更新されます。不明なステータスは UNKNOWN を返し、IN_PROGRESS をデフォルトにしないでください。そうしないと、異常なタスクがリソースを継続的に占有する可能性があります。
コンパイルとフィクスチャテスト
プラグインCLIを含むNew API実行可能ファイルを使用してソースコードを検証します。
new-api plugin lint plugin.js
new-api plugin test plugin.js --fixture golden.json以下の内容を golden.json として保存し、モデルマッピング、無効な入力、および不明なステータスをカバーします。
{
"cases": [
{
"name": "mapped model is sent upstream",
"hook": "buildSubmitRequest",
"args": [
{
"model": "public-alias",
"upstreamModel": "demo-model",
"baseUrl": "https://api.example.com",
"authHeader": "Bearer example-key",
"requestBody": { "prompt": "A quiet garden" }
}
],
"expected": {
"url": "https://api.example.com/jobs",
"method": "POST",
"headers": {
"Authorization": "Bearer example-key",
"Content-Type": "application/json"
},
"body": { "model": "demo-model", "prompt": "A quiet garden" }
}
},
{
"name": "reject an empty prompt",
"hook": "buildSubmitRequest",
"args": [{ "requestBody": { "prompt": " " } }],
"expectedError": "prompt must be a non-empty string"
},
{
"name": "preserve the upstream task id",
"hook": "parseSubmitResponse",
"args": [{}, { "statusCode": 200, "body": { "id": "vendor-123" } }],
"expected": { "taskId": "vendor-123", "taskData": { "id": "vendor-123" } }
},
{
"name": "unknown status is not in progress",
"hook": "parseTaskResult",
"args": [
{},
{ "status": "unexpected" },
{ "status": 200, "headers": {} }
],
"expected": { "status": "UNKNOWN" }
}
]
}フィクスチャは決定論的な同期フックのみを実行し、アップストリームへのリクエストは送信しません。正式なプラグインでは、送信エラー、成功と失敗の最終状態、ポーリングコンテキスト、バッチ処理、使用量のゼロ値、プロトコルレンダリング、および成果物の読み取りもカバーする必要があります。
管理ページでのデバッグ
Rootは、インストール済みのプラグイン詳細の「サンドボックス」(Sandbox)でフックを選択し、パラメータのJSON配列を入力できます。例えば、buildSubmitRequest をデバッグする際には、以下を入力します。
[
{
"model": "demo-model",
"baseUrl": "https://api.example.com",
"authHeader": "Bearer example-key",
"requestBody": { "prompt": "A quiet garden" }
}
]サンドボックス呼び出しは、選択された同期関数のみを実行し、それが返すHTTPリクエスト記述子は実行しません。したがって、成功した出力はフックの動作が入力と一致することを示すだけであり、アップストリームリクエストが必ず成功することを証明するものではありません。
拡張機能
- バッチクエリ:
fetchMode: "batch"を宣言し、バッチ構築および解析フックを実装します。各結果には対応するタスクIDを含める必要があります。 - ネイティブインターフェース:
meta.routesとnativeデコーダー、レンダラーを介してベンダー固有のエントリポイントを実装します。 - ホストプロトコル:
meta.protocolsを宣言し、Video または Responses のプロトコルフックを実装します。 - 成果物:
listArtifactsとbuildContentRequestをペアで実装します。 - 即時完了またはアップストリームSSE:機能宣言に従って、即時最終状態、SSEスナップショット、または増分イベントフックを実装します。
詳細な制約については、API v1 リファレンスを参照してください。公開準備の際には、公開仕様に従ってバージョン、ログを追加し、インデックスを生成してください。
このガイドはいかがですか?
最終更新