# Editor Assistant API 実装解説

## 要求仕様

Editor Assistant API は、OpenAI AssistantAPI を使用して、マークダウンエディターの編集をサポートする機能です。主な要件は以下の通りです：

1. **ストリーミング処理**：
   - OpenAI からの応答をストリーミングで受け取り、Server-Sent Events (SSE) でクライアントにリアルタイムに転送
   - JSON データを適切なタイミングで解析し、クライアントに送信

2. **データ形式**：
   - SSE による応答は `SseMessageSchema`, `SseDetectedDiffSchema`, `SseFinalizedSchema` に準拠した JSON 形式
   - `{ message: "..." }` と delta 形式の差分情報（`insert`, `delete`, `retain`）を含む

3. **エラーハンドリング**：
   - 不完全な JSON データの処理時のエラーを適切に処理
   - リソースリークの防止

4. **効率性**：
   - メモリ使用量を最小限に抑える
   - 不要な通信を避け、クライアントへの適切なタイミングでのデータ送信を実現
   - メッセージの増分送信による通信量削減と、すでに処理済みの要素のスキップによる処理効率の向上

## 重要なインプット

### 実装時に参照したコード

1. **jsonrepair ライブラリ**：
   - 壊れた JSON や不完全な JSON を修復するライブラリ
   - 特に部分的なストリーミング JSON の処理に有効

2. **型定義**：
   - `message-error.ts`: エラー型と定義
   - `schema.ts`: エディターアシスタントのメッセージと差分の Zod スキーマ定義

### 今後のリファクタリングに重要なインプット

1. **OpenAI API の仕様変更**：
   - AssistantAPI のレスポンス形式の変更に注意

2. **jsonrepair のアップデート**：
   - 新バージョンでの API 変更や最適化手法の変更を確認

3. **パフォーマンス監視**：
   - メモリ使用量と処理時間のモニタリング
   - 大規模 JSON 処理時のボトルネック特定

## 実装のポイント

### 1. ストリーミング処理と不完全JSONの修復

ストリーミング処理において、最大の課題は不完全なJSON文字列の処理です。OpenAI APIから部分的に届くJSONデータを即座に解析するために、以下の対策を実装しています：

- **jsonrepair ライブラリの採用理由**：
  - 通常、JSON文字列は完全な形でなければパースできません。これはストリーム処理において大きな制約となります。
  - 全ての文字列を受け取るまで待たずに、途中経過をリアルタイムにユーザーに提示するため、jsonrepairを使用して部分的なJSON文字列を修復しています。
  - これにより、メッセージと差分情報を受信次第、速やかにクライアントに届けることが可能になり、ユーザー体験が大幅に向上します。
  
  **具体例**:
  ```javascript
  // ストリームから受け取った不完全なJSONの例
  const partialJson = '{"contents": [{"message": "テキストを修正し';
  
  // 通常のJSON.parseではエラー
  // JSON.parse(partialJson); // SyntaxError: Unexpected end of JSON input
  
  // jsonrepairを使用した修復
  const repairedJson = jsonrepair(partialJson);
  // 結果: '{"contents": [{"message": "テキストを修正しています"}]}'
  
  // 修復されたJSONはパース可能
  const parsedJson = JSON.parse(repairedJson);
  // 結果: { contents: [{ message: 'テキストを修正しています' }] }
  ```
  
  - このように、正常なJSONとして完結していない途中のデータでも、jsonrepairは欠けている部分を補完して有効なJSONに変換します。OpenAI APIからの応答では、完全なJSONが揃うまで待つことなく、部分的に受信したデータを即座に処理できるようになります。

- **rawBufferの累積と継続的な解析**：
  - 受信したテキストチャンクを`rawBuffer`に累積し、その都度jsonrepairでパース可能な形に修復しています。
  - これは特にOpenAI APIの応答がJSON形式で指定されているにもかかわらず、ストリームではその一部だけが届く特性に対応するための実装です。

### 2. 差分検出と適応的送信制御

エディターアシスタントの核心部分は、OpenAI APIからのレスポンスから差分情報を適切に抽出し、効率的にクライアントに送信する機能です。以下のような工夫を行っています：

- **メッセージと差分の処理の統合と最適化**：
  - UI/UX要件に基づく設計として、メッセージと差分の処理を単一ループで効率的に実装しています。
  - **メッセージ処理**：メッセージの「増分」（新しく追加された部分）のみをクライアントに送信します。これにより通信量を削減し、クライアント側の処理負荷を軽減します。
  - **差分処理**：JSONノードとして確定した差分は即座に検出し通知します。ただし、確定していない（変更中の可能性がある）差分は送信を控えることでエディターの過剰な更新を防止します。

- **処理効率の向上メカニズム**：
  - `processedMessages` Mapを使って、各メッセージ要素の前回の内容を記録し、差分のみを計算します。
  - `lastProcessedContentLength` を用いて、すでに処理済みの要素をスキップします。これにより大量のデータでも効率的に処理できます。
  ```javascript
  // 処理開始位置の最適化 - 確定済み要素のスキップ
  const startProcessingIndex = Math.max(0, Math.min(this.lastProcessedContentLength, contents.length) - 1);
  
  // 単一ループでメッセージと差分を処理
  for (let i = startProcessingIndex; i < contents.length; i++) {
    // メッセージと差分の処理
  }
  ```

- **OpenAIストリームの特性に対応した差分確定判定**：
  - OpenAI APIからのJSONストリームは「前方から順に確定していく」特性があります。このAPIの特性を活用し、以下の判定ロジックを実装しています：
  ```javascript
  // 最終要素が変化した、またはこれが最終要素ではない場合 → 差分を確定とみなす
  if (i < currentContentIndex || currentContentIndex > this.lastContentIndex) {
    // 差分を確定して送信リストに追加
  }
  ```
  - この条件判定は単なる技術的工夫ではなく、UXの向上を目的としています。確定していない差分を頻繁に送信すると、エディターが頻繁に更新されてユーザー体験が悪化するためです。

- **重複防止メカニズム**：
  - 差分の重複送信を避けるため、一意のキーを生成する`getDiffKey`メソッドを実装しています。
  - Setデータ構造（`sentDiffKeys`）を使うことで、O(1)の時間複雑度で効率的に重複チェックを行います。
  - この実装は、ストリームデータの累積的な性質（同じデータが何度も現れる可能性がある）に対応するために不可欠です。

- **増分メッセージ計算の最適化**：
  - メッセージ要素ごとに前回のメッセージとの差分を計算する`getAppendedContent`メソッドを実装しています。
  - これにより、クライアントには新たに追加された部分のみを送信でき、通信量を大幅に削減できます。
  ```javascript
  private getAppendedContent(previousMessage: string, currentMessage: string): string {
    // 前回のメッセージから増分部分のみを返す
    return currentMessage.slice(previousMessage.length);
  }
  ```

### 3. エラー耐性とリソース管理

ストリーミング処理においてエラー耐性とリソース管理は特に重要です。以下の対策を講じています：

- **エラーハンドリングの階層化**：
  - JSONパースエラーはデバッグ用にログ出力するのみとし、処理を継続します。これはストリーミングの性質上、部分的なデータでパースエラーが発生するのは正常な動作だからです。
  - 重大なエラーはクライアントに適切に通知し、リソースを解放します。

- **リソース解放の徹底**：
  - クライアント切断時やエラー発生時、処理完了時など、あらゆるシナリオでリソースを確実に解放するクリーンアップ処理を実装しています。
  - `destroy`メソッドでメモリキャッシュをクリアし、イベントリスナーを解除することで、メモリリークを防止しています。

- **非同期ストリーム処理の安全な終了**：
  - ストリームの終了を適切に検出し、完全な結果を送信してから接続を終了する機構を設けています。
  - エラー時でも可能な限り正常な形でレスポンスを返し、クライアント側での復旧を容易にします。

このような設計と実装により、リアルタイム性と正確性を両立したエディターアシスタント機能を実現しています。ストリーミング処理の特性を活かしつつ、効率的なデータ処理と適応的な通知制御によって優れたユーザー体験を提供しています。

