# 機能設計書 25-メッセージ管理

## 概要

本ドキュメントは、OpenCodeにおけるメッセージ管理機能の設計を定義する。メッセージはセッション内のユーザーとAIアシスタント間の対話単位であり、各メッセージは複数のパーツ（テキスト、ツール呼び出し、ファイル添付等）で構成される。

### 本機能の処理概要

メッセージ管理機能は、セッション内のメッセージとパーツの作成・更新・削除・取得を行う機能である。ストリーミング処理に対応し、AIの応答をリアルタイムで処理・保存する。

**業務上の目的・背景**：AIとの対話履歴を永続化し、セッションの再開や履歴参照を可能にする。ストリーミングレスポンスをリアルタイムで保存することで、中断時のデータ損失を防ぐ。

**機能の利用シーン**：ユーザーがメッセージを送信する際、AIが応答を生成する際、過去の会話履歴を表示する際、メッセージをリバート（取り消し）する際に利用される。

**主要な処理内容**：
1. ユーザーメッセージの作成・保存
2. アシスタントメッセージの作成・更新（ストリーミング対応）
3. パーツ（テキスト、ツール、ファイル等）の管理
4. メッセージ一覧の取得（ストリーミング形式）
5. メッセージ・パーツの削除
6. パーツのデルタ更新（インクリメンタル更新）

**関連システム・外部連携**：LSP（Language Server Protocol）との連携で診断情報を取得。Snapshot機能との連携でファイル変更追跡。

**権限による制御**：セッション単位での権限管理。メッセージ操作はセッション所有者のみ可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 2 | セッション画面 | 主画面 | MessageTimelineによるメッセージの表示・管理 |
| 26 | セッション | 主画面 | UserMessage/AssistantMessageコンポーネントによる表示 |

## 機能種別

CRUD操作 / ストリーミング処理

## 入力仕様

### 入力パラメータ

#### メッセージ更新（updateMessage）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| id | string | Yes | メッセージID | message_で始まるIdentifier形式 |
| sessionID | string | Yes | セッションID | session_で始まるIdentifier形式 |
| role | "user" \| "assistant" | Yes | メッセージロール | 列挙値 |
| time | object | Yes | タイムスタンプ情報 | created必須、completed任意 |
| その他 | 各種 | 条件付き | ロールに応じた追加フィールド | zodスキーマによる検証 |

#### パーツ更新（updatePart）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| id | string | Yes | パーツID | part_で始まるIdentifier形式 |
| messageID | string | Yes | 親メッセージID | message_で始まるIdentifier形式 |
| sessionID | string | Yes | セッションID | session_で始まるIdentifier形式 |
| type | string | Yes | パーツタイプ | 列挙値（text, tool, file等） |

### 入力データソース

- AIプロバイダーからのストリーミングレスポンス
- ユーザー入力（テキスト、ファイル添付）
- ツール実行結果

## 出力仕様

### 出力データ

#### MessageV2.Info（メッセージ情報）

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | string | メッセージID |
| sessionID | string | セッションID |
| role | "user" \| "assistant" | ロール |
| time.created | number | 作成日時 |
| time.completed | number? | 完了日時（アシスタントのみ） |

#### MessageV2.Part（パーツ情報）

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | string | パーツID |
| messageID | string | 親メッセージID |
| sessionID | string | セッションID |
| type | string | パーツタイプ |

### 出力先

- SQLiteデータベース（MessageTable、PartTable）
- Busイベント（message.updated、message.removed、message.part.updated等）

## 処理フロー

### 処理シーケンス

```
1. メッセージ作成/更新（updateMessage）
   ├─ zodスキーマによる検証
   ├─ MessageTableへのINSERT/UPDATE（onConflictDoUpdate）
   └─ message.updatedイベント発行

2. パーツ作成/更新（updatePart）
   ├─ zodスキーマによる検証
   ├─ PartTableへのINSERT/UPDATE（onConflictDoUpdate）
   └─ message.part.updatedイベント発行

3. パーツデルタ更新（updatePartDelta）
   └─ message.part.deltaイベント発行（DB更新なし）

4. メッセージ取得（messages/stream）
   ├─ MessageTableからSELECT
   ├─ PartTableから関連パーツ取得
   └─ WithParts形式で返却

5. メッセージ削除（removeMessage）
   ├─ MessageTableからDELETE（CASCADE）
   └─ message.removedイベント発行
```

### フローチャート

```mermaid
flowchart TD
    A[開始] --> B{操作種別}
    B -->|メッセージ更新| C[zodスキーマ検証]
    C --> D[DB INSERT/UPDATE]
    D --> E[message.updatedイベント]

    B -->|パーツ更新| F[zodスキーマ検証]
    F --> G[DB INSERT/UPDATE]
    G --> H[message.part.updatedイベント]

    B -->|パーツデルタ| I[message.part.deltaイベント]

    B -->|メッセージ取得| J[DB SELECT]
    J --> K[パーツ取得]
    K --> L[WithParts返却]

    B -->|メッセージ削除| M[DB DELETE]
    M --> N[message.removedイベント]

    E --> O[終了]
    H --> O
    I --> O
    L --> O
    N --> O
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-25-01 | ロール別スキーマ | UserとAssistantでメッセージスキーマが異なる | メッセージ作成時 |
| BR-25-02 | 親子関係 | AssistantメッセージはparentID（UserメッセージID）を持つ | Assistantメッセージ |
| BR-25-03 | CASCADE削除 | メッセージ削除時、関連パーツも自動削除 | メッセージ削除時 |
| BR-25-04 | Upsert | 同一IDのメッセージ/パーツは更新される | INSERT時にコンフリクト |
| BR-25-05 | パーツタイプ | text, tool, file, reasoning, step-start, step-finish等 | パーツ作成時 |

### 計算ロジック

ツール実行時間の計算:
```typescript
// ToolStateCompleted
time: {
  start: number,
  end: number,
  compacted: number | undefined // 圧縮された場合
}
// 実行時間 = end - start
```

## データベース操作仕様

### 操作別データベース影響一覧

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| メッセージ更新 | message | INSERT/UPDATE | メッセージの作成・更新 |
| パーツ更新 | part | INSERT/UPDATE | パーツの作成・更新 |
| メッセージ取得 | message, part | SELECT | メッセージとパーツの取得 |
| メッセージ削除 | message | DELETE | メッセージの削除（CASCADE） |
| パーツ削除 | part | DELETE | 個別パーツの削除 |

### テーブル別操作詳細

#### MessageTable

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT/UPDATE | id | メッセージID | 主キー |
| INSERT/UPDATE | session_id | セッションID | 外部キー |
| INSERT/UPDATE | time_created | 作成日時 | タイムスタンプ |
| INSERT/UPDATE | data | メッセージデータ | JSON（id, sessionID除く） |

#### PartTable

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT/UPDATE | id | パーツID | 主キー |
| INSERT/UPDATE | message_id | メッセージID | 外部キー |
| INSERT/UPDATE | session_id | セッションID | 検索用 |
| INSERT/UPDATE | time_created | 作成日時 | タイムスタンプ |
| INSERT/UPDATE | data | パーツデータ | JSON（id, messageID, sessionID除く） |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | Error | メッセージが存在しない（get時） | "Message not found: {messageID}"エラー |
| - | 制約違反 | 存在しないセッションIDを参照 | 外部キー制約エラー |

### リトライ仕様

リトライ機構は実装されていない。

## トランザクション仕様

Database.use()によるトランザクション管理。各操作は個別のトランザクションで実行される。

## パフォーマンス要件

- メッセージ更新は10ms以内
- ストリーミング処理はリアルタイム性を維持
- 大規模セッション（1000+メッセージ）でもページネーション（50件単位）で対応

## セキュリティ考慮事項

- セッション単位でのアクセス制御
- JSONカラムには検証済みデータのみ保存
- ツール実行結果は信頼できるソースからのみ

## 備考

- パーツのデルタ更新（updatePartDelta）はDBを更新せず、イベントのみ発行
- toModelMessages関数でAI SDKのフォーマットに変換

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | session.sql.ts | `packages/opencode/src/session/session.sql.ts` | MessageTable、PartTableの定義（37-62行目） |
| 1-2 | message-v2.ts | `packages/opencode/src/session/message-v2.ts` | MessageV2.User、MessageV2.Assistant型（345-438行目） |
| 1-3 | message-v2.ts | `packages/opencode/src/session/message-v2.ts` | MessageV2.Part discriminated union（371-388行目） |

**読解のコツ**: メッセージはroleで判別され、パーツはtypeで判別される。zodのdiscriminatedUnionパターンを理解する。

#### Step 2: CRUD操作を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.ts | `packages/opencode/src/session/index.ts` | Session.updateMessage関数（670-690行目） |
| 2-2 | index.ts | `packages/opencode/src/session/index.ts` | Session.updatePart関数（735-756行目） |
| 2-3 | index.ts | `packages/opencode/src/session/index.ts` | Session.removeMessage関数（692-710行目） |
| 2-4 | index.ts | `packages/opencode/src/session/index.ts` | Session.removePart関数（712-730行目） |

**主要処理フロー**:
- **670-690行目**: onConflictDoUpdateでupsert、イベント発行
- **735-756行目**: パーツのupsert、イベント発行
- **692-710行目**: CASCADE削除、イベント発行

#### Step 3: 取得処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | index.ts | `packages/opencode/src/session/index.ts` | Session.messages関数（512-526行目） |
| 3-2 | message-v2.ts | `packages/opencode/src/session/message-v2.ts` | MessageV2.stream関数（716-767行目） |
| 3-3 | message-v2.ts | `packages/opencode/src/session/message-v2.ts` | MessageV2.get関数（778-792行目） |

**主要処理フロー**:
- **716-767行目**: ページネーション（50件単位）でストリーミング取得
- **732-753行目**: パーツのバッチ取得と関連付け

#### Step 4: AI SDK変換を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | message-v2.ts | `packages/opencode/src/session/message-v2.ts` | toModelMessages関数（491-714行目） |

### プログラム呼び出し階層図

```
Session.updateMessage (public API)
    │
    ├─ zod検証 (MessageV2.Info)
    │
    └─ Database.use()
           ├─ db.insert(MessageTable)
           │      └─ onConflictDoUpdate
           └─ Database.effect()
                  └─ Bus.publish(MessageV2.Event.Updated)

Session.updatePart (public API)
    │
    ├─ zod検証 (MessageV2.Part)
    │
    └─ Database.use()
           ├─ db.insert(PartTable)
           │      └─ onConflictDoUpdate
           └─ Database.effect()
                  └─ Bus.publish(MessageV2.Event.PartUpdated)

MessageV2.stream (取得)
    │
    └─ ジェネレーター関数
           │
           ├─ db.select(MessageTable)
           │      └─ ページネーション（50件）
           │
           └─ db.select(PartTable)
                  └─ inArray(message_id)
```

### データフロー図

```
[入力]                    [処理]                         [出力]

MessageV2.Info ─────▶ Session.updateMessage() ──────▶ DB (message)
                              │                        │
                              └─ イベント発行 ─────────▶ Bus Event

MessageV2.Part ─────▶ Session.updatePart() ─────────▶ DB (part)
                              │                        │
                              └─ イベント発行 ─────────▶ Bus Event

sessionID ──────────▶ MessageV2.stream() ───────────▶ WithParts[]
                              │
                              ├─ メッセージ取得
                              └─ パーツ取得

WithParts[] ────────▶ toModelMessages() ────────────▶ ModelMessage[]
                              │
                              └─ AI SDK形式変換
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| index.ts | `packages/opencode/src/session/index.ts` | ソース | updateMessage、updatePart、removeMessage等の実装 |
| message-v2.ts | `packages/opencode/src/session/message-v2.ts` | ソース | メッセージ・パーツの型定義、stream関数、toModelMessages |
| session.sql.ts | `packages/opencode/src/session/session.sql.ts` | ソース | MessageTable、PartTableのスキーマ定義 |
| bus-event.ts | `packages/opencode/src/bus/bus-event.ts` | ソース | イベント定義 |
