Using Hono with Agentuity

Add Agentuity service clients and AI Gateway routing to a Hono app

Write normal Hono route handlers. Use direct Agentuity clients inside handlers by default, or add @agentuity/hono when middleware is the convenient place to share clients and telemetry across routes.

Create a Starter

npm create agentuity -- --name my-hono-api --framework hono --services keyvalue
cd my-hono-api

The starter includes a translation example in src/index.ts that uses AIGatewayClient for model calls. Adding --services keyvalue installs @agentuity/keyvalue and updates the starter checklist, but it does not add another route by itself.

Key files:

FilePurpose
src/index.tsHono app, API route, and server entry
src/landing.tsxRendered page shell for /
package.jsonscripts for dev, build, start, and deploy

For an existing Hono app, add the CLI and link the project:

npm install -D @agentuity/cli
npx agentuity project import --validate-only
npx agentuity project import

npm install -D installs the CLI as a project-local dev dependency, not a global install. Use npx to run that local CLI. For Bun, pnpm, and Yarn equivalents, see Local versus global CLI.

What Agentuity adds

AreaHono keepsAgentuity adds
routingnew Hono() and normal route handlersdirect service clients or @agentuity/hono middleware
local devyour Hono dev scriptagentuity dev supplies service credentials
buildyour Node-targeted build commandagentuity build runs the build and writes generic launch metadata
deploythe built server entryagentuity deploy ships the packaged app

Use Service Clients in Routes

Direct service clients work in any Hono handler. They read AGENTUITY_SDK_KEY (with AGENTUITY_CLI_KEY as a fallback) from the process environment, so agentuity dev can supply the linked project key for local runs.

Install what the example uses:

npm install hono @agentuity/aigateway @agentuity/keyvalue valibot

This route translates input text, persists the last five translations under a session cookie, and calls AI Gateway with the project SDK key. Use a provider SDK directly only when the provider-specific API is the point of the route.

typescriptsrc/index.ts
import { AIGatewayClient } from '@agentuity/aigateway';
import { KeyValueClient } from '@agentuity/keyvalue';
import { Hono } from 'hono';
import type { Context } from 'hono';
import { getCookie, setCookie } from 'hono/cookie';
import * as v from 'valibot';
 
const DEFAULT_MODEL = 'openai/gpt-5.4-mini';
const HISTORY_NAMESPACE = 'translation-history';
const SESSION_COOKIE = 'agentuity_session';
const SESSION_TTL_SECONDS = 60 * 60 * 24 * 30;
 
const requestSchema = v.object({
	model: v.optional(v.string()),
	text: v.string(),
	toLanguage: v.string(),
});
 
interface HistoryEntry {
	readonly model: string;
	readonly sessionId: string;
	readonly text: string;
	readonly timestamp: string;
	readonly toLanguage: string;
	readonly translation: string;
}
 
interface HistoryState {
	readonly history: readonly HistoryEntry[];
	readonly translationCount: number;
}
 
const kv = new KeyValueClient(); 
const gateway = new AIGatewayClient(); 
const app = new Hono();
 
function getSessionId(c: Context): string {
	const existing = getCookie(c, SESSION_COOKIE);
	if (existing) return existing;
 
	const sessionId = crypto.randomUUID();
	setCookie(c, SESSION_COOKIE, sessionId, {
		httpOnly: true,
		path: '/',
		sameSite: 'Lax',
		secure: new URL(c.req.url).protocol === 'https:',
		maxAge: SESSION_TTL_SECONDS,
	});
	return sessionId;
}
 
app.post('/api/translate', async (c) => {
	const body: unknown = await c.req.json();
	const parsed = v.safeParse(requestSchema, body);
	if (!parsed.success) {
		return c.json({ error: 'text and toLanguage are required' }, 400);
	}
 
	const input = parsed.output;
	const sessionId = getSessionId(c);
	const model = input.model ?? DEFAULT_MODEL;
	const result = await gateway.completeText({ 
		model,
		messages: [
			{ role: 'user', content: `Translate to ${input.toLanguage}:\n\n${input.text}` },
		],
	});
 
	if (!result.hasText) {
		return c.json({ error: 'model returned no text' }, 502);
	}
 
	const translation = result.text;
	const previous = await kv.get<HistoryState>(HISTORY_NAMESPACE, sessionId);
	const history = [
		...(previous.exists ? previous.data.history : []),
		{
			model,
			sessionId,
			text: input.text,
			timestamp: new Date().toISOString(),
			toLanguage: input.toLanguage,
			translation,
		},
	].slice(-5);
	const translationCount = (previous.exists ? previous.data.translationCount : 0) + 1;
 
	await kv.set(HISTORY_NAMESPACE, sessionId, { history, translationCount }, { ttl: SESSION_TTL_SECONDS }); 
 
	return c.json({ sessionId, model, toLanguage: input.toLanguage, translation, history, translationCount });
});
 
export default app;

KeyValueClient.get() returns a discriminated result: check result.exists before accessing result.data to keep the read path type-safe.

Use the model id exactly as it appears in the AI Gateway model catalog. The default example keeps the model in code, but you can move it to app configuration or request input.

Use @agentuity/hono for Shared Clients

When most routes need the same Agentuity clients, @agentuity/hono can initialize them once and expose them on c.var.*. Keep direct clients for a single route, one-off script, or code you want to move across frameworks. Use middleware when the app benefits from shared clients, telemetry defaults, and route-local access through c.var. If your unbundled dev runner has trouble resolving the telemetry package, keep direct clients in dev or bundle the server entry before using the middleware path.

npm install @agentuity/hono
typescriptsrc/index.ts
import { agentuity } from '@agentuity/hono';
import type { Logger, Services } from '@agentuity/hono';
import { Hono } from 'hono';
import * as v from 'valibot';
 
type Variables = Pick<Services, 'kv'> & {
	logger: Logger;
};
 
const app = new Hono<{ Variables: Variables }>();
 
app.use('*', agentuity()); 
 
app.post('/api/events', async (c) => {
	const body: unknown = await c.req.json();
	const parsed = v.safeParse(v.object({ title: v.string() }), body);
	if (!parsed.success) {
		return c.json({ error: 'title is required' }, 400);
	}
 
	const id = crypto.randomUUID();
	const event = { id, title: parsed.output.title, createdAt: new Date().toISOString() };
 
	await c.var.kv.set('events', id, event, { ttl: null }); 
	c.var.logger.info('event recorded', { id }); 
 
	return c.json(event, 201);
});
 
export default app;
Direct clients@agentuity/hono middleware
SetupInstantiate in the module that needs the clientOne app.use('*', agentuity())
ScopeWorks anywhere (scripts, workers, other frameworks)Hono apps only
TelemetryUse your existing setup or @agentuity/telemetry directlyAgentuity telemetry defaults through c.var.logger and c.var.tracer
Best forSingle route, script, or cross-framework moduleMost routes need multiple services

Run Locally

agentuity dev runs your package manager's dev script. When the linked project key is available, the CLI supplies AGENTUITY_SDK_KEY to the Hono process.

agentuity dev

Pass a custom script when the dev entry is named differently:

agentuity dev --script dev:api

Smoke-test before debugging the UI:

curl -X POST http://localhost:3000/api/translate \
  -H "content-type: application/json" \
  -d '{"text":"Hello","toLanguage":"Spanish"}'

Deployed Environment Variables

Configure the variables your deployed app needs. Direct AIGatewayClient calls use AGENTUITY_SDK_KEY; the model ID in this example is normal app configuration or request input.

VariableUsed by
AGENTUITY_SDK_KEYAgentuity service clients and @agentuity/aigateway
DATABASE_URLdatabase routes
AWS_*object storage routes; see Object Storage for exact variables

Validate Before Deploy

The current starter uses Hono's Node template and adds explicit build and start scripts so the deploy can launch a single process.

jsonpackage.json
{
	"scripts": {
		"build": "esbuild src/index.ts --bundle --platform=node --format=cjs --target=node22 --outfile=dist/src/index.cjs",
		"start": "node dist/src/index.cjs",
		"deploy": "agentuity deploy"
	}
}

Then validate the packaging. Hono apps are packaged through Agentuity's generic adapter, so the start script is the contract for what the deployed process runs.

npm run build
npx agentuity build
cat .agentuity/launch.json

Deploy only after .agentuity/launch.json points at the documented web process and a local packaged validation of / plus one API route passes:

agentuity deploy --confirm

Common Gotchas

SymptomCheck
Service client returns auth errorsRun with agentuity dev or set AGENTUITY_SDK_KEY
Model call returns an auth errorVerify AGENTUITY_SDK_KEY is available to the server process. See AI Gateway
Cookies not set or missing flagsImport getCookie/setCookie from hono/cookie; setting cookies directly on the response object bypasses Hono's helper
Route works in dev but not after buildRe-inspect .agentuity/launch.json after agentuity build. See Hono on Node.js for the adapter shape
Deploy starts the wrong fileConfirm scripts.start in package.json points at the built server entry (dist/src/index.cjs)
c.var.kv is undefinedAdd app.use('*', agentuity()) before any route that reads from c.var
c.var.* types are missingDeclare the Variables generic on new Hono<{ Variables: ... }>()

Framework Docs

  • Hono on Node.js: confirm the src/index.ts entry and @hono/node-server adapter setup
  • Hono Routing: use standard Hono routing helpers
  • Hono Cookies: configure getCookie, setCookie, and signed cookies

Next Steps

  • Agents: wrap model calls in reusable functions called from Hono routes
  • Chat and Streaming: return streamed model output from Hono routes
  • Key-Value Storage: store compact state by namespace and key
  • AI Gateway: route provider SDK calls through Agentuity
  • Observability: choose app-owned logging and tracing or Agentuity telemetry defaults