feat(eve): Claude Code agent with persistent chat sessions#8
Merged
Conversation
- Replace OpenAI with Claude Code (ai-sdk-provider-claude-code) - Add HMAC trusted-header auth (x-eve-principal, BETTER_AUTH_SECRET) - Add eve-principal sign/verify shared between backend + ai-agent - Add EveModule: proxy controller, credentials controller, chats CRUD - Add EveChat Prisma model + migration - Add same-origin Next.js proxy /api/eve/v1/[...path] - Add /api/chats GET/POST and /api/chats/[sessionId] GET - Rewrite ChatSurface with onSessionChange cursor + URL swap on first turn - Add fetchChatHistory for NDJSON resume replay - Update /chat and /chat/:id pages for new/resume flows - Update ChatsList to read from /api/chats (eve sessions) - Remove withEve() from next.config — ai-agent runs separately - Slim accounts to Claude + Codex only; remove getConnectionStatus - Fix resolveClaudeCredsJson to fall back when Claude not linked - Fix @Inject() on EveProxyController to bypass esbuild decorator metadata bug - Update env.ts: drop OPENAI_API_KEY, add BETTER_AUTH_SECRET + CLAUDE_CODE_OAUTH_TOKEN Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Delete accounts.module.ts, connection-status.ts (unused) - Delete web/src/hooks/use-chats.ts (replaced by /api/chats route) - Remove SUPPORTED_ACCOUNT_PROVIDERS, UpsertAccountOauthDto, upsertAccountOauthSchema (unused exports) - Unexport getValidClaudeOauthFromPool (internal helper only) - Add @anthropic-ai/claude-agent-sdk + ai-sdk-provider-claude-code to ai-agent deps; remove @ai-sdk/openai Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace @ai-sdk/openai dependency with @anthropic-ai/claude-agent-sdk - Add ai-sdk-provider-claude-code provider for Claude Code integration - Add @stablelib/base64 dependency for SDK requirements - Update @anthropic-ai/sdk and supporting platform-specific binaries - Remove trailing whitespace in upsert-account-oauth.dto.ts for consistency Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… versions Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove unused useState import from ChatSurface - Remove unused input state variable from ChatSurface - Update EveMessage import source from 'eve/react' to 'eve/client' - Add missing id field to EveSession interface in ChatsList - Clean up unused dependencies to reduce bundle size and improve type accuracy Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…alStorage - Add useEveAgent mock to replace localStorage-based session management - Import and mock eve/react useEveAgent hook with capture of onSessionChange callback - Replace localStorage stub with useEveAgent mock implementation in beforeEach - Update NewChatPage tests to trigger navigation via onSessionChange callback - Change navigation spy from mockReplace to window.history.replaceState - Simplify test expectations to verify mockSend calls instead of localStorage checks - Update placeholder text matchers to remove "@" hint references - Refactor session creation test to use act() wrapper for callback invocation - Remove first message localStorage persistence assertions - Update fetch mock expectations to use /api/chats instead of /api/proxy/api/chats - Migrate from localStorage-based context entity storage to message parameter passing - Consolidate fetch setup and reduce boilerplate in individual tests Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Reformat union type annotation for capturedOnSessionChange to break across multiple lines - Simplify expect statement by removing unnecessary line breaks in chat input render test - Break long waitFor assertion across multiple lines for improved readability - Improve code consistency with project's formatting standards Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…d session IDs Old beforeEach created chats via ChatsController (numeric DB IDs), but ChatPage now treats the URL param as an eve sessionId → GET /api/eve-chats/:id returned 404 → "Session not found." for every test. Navigate to /chat (new session page) so ChatSurface renders immediately. Remove beforeEach/afterEach session setup — eve manages session lifecycle. Update auth-validation test to watch /api/eve/v1/ instead of /api/chat. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…test After first AI response appears, status may still be 'streaming' — ChatSurface drops messages when working=true. Wait for Submit button to reappear (Stop→Submit transition) before sending the second message. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
anujeet98
requested changes
Jul 17, 2026
| constructor(private readonly db: PrismaService) {} | ||
|
|
||
| async getClaudeAccount(userId: number) { | ||
| return this.db.account.findFirst({ |
Contributor
There was a problem hiding this comment.
move database queries to repository layer
anujeet98
requested changes
Jul 17, 2026
| @Res() res: ExpressResponse, | ||
| ): Promise<void> { | ||
| // --- Auth (Pattern-2) --- | ||
| const session = await auth.api.getSession({ |
Contributor
There was a problem hiding this comment.
keep the controller layer lean, move logic to service layer.
Extracts all Prisma queries from AccountsService into a new AccountsRepository. Service now delegates to repo; no query logic remains in the service layer. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Controller now only resolves session/orgId and delegates. EveProxyService owns org membership check, upstream fetch, header forwarding, and NDJSON streaming. recoverRequestBody moved to service. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
anujeet98
approved these changes
Jul 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Replaces the OpenAI-based agent with Claude Code running inside Fly Sprites.
Adds HMAC-signed identity forwarding, persistent eve chat sessions, a same-origin
proxy, and a full chat surface with resume support.
What changed
AI Agent (
apps/ai-agent)openai('gpt-4.1')toclaudeCode('sonnet'). Claude CLI runs inside a per-session Fly Sprite.x-eve-principalheader. The channel verifies it usingBETTER_AUTH_SECRETshared with the backend — no session tokens, no cookies crossing the trust boundary.ensure-session-sprite.tsruns as astep.startedresolver before each model call, provisioning or reconnecting the Fly Sprite. Claude OAuth credentials are fetched from the backend per user; falls back toCLAUDE_CODE_OAUTH_TOKENenv var if the user hasn't linked Claude under Settings.OPENAI_API_KEY. AddedBETTER_AUTH_SECRET,CLAUDE_CODE_OAUTH_TOKEN,ZUKO_BACKEND_URL.create_task,get_task,update_task,delete_task,list_tasks) andzuko-client.ts.Backend (
apps/backend)EveModule: New module with four controllers/services:EveProxyController— catch-all reverse proxy for/api/eve/v1/*. Validates the Better Auth session, checks org membership, signs an HMACx-eve-principal, and pipes the request/response to the eve agent. Streams NDJSON without buffering.EveCredentialsController—GET /api/internal/eve/claude-oauthreturns the authed user's Claude OAuth blob (refreshing if near-expiry) for the sprite to use.EveChatsController+EveChatsRepository— thin session index:POST /api/eve-chatsupserts a session record (sessionId, continuationToken, streamIndex, title);GET /api/eve-chatslists by user;GET /api/eve-chats/:sessionIdfetches one.AccountsService: New service scoped to Claude and Codex only —getClaudeAccount,upsertClaudeOauth,deleteByProvider,getValidClaudeOauth(with auto-refresh).eve-principal.ts: HMAC sign/verify utility (BETTER_AUTH_SECRET, 60s TTL). Duplicated byte-for-byte in the ai-agent since the two build boundaries can't share code.Frontend (
apps/web)/api/eve/v1/[...path]): Resolves the user's active org from the Better Auth session and forwards to the backend withx-org-id. Pipes streaming responses unbuffered./api/chats,/api/chats/[sessionId]): Thin wrappers over the backendeve-chatsendpoints.ChatSurface: Unified component for/chat(new) and/chat/:id(resume). UsesuseEveAgentwithonSessionChangeto persist the continuation cursor after each turn and swap the URL to/chat/:sessionIdon first message — no remount, live turn uninterrupted.fetchChatHistory: Replays past NDJSON events for resume. Streams with a 1500ms idle timeout to detect end-of-history.ChatsList: Now reads from/api/chats(eve sessions) instead of the old chat API.next.config.ts: RemovedwithEve()— the ai-agent runs as a separate process.Database (
libs/models)EveChatmodel: New table (eve_chat) storingsessionId,continuationToken,streamIndex,title,userId,organizationId,createdAt,updatedAt.How to run locally
nx run @zuko/backend:servecd apps/web && next devcd apps/ai-agent && npm run devEVE_BASE_URLin backend.env)Required env —
apps/ai-agent/.envSPRITES_TOKEN=...
BETTER_AUTH_SECRET=... # must match backend
CLAUDE_CODE_OAUTH_TOKEN=...
ZUKO_BACKEND_URL=http://localhost:3001
Required env —
apps/backend/.envBETTER_AUTH_SECRET=...
EVE_BASE_URL=http://127.0.0.1: # default 2100