gadget/packages/ai
Rob Colbert 00969cf9bc feat: token economics — real API token extraction, stats propagation, and context window fuel gauge
Phase 1: OpenAI API Token Extraction
- Add stream_options: { include_usage: true } to all streaming API calls
- Capture chunk.usage from final streaming chunks and response.usage from non-streaming
- Extend OpenAiChatIterationResult with optional usage field
- Update buildStats() to accept and return real token counts from usage data
- Wire iteration.usage through chat() to both buildStats() call sites

Phase 2: Agent Loop Stats Propagation
- Rename inputTokens/outputTokens to masterInputTokens/masterOutputTokens, add masterThinkingTokens
- Accumulate response.stats.tokenCounts after each master AI call
- Delete all Math.ceil(length/4) crude approximations (master and subagent loops)
- Track startTime/durationMs and emit IWorkOrderCompleteStats with workOrderComplete
- Subagent loop uses response.stats?.tokenCounts instead of Math.ceil

Phase 3: Database Model Changes
- Add contextWindowUsage field to IChatSession, ChatSessionSchema, and frontend ChatSession
- Initialize contextWindowUsage: 0 on session creation

Phase 4: Persist Stats on Turn Completion
- drone-session: accept IWorkOrderCompleteStats, persist turn stats, walk subagent records for aggregate
- drone-session: $inc session stats and contextWindowUsage, add formatDurationLabel() helper
- code-session: accept and forward stats, update in-memory session stats
- message-queue: Redis replay handles 4th stats arg
- Update WorkOrderCompleteMessage type in @gadget/api to accept stats parameter

Phase 5: UI — Context Window Fuel Gauge
- Add contextWindowUsage prop to SessionPanel
- Add fuel gauge bar with E→F labels, green/yellow/red zones, token count display
- Visible for ALL provider types (not gated by apiType)

Phase 6: Frontend Streaming State
- Add IWorkOrderCompleteStats interface to frontend api.ts
- handleWorkOrderComplete accepts stats, updates turn stats and session contextWindowUsage
- Pass contextWindowUsage prop to SessionPanel
2026-05-18 14:36:36 -04:00
..
src feat: token economics — real API token extraction, stats propagation, and context window fuel gauge 2026-05-18 14:36:36 -04:00
types created by merging gadget-code and gadget-drone 2026-04-28 09:20:37 -04:00
.gitignore cleanup 2026-05-07 00:59:15 -04:00
package.json streaming response fixes (Ollama) 2026-05-08 02:02:17 -04:00
README.md more documentation and progress working towards a usable socket protocol 2026-04-29 15:23:03 -04:00
tsconfig.json created by merging gadget-code and gadget-drone 2026-04-28 09:20:37 -04:00
vitest.config.ts streaming response fixes (Ollama) 2026-05-08 02:02:17 -04:00

@gadget/ai

Gadget Code's AI API abstraction layer. Provides a single internal API contract for calling AI providers (Ollama, OpenAI) without consumer code knowing which provider is configured.

Principles

  1. One interface, all providers. Consumer code calls createAiApi() once and holds the resulting AiApi. It never checks provider.sdk again.
  2. All AI SDK knowledge is contained here. No consumer imports ollama or openai SDKs directly.
  3. Responses are normalized. All provider responses are translated to Gadget Code's internal interface types before returning.

Usage

import { createAiApi } from "@gadget/ai";

const provider = {
  _id: "local-ollama",
  name: "Local Ollama",
  sdk: "ollama",
  baseUrl: "http://localhost:11434",
  apiKey: "",
};

const modelConfig = {
  provider,
  modelId: "llama3.2",
  params: {
    reasoning: false,
    temperature: 0.8,
    topP: 0.9,
    topK: 40,
  },
};

const ai = createAiApi(provider, logger);

const result = await ai.generate(modelConfig, {
  prompt: "Explain what this code does",
  systemPrompt: "You are a code reviewer.",
});
console.log(result.response);
console.log(result.stats.duration.text); // formatted, e.g. "00:00:02"

API

Factory

createAiApi(provider, logger?) — Returns an AiApi instance for the given provider. logger is optional and defaults to a no-op logger. Pass your own logger to receive debug output.

AiApi

Abstract base class. Currently implemented:

  • OllamaAiApi — Ollama provider
  • OpenAiApi — OpenAI provider (stubbed)

ai.generate(model, options, streamCallback?)

Single-prompt generation. Returns IAiGenerateResponse.

ai.chat(model, options, streamCallback?)

Chat with conversation history. Pass options.context for multi-turn对话. Returns IAiChatResponse.

Interfaces

All interfaces are exported for use by consumers:

  • IAiProvider — AI provider configuration
  • IAiModelConfig — Model + runtime parameters
  • IAiGenerateOptions / IAiGenerateResponse
  • IAiChatOptions / IAiChatResponse — includes tool_calls for function-calling models
  • IAiInferenceStats — token counts and duration (both raw seconds number and formatted text string)
  • IAiLogger — injectable logger interface (debug, info, warn, error)

Providers

Ollama

Configured via IAiProvider with sdk: "ollama". Uses the ollama npm package. Handles streaming responses and normalizes Ollama-specific response fields (thinking tokens, token counts, duration).

OpenAI

Configured via IAiProvider with sdk: "openai". Stubbed — chat() and generate() throw "Not yet implemented". Implement by wiring the openai npm package following the same pattern as OllamaAiApi.

Duration Formatting

The library uses numeral to provide a consistent formatted duration string (stats.duration.text) in hh:mm:ss format. The raw nanosecond value is also returned in stats.duration.seconds for consumers that need the raw number.

Adding a New Provider

  1. Create packages/ai/src/<provider>.ts — extend AiApi, implement all abstract methods
  2. Update packages/ai/src/index.ts — add the new class to the createAiApi factory switch
  3. Update this README

No consumer code changes required.