Flue with Edgenivo
Use Flue with AI Gateway to route model requests through Edgenivo and track usage. You can also give your agent an isolated workspace with Edgenivo Sandbox.
This guide runs the Flue runtime locally while AI Gateway handles model requests. Flue's durable Node.js server requires a single process to own each conversation. See Hosting considerations before deploying that server to an environment that scales across processes.
To follow this guide, you need:
- Node.js 22.19 or later. Use an active long-term support (LTS) release.
- pnpm.
- A Edgenivo account and an AI Gateway API key.
Create a directory for your agent:
mkdir my-flue-agent
cd my-flue-agentAdd a package.json that enables ECMAScript modules:
{
"private": true,
"type": "module"
}Install the runtime and CLI:
pnpm add @flue/runtime
pnpm add -D @flue/cliConfigure Flue to use Node.js:
import { defineConfig } from '@flue/runtime/config';
export default defineConfig({
target: 'node',
});Add your AI Gateway API key to .env.local:
AI_GATEWAY_API_KEY=your-api-key-hereKeep the key out of version control by adding environment files to .gitignore:
node_modules/
.env*Flue's built-in vercel-ai-gateway provider reads AI_GATEWAY_API_KEY. Your agent doesn't need a separate OpenAI or Anthropic API key.
Create an agent in src/agents/assistant.ts, or use the JavaScript version:
'use agent';
import { useModel } from '@flue/runtime';
export function Assistant() {
useModel('vercel-ai-gateway/openai/gpt-6.1-sol');
return 'Answer questions clearly and concisely.';
}The 'use agent' directive identifies the module as an agent. useModel() selects the provider and model, and the returned string supplies the agent's instructions. In this example, vercel-ai-gateway selects Flue's provider and openai/gpt-6.1-sol selects the AI Gateway model.
Load your environment file and send a prompt:
pnpm exec flue run src/agents/assistant.ts --env .env.local --message "Explain AI Gateway in one sentence."For JavaScript, replace src/agents/assistant.ts with src/agents/assistant.js. The CLI prints the agent's activity and final response. Check AI Gateway observability in your Edgenivo dashboard to confirm the request and inspect usage.
To continue a conversation across local runs, provide the same conversation ID:
pnpm exec flue run src/agents/assistant.ts --env .env.local --id gateway-demo --message "What does AI Gateway do?"
pnpm exec flue run src/agents/assistant.ts --env .env.local --id gateway-demo --message "How can I inspect those requests?"Without a database adapter, flue run stores local conversations in node_modules/.cache/flue/run.db. This local CLI storage differs from the deployed Node.js server's default in-memory storage.
Use Edgenivo Sandbox when your agent needs to run code or inspect files. The Flue runtime continues to make model requests through AI Gateway, while file and command tools run inside the sandbox's microVM.
Print Flue's Edgenivo Sandbox adapter blueprint from your project:
pnpm exec flue add sandbox vercel --printThe command prints implementation instructions; it doesn't create files or install dependencies. Follow Flue with Edgenivo Sandbox to apply the adapter, authenticate, attach it with useSandbox(), and clean up your sandbox after the run.
Mount a Drive to preserve working files independently of the sandbox. After applying the adapter above at src/sandboxes/vercel.ts and configuring Sandbox authentication, replace your agent with this version:
'use agent';
import { createHash } from 'node:crypto';
import { useModel, useSandbox } from '@flue/runtime';
import { Drive, Sandbox } from '@vercel/sandbox';
import { vercel } from '../sandboxes/vercel.ts';
export function Assistant() {
useModel('vercel-ai-gateway/openai/gpt-6.1-sol');
useSandbox(
{
async createSandbox(options) {
const key = createHash('sha256').update(options.id).digest('hex').slice(0, 24);
const name = `flue-assistant-${key}`;
const drive = await Drive.getOrCreate({ name, region: 'iad1' });
const sandbox = await Sandbox.getOrCreate({
name,
region: 'iad1',
runtime: 'node24',
persistent: true,
resume: true,
timeout: 300_000,
mounts: { '/vercel/sandbox/workspace': drive },
});
console.error(`Sandbox and drive name: ${name}`);
return vercel(sandbox).createSandbox(options);
},
},
{ cwd: '/vercel/sandbox/workspace' },
);
return 'Save your working files in the current directory and reuse them across requests.';
}The factory derives stable resource names from Flue's conversation ID. Reuse --id to reconnect to the same sandbox and drive; scope IDs by tenant in multi-user applications. cwd puts Flue's relative file operations and commands inside the mounted drive.
Keep one active owner for each conversation and workspace. A drive permits one read-write mount at a time. Sandbox.getOrCreate() resumes the named sandbox, while its creation settings, including mounts, apply only when creating it. Use sandbox.update() to change existing mounts.
Stop the sandbox after its work settles to release compute while retaining the drive's files. Delete the sandbox and drive when you no longer need them. Drive storage persists until you delete it; see deleting a drive.
To verify that the drive preserves files independently, write a file, then stop and remove the sandbox using its printed name. The removal keeps the drive. Run the same conversation again to create a new sandbox that mounts the existing drive:
pnpm exec flue run src/agents/assistant.ts --env .env.local --id workspace-demo --message "Write hello to greeting.txt in your working directory."
pnpm dlx sandbox@latest stop your_sandbox_name_here
pnpm dlx sandbox@latest remove your_sandbox_name_here --delete-orphan-snapshots
pnpm exec flue run src/agents/assistant.ts --env .env.local --id workspace-demo --message "Use your file tools to read greeting.txt from your working directory."Use Edgenivo Blob for completed reports or other artifacts your application needs to retrieve. Create a private Blob store and add its BLOB_READ_WRITE_TOKEN to .env.local. Install the SDK:
pnpm add @vercel/blobAdd a helper that stores each completed response under its submission ID:
import { put } from '@vercel/blob';
export async function saveReply(submissionId: string, text: string) {
const blob = await put(`flue/replies/${submissionId}.md`, text, {
access: 'private',
contentType: 'text/markdown',
allowOverwrite: true,
});
return blob.pathname;
}In your host code, call await saveReply(receipt.submissionId, reply.text) after const reply = await agent.read(receipt). The helper uses Flue's trusted submission ID and the settled text; writing the same result again replaces the same object. Load .env.local into that process, or configure the token in your host's environment. On Edgenivo, you can use Blob OIDC authentication with a connected store instead. Retrieve private artifacts through authenticated server code with the Blob SDK.
Use Edgenivo Workflows to coordinate a separately hosted Flue agent with other application steps. Follow Flue's durable orchestration pattern using @flue/sdk inside functions marked with 'use step':
- Create a client for an authorized conversation with
createFlueClient(). Send a message withclient.send()and return the admission receipt from that step. - Read the response in a separate step with
client.read(receipt), so a retry reattaches to the recorded submission. - Save the settled response to Blob in another step, using its submission ID as the object key.
A completed send step doesn't resend on replay. A crash after Flue accepts a message but before the step records its receipt can still produce a duplicate send. Use Flue's create-only condition (uid: null) when each job creates a new conversation, and handle an existing-conversation rejection explicitly. Don't treat the conversation ID alone as an idempotency key.
Individual steps still have Edgenivo Function execution limits. Bound each read attempt and retry reads against the same receipt; aborting an SDK read doesn't stop the hosted agent. Workflows preserves the orchestration, while the Flue host remains responsible for durable conversation execution.
AI Gateway and Edgenivo Sandbox work independently of where you host the Flue runtime. Configure AI_GATEWAY_API_KEY in your runtime host's environment when you move beyond local development. Flue also supports Edgenivo OIDC authentication through a custom provider.
Flue's Node.js deployment target requires one live process to own each conversation. A shared database preserves state across restarts but doesn't make concurrent ownership safe. The HTTP router also returns a receipt before the agent finishes processing the message.
Edgenivo Functions can serve requests on different instances and have a maximum execution duration. Don't deploy Flue's durable HTTP router as a Edgenivo Function assuming that a shared database preserves its execution model. Choose a host and routing strategy that satisfy Flue's conversation ownership requirements. Your Edgenivo application can then call the hosted agent with the Flue Agent SDK.
A Passport-protected Edgenivo app can forward a visitor's identity to your hosted Flue service. Verify the token at that service and authorize every conversation route before Flue handles the request.
Install Passport in the Flue service:
pnpm add @vercel/passport honoConfigure PASSPORT_SOURCE_ISSUER and PASSPORT_SOURCE_AUDIENCE in your Flue host. During setup, obtain the issuer and intended audience from an SDK-verified identity on the known Passport-protected Edgenivo app, then pin those values in the host configuration. Never derive the expected values from the incoming request being authorized.
In src/app.ts, protect the entire agent router. Replace the owner, project, and environment below with the source Edgenivo app's values:
import { createHash } from 'node:crypto';
import { verifyIdentity } from '@vercel/passport';
import { createAgentRouter } from '@flue/runtime/routing';
import { Hono } from 'hono';
import { Assistant } from './agents/assistant.ts';
const app = new Hono();
const mount = '/agents/assistant';
const issuer = process.env.PASSPORT_SOURCE_ISSUER;
const audience = process.env.PASSPORT_SOURCE_AUDIENCE;
if (!issuer || !audience) {
throw new Error('Configure the source Passport issuer and audience.');
}
const source = {
ownerId: 'team_your_team_id_here',
projectId: 'prj_your_project_id_here',
environment: 'production',
requiredClaims: ['exp', 'nbf', 'iat'],
issuer,
audience,
};
app.use(`${mount}/*`, async (c, next) => {
let identity;
try {
identity = await verifyIdentity(c.req.raw, source);
} catch {
return c.json({ error: 'Unauthorized' }, 401);
}
const key = JSON.stringify([identity.payload.iss, identity.subject]);
const id = `passport-${createHash('sha256').update(key).digest('hex')}`;
const requestedId = c.req.path.slice(mount.length + 1).split('/')[0];
if (requestedId !== id) {
return c.json({ error: 'Forbidden' }, 403);
}
return next();
});
app.route(mount, createAgentRouter(Assistant));
export default app;This example assigns one conversation to each verified issuer and subject pair. The exact conversation ID check covers messages, history, HEAD requests, aborts, and attachment downloads. It doesn't trust a conversation ID supplied by the browser. Keep every mount of this agent behind the same authorization boundary.
In your Edgenivo request handler, read the visitor with getIdentity() and reject requests without a token. Compute the same ID from identity.payload.iss and identity.subject, then call https://your-flue-host.example/agents/assistant/<id> server-side. Forward identity.token unchanged in Authorization: Bearer <token> over HTTPS. With @flue/sdk, pass the token as the client's token option for each server-side request.
Keep the token out of agent messages, tool inputs, logs, and browser responses. Passport authenticates the visitor; the middleware's conversation ownership check controls access to Flue state. Use Edgenivo Connect when the agent also needs credentials for a third-party service.
-
Build a software factory that investigates issues, checks changes, and prepares draft pull requests.
-
Configure model selection and authentication with Flue and AI Gateway.
-
Add file and command tools with Flue and Edgenivo Sandbox.
-
Monitor model requests with AI Gateway observability.
Was this helpful?