nicholai 0b0a1ff312 feat: implement LangGraph.js agentic framework with OpenRouter integration
- Add complete LangGraph state machine with 4 nodes (plan, execute, validate, advance)
- Integrate OpenRouter API with dynamic model fetching (321+ models)
- Implement Durable Object for state management and WebSocket server
- Create SSH proxy service with full LangGraph agent (deployed to Fly.io)
- Add beautiful retro terminal UI with split-pane layout
- Implement agent control panel with model selection and run controls
- Create API routes for agent lifecycle (start, pause, resume, command, status)
- Add WebSocket integration with auto-reconnect
- Implement proper event streaming following context7 best practices
- Deploy complete stack to Cloudflare Workers + Fly.io

Features:
- Multi-LLM testing via OpenRouter (GPT-4o, Claude, Llama, DeepSeek, etc.)
- Real-time agent reasoning display
- SSH integration with OverTheWire Bandit server
- Pause/resume functionality for manual intervention
- Error handling with retry logic
- Cost tracking infrastructure
- Level-by-level progress tracking (0-33)

Infrastructure:
- Cloudflare Workers: UI, Durable Objects, API routes
- Fly.io: SSH proxy + LangGraph agent runtime
- Full TypeScript throughout
- Comprehensive documentation (10 guides, 2,500+ lines)

Status: 95% complete, production-deployed, fully functional
2025-10-09 07:03:29 -06:00

50 lines
1.3 KiB
TypeScript

/**
* WebSocket route for real-time agent communication
*/
import { NextRequest } from "next/server"
import { getCloudflareContext } from "@opennextjs/cloudflare"
// Get Durable Object stub
function getDurableObjectStub(runId: string, env: any) {
const id = env.BANDIT_AGENT.idFromName(runId)
return env.BANDIT_AGENT.get(id)
}
/**
* GET /api/agent/[runId]/ws
* Upgrade to WebSocket connection
*/
export async function GET(
request: NextRequest,
{ params }: { params: { runId: string } }
) {
const runId = params.runId
const { env } = await getCloudflareContext()
if (!env?.BANDIT_AGENT) {
return new Response("Durable Object binding not found", { status: 500 })
}
try {
// Forward WebSocket upgrade to Durable Object
const stub = getDurableObjectStub(runId, env)
// Create a new request with WebSocket upgrade headers
const upgradeHeader = request.headers.get('Upgrade')
if (!upgradeHeader || upgradeHeader !== 'websocket') {
return new Response('Expected Upgrade: websocket', { status: 426 })
}
// Forward the request to DO
return await stub.fetch(request)
} catch (error) {
console.error('WebSocket upgrade error:', error)
return new Response(
error instanceof Error ? error.message : 'Unknown error',
{ status: 500 }
)
}
}