# Copilot

> client.copilot は、Copilot アシスタントのスレッドとストリーミングのターンを操作します。動作するのは Nodaro アプリの中だけで、ログイン中のユーザー自身のセッションが必要です。

Source: https://nodaro.ai/ja/docs/developers/sdk/copilot

**`client.copilot`** は、ワークフロー用の Nodaro のアシスタントである Copilot を操作します。**スレッド**は、ワークフロー上で開かれる会話で、**ターン**は、1 つのメッセージと、それに答えるためにアシスタントが行うことすべてです。これらのメソッドは `/v1/copilot/*` を呼び出します。この機能については、[ワークフロー Copilot](https://nodaro.ai/docs/get-started/workflow-copilot) を参照してください。

**Copilot が動作するのは、Nodaro アプリの中だけです。**すべてのルートは、ログイン中のユーザー自身のセッションを持たない呼び出し元を、`403 in_app_only` で拒否します。SDK はこれを `ForbiddenError` としてスローし、その `code` は `forbidden` になるため、`message` を確認してください。API トークンと OAuth トークンでは、誰の Copilot も操作できません。ブラウザーアプリからは、[`supabaseAuth`](https://nodaro.ai/docs/developers/sdk/auth#supabaseauthsupabase) を使って利用します。Copilot は Nodaro Cloud で動作します。この機能をオフにしているデプロイメントでは、スレッドを開いたりターンを送信したりすると `503 feature_disabled` が返されます。

## メソッド
| メソッド | 内容 |
| --- | --- |
| [`create(input)`](#createinput) | ワークフロー上、または新しいワークフロー上にスレッドを開きます |
| [`list(params)`](#listparams) | ワークフローの有効なスレッドを読み取ります |
| [`get(id, opts?)`](#getid-opts) | スレッドを、そのメッセージとともに読み取ります |
| [`archive(id)`](#archiveid) | スレッドをアーカイブします |
| [`cancel(id)`](#cancelid) | 実行中のターンを停止します |
| [`stream(threadId, opts)`](#streamthreadid-opts) | メッセージを送信し、ターンのフレームを届くたびに読み取ります |

これらのメソッドは、`stream()` を除き、API の `{ data }` エンベロープをそのまま返します。`stream()` は、フレームを yield します。

## client.copilot
### create(input)
スレッドを開きます。既存の `workflowId` か、`prompt` を渡します。`prompt` を渡した場合は、サーバーがそれをもとにワークフローを作成します。すでに有効なスレッドがあるワークフローを開いた場合、2 つ目のスレッドではなく、その既存のスレッドが返されます。

```ts
create(input: { workflowId?: string; prompt?: string; name?: string }): Promise<{
data: { thread: CopilotThread; workflow: CopilotThreadWorkflow }
}>
```

<TypeTable
type={{
workflowId: { type: 'string', description: "既存のワークフローです。workflowId か prompt のいずれかを指定します。" },
prompt: { type: 'string', description: "新しいワークフローの生成元になるプロンプトです。" },
name: { type: 'string', description: "prompt と一緒に指定する、新しいワークフローの名前です。" },
}}
/>

```ts
const { data } = await client.copilot.create({ workflowId })
const threadId = data.thread.id
```

スレッドは、`id`、`workflowId`、`runMode`（実行を提案して待つ `ask`、または `autoRunLimitCredits` の範囲内で実行する `auto`）、`modelTier`、`allowPublishing`、`userTurnCount`、`lastMessageAt`、`createdAt` を持ちます。`surface` は、そのスレッドが属するアシスタントを示します。存在しない場合、デプロイメントがそれを示していません。

### list(params)
ワークフローの有効なスレッドを読み取ります。存在しない場合は `null` です。

```ts
list(params: { workflowId: string }): Promise<{ data: { thread: CopilotThread | null } }>
```

<TypeTable
type={{
workflowId: { type: 'string', required: true, description: "ワークフローの ID です。" },
}}
/>

```ts
const { data: { thread } } = await client.copilot.list({ workflowId })
```

### get(id, opts?)
スレッドを、そのメッセージとともに読み取ります。スレッドには、導出された `status`（`running` または `idle`）と `activeTurnId` も含まれます。

```ts
get(id: string, opts?: { after?: number; limit?: number }): Promise<{
data: { thread: CopilotThread; messages: CopilotMessage[] }
}>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "スレッドの ID です。" },
after: { type: 'number', description: "追いつくために、このシーケンス番号より後のメッセージだけを返します。" },
limit: { type: 'number', description: "ページのサイズで、サーバーの上限までです。" },
}}
/>

```ts
const { data: { messages } } = await client.copilot.get(threadId, { after: lastSeq })
```

各メッセージは、`id`、`seq`、`turnId`、`role`、`createdAt`、`parts`（テキストの部分とツール呼び出しの部分）を持ちます。

### archive(id)
スレッドをアーカイブします。メッセージは引き続き読み取れ、何も削除されません。実行中のターンがあるスレッドは、アーカイブできません。

```ts
archive(id: string): Promise<{ data: { archived: true } }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "スレッドの ID です。" },
}}
/>

```ts
await client.copilot.archive(threadId)
```

### cancel(id)
スレッドの実行中のターンを停止し、停止を要求したターンを返します。そのターンのストリームは、`done` フレームで終わります。

```ts
cancel(id: string): Promise<{ data: { cancelling: true; turnId: string } }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "スレッドの ID です。" },
}}
/>

```ts
await client.copilot.cancel(threadId)
```

### stream(threadId, opts)
メッセージを送信し、ターンのフレームをサーバー送信イベントとして、届くたびに yield します。完全な回答を待つのではなく、テキスト、ツールの動作、提案をリアルタイムで表示できます。ここで紹介するメソッドのうち、クレジットを消費するのはこれだけです。

```ts
stream(threadId: string, opts: {
message: string
baseVersion?: number
tier?: "economy" | "standard" | "premium"
signal?: AbortSignal
}): AsyncGenerator<CopilotStreamFrame>
```

<TypeTable
type={{
threadId: { type: 'string', required: true, description: "スレッドの ID です。" },
message: { type: 'string', required: true, description: "ユーザーのメッセージです。" },
baseVersion: { type: 'number', description: "ユーザーが見ているワークフローのバージョンです。" },
tier: { type: '"economy" | "standard" | "premium"', description: "このターンのモデルティアです。" },
signal: { type: 'AbortSignal', description: "ターンのストリームを終了させます。" },
}}
/>

```ts
try {
for await (const frame of client.copilot.stream(threadId, { message: "Tidy the graph" })) {
if (frame.type === "token") appendText(frame.data.text)
if (frame.type === "run_proposed") await askTheUser(frame.data)
if (frame.type === "done") break
}
} catch (err) {
if ((err as Error).name !== "AbortError") throw err // a stopped turn is a normal ending
}
```

`CopilotStreamFrame` は、`type` で判別するユニオン型です。

| フレーム | 内容 |
| --- | --- |
| `metadata` | ターンの ID、モデル、`runMode`、`autoRunLimitCredits`、設定 |
| `token` | 回答のテキストの断片 |
| `tool_call` | アシスタントが使うツールで、`started` の後に `finished` または `failed` になります |
| `workflow_updated` | 追加、変更、削除されたノードと、新しいバージョン |
| `workflow_created` | アシスタントが作成したワークフロー |
| `run_proposed` | 人による確認のための、ターンが実行しようとしている内容 |
| `memory_saved` | アシスタントが記憶することを選んだ内容 |
| `usage` | 使用したトークンと `creditsCharged` |
| `done` | 終了です。`completed`、`capped`、`cancelled` のいずれかです。 |
| `error` | ターンが失敗したことを示し、`code` と `message` を伴います |

フレームの `data` はそのまま渡されるため、この SDK がモデル化していないフィールドも受け取れます。モデル化されていない種類のフレームは、例外をスローするのではなくスキップされるため、新しいサーバーによって古いクライアントが壊れることはありません。このバージョンは、`studio` サーフェスのスレッドにある提案カードである `action_proposed` をモデル化していないため、その提案は呼び出し元に渡されません。

**ターンの存続期間は、あなたが管理します。**ターンは数分間実行されることがあるため、クライアントの `timeoutMs` は適用されません。`signal` を渡すか、反復処理を止めてください。どちらもリクエストを終了させます。中断すると、最初のフレームの前であれ、2 つのフレームの間であれ、`NodaroError` ではなく、ランタイム自身の `AbortError` で反復処理が拒否されます。開始リクエストのエラーステータスは、どのフレームより前に、通常の[型付きエラー](https://nodaro.ai/docs/developers/sdk/errors)として、引き続きスローされます。

## Frequently asked questions

### client.copilot は API トークンで使えますか？

使えません。Copilot のすべてのルートは、API トークンと OAuth トークンを 403 in_app_only で拒否します。使えるのは、Nodaro アプリから supabaseAuth で送られる、ログイン中のユーザー自身のセッションだけです。

### client.copilot.stream は何を返しますか？

型付きフレームの非同期イテレーターです。ターンのメタデータ、テキストの断片、ツール呼び出し、ワークフローの更新、実行の提案、使用量、そして最後の done または error フレームです。for await で反復処理します。

### クライアントのタイムアウトで、長い Copilot のターンは止まりますか？

止まりません。ターンは数分間実行されることがあるため、タイムアウトは適用されません。終了させるには、AbortSignal を渡すか、反復処理を止めてください。

### 呼び出し元が受け取れないことがあるのは、どのフレームですか？

この SDK のバージョンがモデル化していない種類のフレームは、スキップされます。これには、studio サーフェスのスレッドにある提案カード（action_proposed という名前です）が含まれるため、その提案は呼び出し元に渡されません。
