Using Astro with Agentuity

Add Agentuity service clients, local development, and deploy validation to an Astro app

Start with a normal Astro app. Agentuity adds service clients, local development support, and deploy validation around the file-based routing and build output you already use.

Create a Starter

npm create agentuity -- --name my-astro-app --framework astro
cd my-astro-app

The starter calls create-astro under the hood, then adds @agentuity/cli, installs @astrojs/node, adds a deploy script, and overlays an AI example using src/pages/api/translate.ts.

Key files in the AI example:

FilePurpose
src/pages/index.astroPage UI that calls the API route
src/pages/api/translate.tsAPI route that calls AI Gateway through the starter helper
astro.config.mjsAdapter and output mode config
package.jsondev, build, and deploy scripts pre-wired

Import an Existing App

For an existing Astro app, add the CLI and register the project:

npm install -D @agentuity/cli
npx agentuity project import --validate-only   # dry run: checks config only
npx agentuity project import                   # registers the project and writes agentuity.json

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.

agentuity project import writes the local project metadata that agentuity deploy expects. Run it once before the first deploy. After that, agentuity dev runs the Astro dev script with the Agentuity environment values your app needs.

What Agentuity adds

AreaAstro keepsAgentuity adds
routingsrc/pages/api/*.ts endpoint filesservice clients imported in server endpoints
local devastro devagentuity dev supplies service credentials
buildAstro server output and adapter configagentuity build runs the build and packages launch metadata
deploythe Node adapter server entryagentuity deploy ships the packaged app

Add an API Route

Astro endpoint files in src/pages/api/ export named functions per HTTP method. Put Agentuity service clients here; never import them from client-side <script> blocks.

The route below uses AIGatewayClient for the model call. The project SDK key authenticates the request, and the model ID is app configuration or request input.

npm install @agentuity/aigateway @agentuity/keyvalue arktype
typescriptsrc/pages/api/translate.ts
import { AIGatewayClient } from '@agentuity/aigateway';
import { KeyValueClient } from '@agentuity/keyvalue';
import { type } from 'arktype';
import type { APIRoute } from 'astro';
 
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;
 
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 requestSchema = type({
  'model?': 'string',
  text: 'string',
  toLanguage: 'string',
});
 
// Instantiated once at module level; reads AGENTUITY_SDK_KEY from env
const kv = new KeyValueClient(); 
const gateway = new AIGatewayClient(); 
 
function getSessionId(cookies: Parameters<APIRoute>[0]['cookies']): string {
  const existing = cookies.get(SESSION_COOKIE)?.value;
  const sessionId = existing ?? crypto.randomUUID();
 
  // Astro.cookies.set writes the Set-Cookie header for the response
  cookies.set(SESSION_COOKIE, sessionId, {
    httpOnly: true,
    maxAge: SESSION_TTL_SECONDS,
    path: '/',
    sameSite: 'lax',
    secure: import.meta.env.PROD,
  });
 
  return sessionId;
}
 
export const POST: APIRoute = async ({ cookies, request }) => {
  const body: unknown = await request.json();
  const parsed = requestSchema(body);
 
  if (parsed instanceof type.errors) {
    return new Response(JSON.stringify({ error: 'text and toLanguage are required' }), {
      status: 400,
      headers: { 'Content-Type': 'application/json' },
    });
  }
 
  const input = parsed;
  const model = input.model ?? DEFAULT_MODEL;
  const sessionId = getSessionId(cookies);
  const result = await gateway.completeText({ 
    model,
    messages: [
      {
        role: 'user',
        content: `Translate to ${input.toLanguage}:\n\n${input.text}`,
      },
    ],
  });
 
  if (!result.hasText) {
    return new Response(JSON.stringify({ error: 'model returned no text' }), {
      status: 502,
      headers: { 'Content-Type': 'application/json' },
    });
  }
 
  const translation = result.text;
 
  // Append to the last 5 entries stored for this session
  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 new Response(JSON.stringify({ translation, history, model, translationCount }), {
    headers: { 'Content-Type': 'application/json' },
  });
};

KeyValueClient reads AGENTUITY_SDK_KEY from the environment. Run the app via agentuity dev so the linked project key is available to endpoints.

Use the model id exactly as it appears in the AI Gateway model catalog. Use a provider SDK directly only when the endpoint depends on provider-specific APIs.

Call the Route from a Page

The .astro page calls the server route from a client-side <script> block:

astrosrc/pages/index.astro
---
// Server-side frontmatter: runs at request time (or build time for static pages)
const title = 'Agentuity + Astro';
---
 
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>{title}</title>
  </head>
  <body>
    <textarea id="text" rows="4">Hello</textarea>
    <input id="lang" value="Spanish" placeholder="Target language" />
    <button id="btn" type="button">Translate</button>
    <p id="result"></p>
    <ul id="history"></ul>
 
    <script>
      // Typed lookups: querySelector with a generic narrows the result and
      // returns null if missing, so we early-return instead of asserting
      const btn = document.querySelector<HTMLButtonElement>('#btn');
      const result = document.querySelector<HTMLParagraphElement>('#result');
      const historyList = document.querySelector<HTMLUListElement>('#history');
      const textInput = document.querySelector<HTMLTextAreaElement>('#text');
      const langInput = document.querySelector<HTMLInputElement>('#lang');
 
      if (!btn || !result || !historyList || !textInput || !langInput) {
        throw new Error('Translate UI is missing required elements');
      }
 
      btn.addEventListener('click', async () => {
      const response = await fetch('/api/translate', { 
          method: 'POST',
          headers: { 'content-type': 'application/json' },
          body: JSON.stringify({ text: textInput.value, toLanguage: langInput.value }),
        });
 
        const data: {
          translation: string;
          history: Array<{ text: string; translation: string; toLanguage: string }>;
        } = await response.json();
        result.textContent = data.translation;
        historyList.replaceChildren(
          ...data.history.map((entry) => {
            const li = document.createElement('li');
            li.textContent = `${entry.text} โ†’ ${entry.translation}`;
            return li;
          })
        );
      });
    </script>
  </body>
</html>

Run Locally

Run through Agentuity. When the linked project key is available, the CLI supplies AGENTUITY_SDK_KEY to the Astro process:

agentuity dev

Smoke-test the API route:

curl http://localhost:4321/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 endpoints
AWS_*object storage endpoints; see Object Storage for exact variables

Validate Before Deploy

Astro requires a server adapter for routes that must render on demand. The starter uses @astrojs/node in standalone mode. Install it either via the Astro CLI or directly:

# Option A: automated (recommended for new projects)
npx astro add node
 
# Option B: manual
npm install @astrojs/node

The explicit astro.config.mjs the starter ships with:

javascriptastro.config.mjs
// @ts-check
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
 
export default defineConfig({
  output: 'server',   // all routes render on demand; opt individual pages out with export const prerender = true
  adapter: node({
    mode: 'standalone', // produces dist/server/entry.mjs, a self-contained Node server
  }),
});

Then build and inspect the launch metadata. Run npm run build when you want Astro-only feedback; agentuity build runs the framework build during packaging and writes .agentuity/launch.json.

npm run build
npx agentuity build
cat .agentuity/launch.json   # verify processes[0].command runs dist/server/entry.mjs

Deploy only after the framework build, Agentuity build, and a local packaged validation of / and one endpoint pass:

agentuity deploy --confirm

Common Gotchas

SymptomCheck
API route works in dev but 404s after deployConfirm output: 'server' is set and the Node adapter is installed
Endpoint returns static HTML, not JSONAPI route files need output: 'server'; without it, Astro prerenders them at build time. See on-demand rendering
launch.json points at _serve.jsThe packaged output is static-only and will not serve src/pages/api/*.ts endpoints
Service client returns auth errorsRun with agentuity dev or set AGENTUITY_SDK_KEY manually
Model call returns an auth errorVerify AGENTUITY_SDK_KEY is available to the endpoint process. See AI Gateway
Service client code appears in the browser bundleKeep KeyValueClient and AIGatewayClient in src/pages/api/*.ts, never in client <script> blocks
Cookies do not persist across requestsPass path: '/' when setting cookies; Astro's cookies.set defaults differ from browser document.cookie

Framework Docs

Next Steps