Skip to content

Extract createBridgeHandler from sandbox-agent-self into agent-core #5

@toyamarinyon

Description

@toyamarinyon

Background

studio.giselles.aiagent-api ルートを追加するにあたり、@giselles-ai/sandbox-agent-core (agent-core) を直接利用して独自の認証・rate limit を組み込みたい。

現状、Bridge 操作(SSE events / dispatch / respond)のロジックは sandbox-agent-self にインラインで実装されており、外部から再利用できない。

Current Architecture

sandbox-agent-self
├── createAgentApiHandler()        ← 1つのエンドポイントに全操作を集約
│   ├── GET  → SSE bridge events   (インライン実装)
│   └── POST → discriminatedUnion
│       ├── agent.run              (createGeminiChatHandler + bridge session)
│       ├── bridge.dispatch        (インライン実装)
│       └── bridge.respond         (インライン実装)
│
└── depends on
    └── agent-core
        ├── createGeminiChatHandler()   ✅ 再利用可能
        ├── createBridgeSession()       ✅ 再利用可能
        ├── dispatchBridgeRequest()     ✅ 再利用可能
        ├── resolveBridgeResponse()     ✅ 再利用可能
        └── (Bridge HTTP handlers)      ❌ 存在しない

Problem

再利用性 備考
Bridge 低レベル関数 (createBridgeSession, dispatch, resolve...) ✅ 再利用可能 agent-core で export 済み
Gemini Chat Handler ✅ 再利用可能 agent-core で export 済み
Bridge HTTP ハンドラ (SSE / dispatch / respond) ❌ 再利用不可 sandbox-agent-self にインラインで埋め込み

外部プロジェクト(studio.giselles.ai 等)が agent-core を使う場合、Bridge の HTTP ハンドラ部分を自前で再実装する必要がある。

Desired Architecture

agent-core (after)
├── createGeminiChatHandler()       (既存)
├── createBridgeSession()           (既存)
├── dispatchBridgeRequest()         (既存)
├── resolveBridgeResponse()         (既存)
├── ...other low-level functions    (既存)
│
└── createBridgeHandler()           ← 🆕 追加
    ├── GET  → SSE bridge events
    └── POST → discriminatedUnion
        ├── bridge.dispatch
        └── bridge.respond

Data Flow

sequenceDiagram
    participant Client as Client (Browser)
    participant Run as /agent-api/run
    participant Bridge as /agent-api/bridge/[...bridge]
    participant Sandbox as Vercel Sandbox (Gemini)
    participant Redis as Redis

    Note over Run: API key auth + rate limit (studio独自)
    Client->>Run: POST { message }
    Run->>Redis: createBridgeSession()
    Run->>Sandbox: createGeminiChatHandler()
    Run-->>Client: ndjson stream (bridge session + Gemini output)

    Note over Bridge: bridge session token auth (agent-core提供)
    Client->>Bridge: GET /events?sessionId=...&token=...
    Bridge-->>Client: SSE stream (bridge requests)

    Sandbox->>Bridge: POST { type: "bridge.dispatch", ... }
    Bridge->>Redis: dispatchBridgeRequest()
    Redis-->>Client: SSE push (request)
    Client->>Bridge: POST { type: "bridge.respond", ... }
    Bridge->>Redis: resolveBridgeResponse()
    Redis-->>Sandbox: response
Loading

Consumer Usage (studio.giselles.ai)

// /agent-api/bridge/[...bridge]/route.ts
import { createBridgeHandler } from "@giselles-ai/sandbox-agent-core";

export const { GET, POST } = createBridgeHandler();
// /agent-api/run/route.ts
import { createBridgeSession, createGeminiChatHandler } from "@giselles-ai/sandbox-agent-core";
// ... studio独自の認証・rate limit ロジック

After refactoring, sandbox-agent-self becomes:

// sandbox-agent-self/src/index.ts
import { createBridgeHandler, createGeminiChatHandler, createBridgeSession } from "@giselles-ai/sandbox-agent-core";

export function createAgentApiHandler(options) {
  const bridge = createBridgeHandler();
  const chat = createGeminiChatHandler();

  return {
    GET: bridge.GET,
    POST: async (request) => {
      const body = await request.json();
      if (body.type === "bridge.dispatch" || body.type === "bridge.respond") {
        return bridge.POST(request);  // delegate to bridge handler
      }
      // agent.run logic (create session + chat)
      ...
    },
  };
}

Scope

  • Extract SSE bridge events handler from sandbox-agent-self into agent-core as part of createBridgeHandler
  • Extract bridge.dispatch / bridge.respond POST handling into createBridgeHandler
  • Export createBridgeHandler from agent-core
  • Refactor sandbox-agent-self to use createBridgeHandler internally
  • Verify existing tests pass after refactoring

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions