Node SDK: Agent Integration

This guide covers patterns for integrating Keito with AI agents in TypeScript/JavaScript applications.

The Agent Session Pattern

The most common pattern: start a timer when the agent begins work, stop it when done, and log an LLM expense for token costs.

import { Keito } from '@keito/sdk';

const keito = new Keito();

// 1. Start a running timer
const entry = await keito.timeEntries.create({
  project_id: 'prj_abc',
  task_id: 'tsk_001',
  spent_date: new Date().toISOString().split('T')[0],
  hours: 0,
  is_running: true,
  source: 'agent',
  metadata: {
    agent_id: 'review-bot-01',
    agent_type: 'claude-code',
    session_id: crypto.randomUUID(),
    model: 'claude-opus-4-6',
  },
});

// 2. Agent does work...
const result = await myAgent.run('Review the authentication module');

// 3. Stop the timer
await keito.timeEntries.update(entry.id, {
  hours: 1.5,
  is_running: false,
  notes: 'Reviewed and refactored auth module',
});

// 4. Log LLM cost
await keito.expenses.create({
  project_id: 'prj_abc',
  expense_category_id: 'cat_llm_usage',
  spent_date: new Date().toISOString().split('T')[0],
  units: 40,
  notes: 'claude-opus-4-6: 25k input + 15k output',
  source: 'agent',
  metadata: {
    agent_id: 'review-bot-01',
    session_id: entry.metadata.session_id,
    model: 'claude-opus-4-6',
    input_tokens: 25000,
    output_tokens: 15000,
  },
});

The Wrapper Helper

For repeated agent calls, use a wrapper that handles tracking automatically:

async function trackAgentWork<T>(
  keito: Keito,
  config: {
    projectId: string;
    taskId: string;
    agentId: string;
    model: string;
  },
  work: () => Promise<{
    result: T;
    inputTokens: number;
    outputTokens: number;
  }>,
): Promise<T> {
  const sessionId = crypto.randomUUID();
  const today = new Date().toISOString().split('T')[0];

  const entry = await keito.timeEntries.create({
    project_id: config.projectId,
    task_id: config.taskId,
    spent_date: today,
    hours: 0,
    is_running: true,
    source: 'agent',
    metadata: {
      agent_id: config.agentId,
      session_id: sessionId,
      model: config.model,
    },
  });

  const startTime = Date.now();

  try {
    const { result, inputTokens, outputTokens } = await work();
    const hours = (Date.now() - startTime) / 3_600_000;

    await keito.timeEntries.update(entry.id, {
      hours: Math.round(hours * 100) / 100,
      is_running: false,
    });

    const totalTokensK = (inputTokens + outputTokens) / 1000;
    await keito.expenses.create({
      project_id: config.projectId,
      expense_category_id: 'cat_llm_usage',
      spent_date: today,
      units: totalTokensK,
      source: 'agent',
      metadata: {
        agent_id: config.agentId,
        session_id: sessionId,
        model: config.model,
        input_tokens: inputTokens,
        output_tokens: outputTokens,
      },
    });

    return result;
  } catch (err) {
    await keito.timeEntries.delete(entry.id);
    throw err;
  }
}

Metadata Best Practices

  • Always include agent_id and session_id.
  • Use session_id to correlate time entries with their LLM expenses.
  • Keep metadata under 4KB.
  • Include model for cost analysis across different LLMs.
  • Don’t store sensitive data in metadata — it’s visible in reports.