UNPKG

@adonis-agora/agent

Version:

Governed, durable-backed AI agent (chat + tool-calling + governance) for AdonisJS — part of the Agora ecosystem.

100 lines • 4.29 kB
import { createHash } from 'node:crypto'; import { InMemoryOpenCodeSessionStore, } from './host.js'; import { openCodeLog } from './log.js'; import { OpenCodeMcpEndpoint, OpenCodeToolsTokens } from './mcp.js'; import { OpenCodeAgentRunner } from './runner.js'; import { OpenCodeTurns } from './turns.js'; const DEFAULT_TOOLS_TTL_MS = 7 * 24 * 60 * 60_000; async function resolve(value, context, what) { if (typeof value !== 'function') return value; if (context.make === undefined) { throw new Error(`[@adonis-agora/agent/opencode] the ${what} is a class, and there is no container to build it with: pass an instance.`); } return context.make(value); } /** * The OpenCode engine — `engine: openCode({ host })` in `config/agent.ts`. The routes, threads, * stream protocol, approvals, questions and queue stay the library's; OpenCode runs the model, the * tools, skills and the context, on the server and sessions the host provides. */ export class OpenCodeEngine { options; name = 'opencode'; durable = false; #turns; #endpoint; constructor(options) { this.options = options; } /** * The engine's turn steps, once the provider built the runner: `pushToSession` (trusted pushes * into a session's run) and `liveRuns()` for the host's own use. */ get turns() { if (this.#turns === undefined) { throw new Error('[@adonis-agora/agent/opencode] the engine has not been started yet.'); } return this.#turns; } /** The tools endpoint, when `tools` is set and the engine has started. */ get endpoint() { return this.#endpoint; } async createRunner(context) { const turns = await this.buildTurns(context); return new OpenCodeAgentRunner(turns, context.store); } adapters() { return this.#endpoint !== undefined ? [this.#endpoint.adapter()] : []; } shutdown() { this.#turns?.close(); } /** The host, the session store, the tools endpoint and the turn steps every OpenCode runner needs. */ async buildTurns(context) { const { host: hostOption, sessions: sessionsOption, ...settings } = this.options; const host = await resolve(hostOption, context, 'host'); const sessions = sessionsOption === undefined ? new InMemoryOpenCodeSessionStore() : await resolve(sessionsOption, context, 'session store'); let tokens; if (settings.tools !== undefined) { const secret = settings.tools.secret ?? (context.appKey !== undefined ? createHash('sha256').update(`opencode-tools:${context.appKey}`).digest('hex') : undefined); if (secret === undefined) { openCodeLog.warn('`tools` has no `secret` and the app has no APP_KEY: the endpoint signs its tokens with a per-process secret, so it only works with one process.'); } tokens = new OpenCodeToolsTokens(secret, settings.tools.ttlMs ?? DEFAULT_TOOLS_TTL_MS); } const turns = new OpenCodeTurns({ host, sessions, settings, factory: context.factory, store: context.store, sink: context.sink, registry: context.registry, queue: context.queue, ...(context.pricingStore !== undefined ? { pricingStore: context.pricingStore } : {}), ...(context.priceCatalog !== undefined ? { priceCatalog: context.priceCatalog } : {}), ...(tokens !== undefined ? { mintToolsToken: (actor, serverKey) => tokens.mint(actor, serverKey) } : {}), }); this.#turns = turns; this.#endpoint = tokens !== undefined ? new OpenCodeMcpEndpoint(turns, tokens) : undefined; return turns; } } /** * Run the agent's turns on OpenCode 2 — `engine: openCode({ host })` in `config/agent.ts`. In * memory: a turn parked on a person lives in this process (single replica); see * `openCodeDurable()` (`@adonis-agora/agent/opencode/durable`) for turns that survive restarts. */ export function openCode(options) { return new OpenCodeEngine(options); } //# sourceMappingURL=engine.js.map