FluxyChat

How-to Guides

HarnessAgent — External Agent Runtime Wrapper

`HarnessAgent` wraps external agent runtimes (Claude Code, Codex, custom coding agents) behind a unified interface compatible with AI SDK stream types.

HarnessAgent — External Agent Runtime Wrapper

HarnessAgent wraps external agent runtimes (Claude Code, Codex, custom coding agents) behind a unified interface compatible with AI SDK stream types.

When to Use

  • You want to invoke an existing agent runtime (Claude Code, Codex) from your application
  • You need streaming output from an external coding agent
  • You want session management (create, detach, resume) for long-running agent interactions

Basic Usage

import { HarnessAgent, type HarnessAdapter } from '@fluxy-chat/agent';

// Create an adapter for your external runtime
const myAdapter: HarnessAdapter = {
  name: 'my-coding-agent',
  async createSession(sandbox) {
    return {
      id: crypto.randomUUID(),
      async destroy() { /* cleanup */ },
      async detach() { return { sessionId: this.id, state: null, createdAt: Date.now() }; },
    };
  },
  async generate(options) {
    // Call your external agent
    return {
      text: 'Agent response',
      finishReason: 'stop',
      usage: { inputTokens: 100, outputTokens: 50 },
      steps: [],
      responseMessages: [],
    };
  },
  async stream(options) {
    // Return streaming result from your agent
    const result = await this.generate(options);
    return {
      stream: new ReadableStream({ ... }),
      result: Promise.resolve(result),
      text: Promise.resolve(result.text),
    };
  },
};

const agent = new HarnessAgent(myAdapter);
const result = await agent.generate({ prompt: 'Refactor the auth module' });

Session Management

Create a session for stateful multi-turn interactions:

const session = await agent.createSession(/* sandbox config */);

try {
  const result = await agent.generate({
    session,
    prompt: 'List all files in the project',
  });
  console.log(result.text);

  // Resume the session for follow-up
  const followUp = await agent.generate({
    session,
    prompt: 'Now refactor the main entry point',
  });
} finally {
  await session.destroy();
}

Streaming

stream() returns a HarnessStreamResult with AI SDK-compatible stream parts:

const sr = agent.stream({ prompt: 'Analyze the codebase' });

// Consume the stream
const reader = sr.stream.getReader();
for await (const part of /* async iteration */) {
  if (part.type === 'text-delta') process.stdout.write(part.delta);
}

// Or get the final result
const result = await sr.result;
console.log(result.text, result.usage);

Adapter Pattern

The HarnessAdapter interface lets you wrap any external runtime:

interface HarnessAdapter {
  readonly name: string;
  createSession(sandbox?: unknown): Promise<HarnessSession>;
  generate(options: {
    prompt: string;
    system?: string;
    tools?: Record<string, AITool>;
    maxSteps?: number;
    signal?: AbortSignal;
    session?: HarnessSession;
  }): Promise<HarnessGenerateResult>;
  stream(options: {
    prompt: string;
    system?: string;
    tools?: Record<string, AITool>;
    maxSteps?: number;
    signal?: AbortSignal;
    session?: HarnessSession;
  }): Promise<HarnessStreamResult>;
}

Type Reference

class HarnessAgent {
  readonly adapter: HarnessAdapter;
  constructor(adapter: HarnessAdapter);
  createSession(sandbox?: unknown): Promise<HarnessSession>;
  generate(options: HarnessGenerateOptions): Promise<HarnessGenerateResult>;
  stream(options: HarnessStreamOptions): HarnessStreamResult;
}

interface HarnessSession {
  readonly id: string;
  destroy(): Promise<void>;
  detach(): Promise<HarnessSessionResumeState>;
}

interface HarnessGenerateResult {
  text: string;
  reasoningText?: string;
  finishReason: AIFinishReason;
  usage: AIUsage;
  steps: readonly HarnessStepResult[];
  responseMessages: readonly HarnessMessage[];
}

interface HarnessStreamResult {
  stream: ReadableStream<AIStreamPart>;
  result: Promise<HarnessGenerateResult>;
  text: Promise<string>;
}

See Also

On this page