Skip to content

feat(eve): Claude Code agent with persistent chat sessions#8

Merged
anujeet98 merged 19 commits into
mainfrom
feat/eve-agent-claude-code
Jul 17, 2026
Merged

feat(eve): Claude Code agent with persistent chat sessions#8
anujeet98 merged 19 commits into
mainfrom
feat/eve-agent-claude-code

Conversation

@Rishavraaj

Copy link
Copy Markdown
Member

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)

  • Model: Switched from openai('gpt-4.1') to claudeCode('sonnet'). Claude CLI runs inside a per-session Fly Sprite.
  • Auth: HMAC-signed x-eve-principal header. The channel verifies it using BETTER_AUTH_SECRET shared with the backend — no session tokens, no cookies crossing the trust boundary.
  • Sprite provisioning: ensure-session-sprite.ts runs as a step.started resolver before each model call, provisioning or reconnecting the Fly Sprite. Claude OAuth credentials are fetched from the backend per user; falls back to CLAUDE_CODE_OAUTH_TOKEN env var if the user hasn't linked Claude under Settings.
  • Env: Removed OPENAI_API_KEY. Added BETTER_AUTH_SECRET, CLAUDE_CODE_OAUTH_TOKEN, ZUKO_BACKEND_URL.
  • Removed: Old CRM task tools (create_task, get_task, update_task, delete_task, list_tasks) and zuko-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 HMAC x-eve-principal, and pipes the request/response to the eve agent. Streams NDJSON without buffering.
    • EveCredentialsControllerGET /api/internal/eve/claude-oauth returns the authed user's Claude OAuth blob (refreshing if near-expiry) for the sprite to use.
    • EveChatsController + EveChatsRepository — thin session index: POST /api/eve-chats upserts a session record (sessionId, continuationToken, streamIndex, title); GET /api/eve-chats lists by user; GET /api/eve-chats/:sessionId fetches 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)

  • Same-origin proxy (/api/eve/v1/[...path]): Resolves the user's active org from the Better Auth session and forwards to the backend with x-org-id. Pipes streaming responses unbuffered.
  • Chat session API (/api/chats, /api/chats/[sessionId]): Thin wrappers over the backend eve-chats endpoints.
  • ChatSurface: Unified component for /chat (new) and /chat/:id (resume). Uses useEveAgent with onSessionChange to persist the continuation cursor after each turn and swap the URL to /chat/:sessionId on 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: Removed withEve() — the ai-agent runs as a separate process.

Database (libs/models)

  • EveChat model: New table (eve_chat) storing sessionId, continuationToken, streamIndex, title, userId, organizationId, createdAt, updatedAt.

How to run locally

Service Command Port
Backend nx run @zuko/backend:serve 3001
Frontend cd apps/web && next dev 3000
AI Agent cd apps/ai-agent && npm run dev any (set EVE_BASE_URL in backend .env)

Required env — apps/ai-agent/.env
SPRITES_TOKEN=...
BETTER_AUTH_SECRET=... # must match backend
CLAUDE_CODE_OAUTH_TOKEN=...
ZUKO_BACKEND_URL=http://localhost:3001

Required env — apps/backend/.env
BETTER_AUTH_SECRET=...
EVE_BASE_URL=http://127.0.0.1: # default 2100

Rishavraaj and others added 16 commits July 16, 2026 02:40
- 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>
constructor(private readonly db: PrismaService) {}

async getClaudeAccount(userId: number) {
return this.db.account.findFirst({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move database queries to repository layer

@Res() res: ExpressResponse,
): Promise<void> {
// --- Auth (Pattern-2) ---
const session = await auth.api.getSession({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

keep the controller layer lean, move logic to service layer.

Rishavraaj and others added 3 commits July 17, 2026 14:48
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
anujeet98 merged commit fd7a4ba into main Jul 17, 2026
1 check passed
@anujeet98
anujeet98 deleted the feat/eve-agent-claude-code branch July 17, 2026 10:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants