#!/usr/bin/env -S npx --yes tsx
// =============================================================================
// session_messages.ts — Retrieve and format messages from an OpenCode session
//
// Part of: .opencode/skills/auto-agents-system/scripts/
//
// Fetches the message history for a single session.  By default every known
// part type is returned in full, with no truncation:
//
//   text        — the model's visible output
//   reasoning   — internal chain-of-thought (clearly distinct from text)
//   tool        — full tool call: input object + complete output + error state
//   patch       — file-change snapshot (hash + list of modified files)
//   step_start  — inference step boundary (snapshot ref)
//   step_finish — inference step boundary (reason, token counts, cost)
//
// Use the filter/truncation flags below to narrow or condense the output.
//
// USAGE:
//   session_messages.ts --session-id ID [OPTIONS]
//
//   --session-id ID          Target session ID (e.g. ses_abc123)
//   --limit N                Return only the last N messages (default: all)
//   --offset M               Skip M messages from the newest end before --limit
//
//   Filter flags (each removes a part type entirely; default: include all):
//   --no-reasoning           Exclude reasoning parts
//   --no-tools               Exclude tool call parts
//   --no-patches             Exclude patch parts
//   --no-step-boundaries     Exclude step_start and step_finish parts
//
//   Truncation flags (default: no truncation):
//   --reasoning-max N        Truncate each reasoning block to N chars
//   --tool-output-max N      Truncate each tool output to N chars
//
//   Output mode:
//   --text-only              Shortcut: exclude everything except text parts,
//                            and collapse to { role, agent, time, text }
//   --raw                    Output full raw Message API objects (all fields)
//   --server URL             OpenCode Server base URL (default: http://localhost:4096)
//
// OUTPUT (stdout, default):
//   JSON array of FormattedMessage: { role, agent, time, parts: FmtPart[] }
//   Parts are ordered as they appear in the message (step_start first, etc.)
//
// MESSAGE ORDERING:
//   --limit returns the N most-recent messages, sorted oldest-first within
//   that window.  --offset skips from the newest end before applying --limit.
//
// DEPENDENCIES: Node.js >= 18 (native fetch), npx tsx (TypeScript execution)
// =============================================================================
import { fileURLToPath } from 'node:url';
import {
  getMessages,
  formatMessages,
  parseServerFlag,
  getFlag,
  hasFlag,
} from './session_api.ts';

// ─────────────────────────────────────────────────────────────────────────────
// MAIN
// ─────────────────────────────────────────────────────────────────────────────

async function main(): Promise<void> {
  const { server, rest } = parseServerFlag(process.argv);

  if (rest.includes('-h') || rest.includes('--help')) {
    printUsage();
    process.exit(0);
  }

  const sessionId = getFlag(rest, '--session-id');
  if (!sessionId) { process.stderr.write('ERROR: --session-id is required\n'); process.exit(1); }

  // ── Pagination ──────────────────────────────────────────────────────────────
  const limitStr = getFlag(rest, '--limit');
  const limit = limitStr !== undefined ? parseInt(limitStr, 10) : undefined;
  if (limit !== undefined && (isNaN(limit) || limit < 1)) {
    process.stderr.write('ERROR: --limit must be a positive integer\n');
    process.exit(1);
  }

  const offsetStr = getFlag(rest, '--offset');
  const offset = offsetStr !== undefined ? parseInt(offsetStr, 10) : undefined;
  if (offset !== undefined && (isNaN(offset) || offset < 0)) {
    process.stderr.write('ERROR: --offset must be a non-negative integer\n');
    process.exit(1);
  }

  // ── Output mode ─────────────────────────────────────────────────────────────
  const raw      = hasFlag(rest, '--raw');
  const textOnly = hasFlag(rest, '--text-only');

  // ── Filter flags (all default to include) ───────────────────────────────────
  const includeReasoning      = !hasFlag(rest, '--no-reasoning');
  const includeTools          = !hasFlag(rest, '--no-tools');
  const includePatches        = !hasFlag(rest, '--no-patches');
  const includeStepBoundaries = !hasFlag(rest, '--no-step-boundaries');

  // ── Truncation flags ────────────────────────────────────────────────────────
  const reasoningMaxStr = getFlag(rest, '--reasoning-max');
  const reasoningMaxChars = reasoningMaxStr !== undefined
    ? (() => {
        const n = parseInt(reasoningMaxStr, 10);
        if (isNaN(n) || n < 1) { process.stderr.write('ERROR: --reasoning-max must be a positive integer\n'); process.exit(1); }
        return n;
      })()
    : undefined;

  const toolOutputMaxStr = getFlag(rest, '--tool-output-max');
  const toolOutputMaxChars = toolOutputMaxStr !== undefined
    ? (() => {
        const n = parseInt(toolOutputMaxStr, 10);
        if (isNaN(n) || n < 1) { process.stderr.write('ERROR: --tool-output-max must be a positive integer\n'); process.exit(1); }
        return n;
      })()
    : undefined;

  // ── Fetch ───────────────────────────────────────────────────────────────────
  const descParts: string[] = [];
  if (limit  !== undefined) descParts.push(`last ${limit}`);
  else                       descParts.push('all');
  if (offset !== undefined) descParts.push(`offset ${offset}`);
  process.stderr.write(`Fetching ${descParts.join(', ')} message(s) from ${sessionId}…\n`);

  const messages = await getMessages(server, sessionId, limit, offset);
  process.stderr.write(`Retrieved ${messages.length} message(s).\n`);

  // ── Format & output ─────────────────────────────────────────────────────────
  if (raw) {
    process.stdout.write(JSON.stringify(messages, null, 2) + '\n');
    return;
  }

  if (textOnly) {
    // Shortcut: collapse to plain { role, agent, time, text } — no parts array
    const formatted = formatMessages(messages, {
      includeReasoning: false, includeTools: false,
      includePatches:   false, includeStepBoundaries: false,
    });
    const simplified = formatted.map(msg => ({
      role:  msg.role,
      agent: msg.agent,
      time:  msg.time,
      text:  msg.parts
        .filter(p => p.type === 'text')
        .map(p => (p as { type: 'text'; content: string }).content)
        .join(''),
    }));
    process.stdout.write(JSON.stringify(simplified, null, 2) + '\n');
    return;
  }

  const formatted = formatMessages(messages, {
    includeReasoning,
    includeTools,
    includePatches,
    includeStepBoundaries,
    reasoningMaxChars,
    toolOutputMaxChars,
  });
  process.stdout.write(JSON.stringify(formatted, null, 2) + '\n');
}

function printUsage(): void {
  process.stderr.write(`\
Usage:
  session_messages.ts --session-id ID [OPTIONS]

REQUIRED:
  --session-id ID          Target session ID (e.g. ses_abc123)

PAGINATION:
  --limit N                Return only the last N messages (default: all).
                           Returns the N most-recent, sorted oldest-first.
  --offset M               Skip M messages from the newest end before --limit

FILTER (each removes a part type entirely; default: include all):
  --no-reasoning           Exclude reasoning (chain-of-thought) parts
  --no-tools               Exclude tool call parts
  --no-patches             Exclude file-change patch parts
  --no-step-boundaries     Exclude step_start and step_finish parts

TRUNCATION (default: no truncation):
  --reasoning-max N        Truncate each reasoning block to N chars
  --tool-output-max N      Truncate each tool output to N chars

OUTPUT MODE:
  --text-only              Shortcut for all --no-* flags; collapses to
                           { role, agent, time, text } (no parts array)
  --raw                    Raw Message API objects (ignores all other flags)
  --server URL             OpenCode Server base URL (default: http://localhost:4096)
  -h, --help               Print this message and exit
`);
}

// ─────────────────────────────────────────────────────────────────────────────
// CLI ENTRY POINT
// ─────────────────────────────────────────────────────────────────────────────

if (process.argv[1] === fileURLToPath(import.meta.url)) {
  main().catch((err: unknown) => {
    process.stderr.write(`ERROR: ${err instanceof Error ? err.message : String(err)}\n`);
    process.exit(1);
  });
}
