Skip to content

TypeScript SDK

@koda-rising/sdk is the official TypeScript client — the same one the CLI and VS Code extension use. It’s a thin, typed wrapper over the HTTP API. Any language can call the REST API directly (see /openapi.json); this SDK is the ergonomic path for TypeScript/Node.

  • Runtime: Node 18+ (global fetch + web streams). Works in Bun/Deno.
  • Package: @koda-rising/sdk.
import { KodaClient } from '@koda-rising/sdk';
const koda = new KodaClient(
'https://your-gateway.example.com', // base URL (no trailing path)
process.env.KODA_TOKEN!, // an API key — Authorization: Bearer <key>
process.env.KODA_ORG, // optional org id (sent as the x-org-id header)
);

Create a key from the dashboard, the CLI (koda keys create --scopes agent:run,wallet:read), or createKey below. Keys carry scopes: agent:run, wallet:read, keys:manage, metrics:read. The SDK is for servers, CI, and scripts — never ship a key in client-side code.

import { minorToNaira } from '@koda-rising/sdk';
const me = await koda.me(); // { userId, orgId, role, balanceMinor, scopes, tier, currency, allowedLanes, ... }
console.log(`Balance: ${me.currency?.symbol}${minorToNaira(me.balanceMinor)}`);
const { balanceMinor } = await koda.getWallet();
const intent = await koda.topup(5000); // ₦5,000; { reference, amountMinor, authorizationUrl?, ussdCode? }
if (intent.authorizationUrl) redirectUser(intent.authorizationUrl);
await koda.topup(3000, { channel: 'mpesa', phone: '2547XXXXXXXX' }); // cardless rails take a channel (+ phone)
const v = await koda.verifyTopup(intent.reference); // { credited, status, balanceMinor? }

agentRun and agentRunLoop are async generators — iterate them to consume events as they arrive.

for await (const ev of koda.agentRun('Add a null check to parseUser()', [
{ path: 'src/user.ts', content: currentSource },
])) {
if (ev.decision) console.log(`lane=${ev.decision.lane} model=${ev.decision.model}`);
if (ev.plan) console.log(ev.plan);
if (ev.edits) applyEdits(ev.edits); // FileEdit[] — { path, action, content?, description? }
if (ev.review?.findings.length) console.warn(ev.review.findings); // fintech code-quality findings
if (ev.summary) console.log(ev.summary, `cost=${ev.costMinor} balance=${ev.balanceMinor}`);
}

Each AgentEvent may carry: decision, plan, edits, summary, costMinor, balanceMinor, chunk, review.

Runs plan→edit→verify→retry server-side, metered per iteration. Pass a sandbox + testCmd to actually run the project’s tests between turns (use docker in production; local needs the operator opt-in and is single-tenant only).

for await (const ev of koda.agentRunLoop('Make the failing test pass', context, {
sandbox: 'docker', testCmd: 'npm test', maxIters: 4,
})) {
if (ev.step) console.log(`iter ${ev.step.iteration}: ${ev.step.edits} edits, ok=${ev.step.ok}`);
if (ev.turn) console.log(` ${ev.turn.lane}/${ev.turn.model} +${ev.turn.costMinor} (${ev.turn.reason})`);
if (ev.verified !== undefined) console.log(`done: verified=${ev.verified} in ${ev.iterations} iters, total=${ev.totalCostMinor}`);
if (ev.error) throw new Error(ev.error);
}
await koda.repoIndex([{ path: 'src/pay.ts', content }]); // { indexed, chunks, costMinor, balanceMinor } — vector mode
const status = await koda.repoStatus(); // { enabled, chunks, paths }
await koda.repoClear('src/pay.ts'); // one path, or repoClear() for all
const key = await koda.createKey('ci-token', ['agent:run', 'wallet:read']); // { id, key, prefix } — key shown ONCE
await koda.listKeys(); await koda.revokeKey(key.id);
const team = await koda.createTeam('Acme Eng'); // { orgId, walletId }
await koda.addMember(team.orgId, { email: 'dev@acme.io', role: 'member', capNaira: 20000 }); // ₦20k monthly cap
await koda.listMembers(team.orgId);
  • Insufficient balance — agentRun/agentRunLoop throw Error('insufficient balance — top up to continue') on a 402. Catch it and prompt a top-up.
  • Other non-2xx throw Error('agent <status>') (streaming) or reject with the gateway’s { error } message.
  • Idempotency — pass { idempotencyKey } to make a retried call exactly-once.
  • Low bandwidth — { lowBandwidth: true } pins the cheap open lane and trims context.
try {
for await (const ev of koda.agentRun(prompt, ctx, { idempotencyKey: reqId, lowBandwidth: true })) { /* … */ }
} catch (e) {
if (String(e).includes('insufficient balance')) await promptTopUp();
else throw e;
}

The API is plain HTTP + SSE. Point any OpenAPI generator at /openapi.json for a typed client, or call the routes directly — see the API reference and the interactive docs at /docs.