flue-eve

Client

Use flue-eve/client anywhere you want the Eve session protocol without hand-writing POST requests and NDJSON stream parsing: scripts, evals, tests, backend jobs, or custom UIs.

Quick start

import { Client } from 'flue-eve/client'

const client = new Client({ host: 'http://127.0.0.1:5173' })
const session = client.session()

const response = await session.send('Hello')
const result = await response.result()

console.log(result.status)
console.log(result.message)

Create a client

const client = new Client({
  host: '', // same-origin by default
  auth: {
    bearer: async () => await getAccessToken(),
  },
  headers: async () => ({
    'x-request-id': crypto.randomUUID(),
  }),
  maxReconnectAttempts: 3,
})

host is the origin where /eve/v1/* is mounted. Use "" for same-origin browser code and a full URL for scripts or server-to-server calls.

Check health and info

const health = await client.health()
console.log(health.status, health.workflowId)

const info = await client.info()
console.log(info.agent?.name, info.tools)

Non-2xx responses throw ClientError, which carries the HTTP status and response body.

Start a session

const session = client.session()

const response = await session.send({
  message: 'Hello',
  agent: 'assistant',
  outputSchema: {
    type: 'object',
    properties: {
      answer: { type: 'string' },
    },
  },
  clientContext: {
    userId: 'user-123',
    tier: 'pro',
  },
})

for await (const event of response) {
  console.log(event.type)
}

After send(), the session state contains the returned sessionId, continuationToken, and next streamIndex.

Multi-turn conversations

Use the same ClientSession for follow-up turns:

const session = client.session()

await (await session.send('What is 2+2?')).result()
await (await session.send({ message: 'Now multiply by 3' })).result()
await (await session.send({ message: 'Add 10 to that' })).result()

Each send() call reuses the session's sessionId and continuationToken.

Wait for a result

send() returns a MessageResponse. Call result() when you want the final turn summary instead of handling every stream event:

const response = await session.send('Summarize this file')
const result = await response.result()

console.log(result.status)  // "completed" | "failed" | "waiting"
console.log(result.message) // final assistant text, when present
console.log(result.data)    // structured output, when outputSchema is used

When outputSchema is used, type the expected result:

const response = await session.send<{ colors: string[] }>({
  message: 'List 3 colors',
  outputSchema: {
    type: 'object',
    properties: {
      colors: { type: 'array', items: { type: 'string' } },
    },
  },
})

const result = await response.result()
console.log(result.data?.colors)

Stream events

MessageResponse is also an async iterable:

const response = await session.send('Explain the plan')

for await (const event of response) {
  switch (event.type) {
    case 'message.appended':
      process.stdout.write(String(event.data.messageDelta ?? ''))
      break
    case 'actions.requested':
      console.log('Actions requested:', event.data.actions)
      break
    case 'action.result':
      console.log('Action result:', event.data.result)
      break
    case 'session.waiting':
      console.log('Turn complete')
      break
  }
}

Reconnect after disconnect

The session stores the next stream index as it consumes events. To resume an existing stream, restore the session state and call stream():

const restored = client.session(savedSessionState)

for await (const event of restored.stream()) {
  console.log(event.type)
}

You can also override the replay point:

for await (const event of restored.stream({ startIndex: savedSessionState.streamIndex })) {
  console.log(event)
}

The streamIndex is owned by the server-side journal, ensuring consistent replay even though Flue uses its own stream offsets internally.

Authentication

For production, provide bearer or basic auth:

const client = new Client({
  host: 'https://api.example.com',
  auth: { bearer: process.env.EVE_AUTH_TOKEN ?? '' },
})

The token is sent as Authorization: Bearer <token> on every request, including reconnects.

Save and load session state

Persist session cursors to resume after reloads:

import { loadSessionState, saveSessionState } from 'flue-eve/client'

saveSessionState(localStorage, session.state)

const saved = loadSessionState(localStorage)
if (saved) {
  const resumed = new Client({ host: '' }).session(saved)
  await resumed.send('Continue from here')
}

See React for the hook wrapper that handles this pattern inside a browser UI.