Official

codex

Codex agent provider: runs the Codex CLI's app-server for each account.

The app opens the listing; nothing installs until an agent in your Plugins workspace has read the files and you enable the plugin. In a terminal: cvg install convergence/codex@0.2.0

Permissions in 0.2.0

  • Provide agents agents.provideMediumAdds agents to the app.Provide the Codex agent and pass its tool calls to plugin tools
  • Run named programs processMediumStarts the listed programs.Run the Codex CLI, and ask or tell the installer that owns it (npm or Homebrew) about a newer versionPrograms: codexnpmbrew
  • Environment variables envMediumReads the listed environment variables.Find each account's Codex home, and pass extra app-server arguments set in CODEX_ARGSVariables: HOMECODEX_HOMECODEX_ARGS
  • Read files fs.readMediumReads files in the listed places.Read, once, the accounts the previous Codex provider keptPlaces: its own data folder

Files

map.test.ts25.3 KB
// Ported from the Rust plugin's map.rs and agent.rs unit tests.
import { test } from "node:test";
import assert from "node:assert/strict";
import type { History } from "./types.ts";
import type { Turn } from "./wire.ts";
import * as map from "./map.ts";

/// The parent's turns as `thread/read` returns them: a v1 spawn, the
/// `wait` that carries the answer, and a v2 `started` activity.
function parentTurns(): Turn[] {
  return [
    {
      id: "turn-1",
      status: "completed",
      startedAt: 100,
      completedAt: 200,
      items: [
        { type: "userMessage", id: "u1", content: [{ type: "text", text: "Count the tests" }] },
        {
          type: "collabAgentToolCall",
          id: "call-spawn",
          tool: "spawnAgent",
          status: "completed",
          senderThreadId: "root",
          receiverThreadIds: ["child-1"],
          prompt: "Count the tests\nthen report",
          model: "gpt-5.5",
          reasoningEffort: "low",
          agentsStates: { "child-1": { status: "pendingInit", message: null } },
        },
        {
          type: "collabAgentToolCall",
          id: "call-wait",
          tool: "wait",
          status: "completed",
          senderThreadId: "root",
          receiverThreadIds: ["child-1"],
          prompt: null,
          model: null,
          reasoningEffort: null,
          agentsStates: { "child-1": { status: "completed", message: "Final: 42 tests." } },
        },
        { type: "subAgentActivity", id: "act-1", kind: "started", agentThreadId: "child-2", agentPath: "/root/scout" },
        {
          type: "subAgentActivity",
          id: "act-2",
          kind: "completed",
          agentThreadId: "child-2",
          agentPath: "/root/scout",
        },
        { type: "agentMessage", id: "m1", text: "There are 42 tests." },
      ],
    },
  ];
}

function children(): Map<string, History> {
  return new Map([
    [
      "child-1",
      {
        thread: {
          id: "child-1",
          preview: "Count the tests\nthen report",
          parentThreadId: "root",
          agentNickname: "Euler",
          agentRole: "explorer",
          historyMode: "paginated",
          source: { subAgent: { thread_spawn: { parent_thread_id: "root", depth: 1 } } },
        },
        turns: [
          {
            id: "turn-c1",
            status: "completed",
            startedAt: 110,
            completedAt: 190,
            items: [
              { type: "userMessage", id: "cu1", content: [{ type: "text", text: "Count the tests" }] },
              {
                type: "commandExecution",
                id: "cmd-1",
                command: "rg -c test",
                status: "completed",
                commandActions: [{ type: "search", command: "rg -c test", query: "test", path: null }],
              },
              {
                type: "collabAgentToolCall",
                id: "call-grand",
                tool: "spawnAgent",
                status: "completed",
                senderThreadId: "child-1",
                receiverThreadIds: ["grandchild-1"],
                prompt: "Read the parser",
                agentsStates: {},
              },
              { type: "agentMessage", id: "cm1", text: "42 tests." },
            ],
          },
        ],
      },
    ],
    [
      "grandchild-1",
      {
        thread: { id: "grandchild-1", preview: "Read the parser", parentThreadId: "child-1" },
        turns: [
          {
            id: "turn-g1",
            status: "completed",
            items: [{ type: "agentMessage", id: "gm1", text: "The parser is fine." }],
          },
        ],
      },
    ],
  ]);
}

test("background questions preserve choices and free text", () => {
  const native = {
    type: "agentMessage",
    id: "q1",
    text: "Checking",
    delivery: "async",
    questions: [
      { title: "Which target?", options: ["Desktop", "Mobile"] },
      { title: "Anything else?", options: null },
    ],
  };
  const request = present(map.backgroundQuestion(native));
  assert.equal(request.responseMode, "message");
  assert.equal(request.id, "async:q1");
  assert.equal(present(request.fields[0].options)[1].value, "Mobile");
  assert.equal(request.fields[0].kind, "select");
  assert.ok(request.fields.every((field) => field.allowOther && field.required));
  assert.equal(request.fields[1].kind, "text");
  assert.equal(request.fields[1].options, undefined);
  assert.equal(map.backgroundQuestion({ ...native, delivery: null }), null);
  assert.equal(map.backgroundQuestion({ ...native, questions: [] }), null);
  assert.equal(map.backgroundQuestion({ type: "agentMessage", id: "old", text: "hi" }), null);
});

test("history rebuilds each subagent at its spawn", () => {
  const items = map.transcriptWithSubagents(parentTurns(), children(), null);
  // user, v1 task, v2 task, assistant: `wait` and the other activity are
  // bookkeeping the task rows already show.
  assert.equal(items.length, 4);
  assert.equal(items[0].role, "user");
  assert.equal(items[3].role, "assistant");

  const one = items[1];
  assert.ok(one.role === "task");
  assert.equal(one.id, "task-child-1", "the id a live task gets");
  assert.equal(one.role, "task");
  const task = one.task;
  assert.equal(task.id, "child-1");
  assert.equal(task.title, "Count the tests", "the prompt's first line, never a tool name");
  assert.equal(task.prompt, "Count the tests\nthen report");
  assert.equal(task.name, "Euler");
  assert.equal(task.toolCallId, "call-spawn");
  assert.deepEqual([task.model, task.effort], ["gpt-5.5", "low"]);
  assert.equal(task.status, "completed");
  assert.equal(task.summary, "Final: 42 tests.", "the answer `wait` returned");
  assert.equal(task.parentTaskId, undefined);
  assert.equal(task.toolUses, 1);
  assert.equal(Date.parse(present(task.startedAt)) / 1000, 110);
  assert.equal(Date.parse(present(task.endedAt)) / 1000, 190);
  assert.equal(one.createdAt, task.startedAt);

  // The child's own transcript, with the grandchild inside it.
  const nested = present(one.items);
  assert.ok(nested[2].role === "task");
  assert.equal(nested.length, 4);
  assert.equal(nested[0].role, "user");
  assert.equal(nested[1].role, "tool");
  const grand = nested[2].task;
  assert.equal(grand.id, "grandchild-1");
  assert.equal(grand.parentTaskId, "child-1");
  assert.equal(grand.title, "Read the parser");
  assert.equal(grand.status, "idle", "a finished turn without a final state can be resumed");
  assert.equal(grand.summary, "The parser is fine.", "its last message is its result");
  assert.equal(present(nested[2].items).length, 1);

  // A v2 child that could not be read still gets a row, named by its
  // path, with the status the activity reported.
  assert.ok(items[2].role === "task");
  const v2 = items[2].task;
  assert.equal(v2.id, "child-2");
  assert.equal(v2.name, undefined);
  assert.equal(v2.title, "scout");
  assert.equal(v2.status, "completed");
  assert.equal(v2.toolCallId, undefined);
  assert.equal(v2.toolUses, undefined, "an unread child has no count");
  assert.deepEqual(items[2].items, []);
});

test("a v2 child takes its title from its preview", () => {
  const all = children();
  all.set("child-2", {
    thread: { id: "child-2", preview: "Scout the repo\nfor TODOs", parentThreadId: "root", agentNickname: "Scout" },
    turns: [
      {
        id: "turn-s1",
        status: "completed",
        items: [
          { type: "subAgentActivity", id: "act-up", kind: "interacted", agentThreadId: "root", agentPath: "/root" },
          { type: "agentMessage", id: "sm1", text: "3 TODOs." },
        ],
      },
    ],
  });
  const items = map.transcriptWithSubagents(parentTurns(), all, null);
  const v2 = items[2];
  assert.ok(v2.role === "task");
  assert.equal(v2.task.title, "Scout the repo", "a readable instruction wins over the task name");
  assert.equal(v2.task.name, "Scout");
  assert.equal(v2.task.summary, "3 TODOs.");
  assert.equal(present(v2.items).length, 1, "a message to the parent is not a child");
});

test("a thread that spawns its own ancestor does not loop", () => {
  const turns = [
    {
      id: "t",
      status: "completed",
      items: [{ type: "collabAgentToolCall", id: "c", tool: "spawnAgent", receiverThreadIds: ["a"], agentsStates: {} }],
    },
  ];
  const loop = new Map([["a", { thread: { id: "a" }, turns }]]);
  const items = map.transcriptWithSubagents(turns, loop, null);
  assert.equal(items.length, 1);
  assert.ok(items[0].role === "task");
  assert.deepEqual(items[0].items, [], "the child's own spawn of itself is skipped");
});

test("a subagent title is its instruction, then its task name", () => {
  assert.equal(map.taskTitle("Read the parser\nthen report", "reader", "Euler"), "Read the parser");
  assert.equal(map.taskTitle(null, "pipeline_eval_tools", "Planck"), "pipeline eval tools");
  assert.equal(map.taskTitle("  ", null, "Planck"), "Planck");
  assert.equal(map.taskTitle(null, null, null), "Subagent");
  assert.equal(map.pathTaskName("/root/audit/source_check"), "source_check");
  assert.equal(map.pathTaskName("/root"), null);
});

test("history lists every child once, in order", () => {
  assert.deepEqual(map.childIds(parentTurns()), ["child-1", "child-2"]);
});

test("read commands classify as read", () => {
  assert.equal(map.commandToolKind([{ type: "read", command: "cat a", name: "a", path: "a.rs" }]), "read");
});

test("list and search classify as search", () => {
  assert.equal(
    map.commandToolKind([
      { type: "listFiles", command: "ls", path: "src" },
      { type: "search", command: "rg x", query: "x", path: "src" },
    ]),
    "search",
  );
});

test("unknown or mixed commands execute", () => {
  assert.equal(map.commandToolKind([{ type: "unknown", command: "echo hi" }]), "execute");
  assert.equal(
    map.commandToolKind([
      { type: "read", command: "cat a", name: "a", path: "a.rs" },
      { type: "unknown", command: "rm -rf b" },
    ]),
    "execute",
  );
  assert.equal(map.commandToolKind([]), "execute");
  assert.equal(map.commandToolKind(undefined), "execute");
});

test("a command item carries terminal content", () => {
  const call = map.toolCallFromItem({
    type: "commandExecution",
    id: "exec-1",
    command: "/bin/zsh -lc 'echo hi'",
    cwd: "/tmp/work",
    processId: null,
    source: "unifiedExecStartup",
    status: "completed",
    commandActions: [{ type: "unknown", command: "echo hi" }],
    aggregatedOutput: "hi\n",
    exitCode: 0,
    durationMs: 0,
  });
  assert.ok(call);
  assert.equal(call.kind, "execute");
  assert.equal(call.status, "completed");
  assert.equal(call.title, "echo hi", "the shell wrapper is not part of the title");
  assert.equal(call.name, "shell");
  assert.deepEqual(call.input, { command: "/bin/zsh -lc 'echo hi'", cwd: "/tmp/work" });
  assert.deepEqual(call.content, [
    { type: "terminal", command: "/bin/zsh -lc 'echo hi'", cwd: "/tmp/work", output: "hi\n", exitCode: 0 },
  ]);
  assert.equal(call.locations, undefined);
});

test("the shell wrapper comes off only a real shell invocation", () => {
  assert.equal(map.unwrapped('/bin/bash -c "ls -la"'), "ls -la");
  assert.equal(map.unwrapped("sh -lc git status"), "git status");
  assert.equal(map.unwrapped("python -c 'print(1)'"), "python -c 'print(1)'");
  assert.equal(map.unwrapped("ls"), "ls");
});

// Codex sends an update as a unified diff, but a new or deleted file
// whole (`format_file_change_diff` in its app-server protocol): read as a
// patch, three new files counted +0 -0.
test("a file change item carries an update's patch and a whole file's text", () => {
  const call = map.toolCallFromItem({
    type: "fileChange",
    id: "fc-1",
    status: "completed",
    changes: [
      { path: "src/a.rs", kind: { type: "update", move_path: null }, diff: "@@ -1 +1 @@\n-a\n+b\n" },
      { path: "src/b.rs", kind: { type: "add" }, diff: "new\n" },
      { path: "src/c.rs", kind: { type: "delete" }, diff: "old\n" },
    ],
  });
  assert.ok(call);
  assert.equal(call.kind, "edit");
  assert.equal(call.title, "src/a.rs and 2 more");
  assert.equal(present(call.locations).length, 3);
  assert.deepEqual(call.content, [
    { type: "diff", path: "src/a.rs", diff: "@@ -1 +1 @@\n-a\n+b\n" },
    { type: "diff", path: "src/b.rs", oldText: "", newText: "new\n" },
    { type: "diff", path: "src/c.rs", oldText: "old\n", newText: "" },
  ]);
  assert.equal(call.input, undefined);
});

test("unknown item types are ignored, not fatal", () => {
  const unknown = { type: "somethingCodexAddedLater", id: "x1" };
  assert.equal(map.itemType(unknown), null);
  assert.equal(map.itemId(unknown), null);
  assert.equal(map.toolCallFromItem(unknown), null);
  assert.equal(map.transcriptItem(unknown), null);
});

test("rate limits become named windows", () => {
  const limits = map.usageLimits({
    rateLimits: {
      limitName: "Codex",
      primary: { usedPercent: 82.5, windowDurationMins: 300, resetsAt: 1_800_000_000 },
      secondary: { usedPercent: 100.0, windowDurationMins: 10080, resetsAt: null },
      rateLimitReachedType: "rate_limit_reached",
    },
    rateLimitResetCredits: { availableCount: 2 },
  });
  assert.equal(limits.resetCredits, 2);
  assert.equal(present(limits.windows).length, 2);
  assert.equal(present(limits.windows)[0].label, "5 hours");
  assert.equal(present(limits.windows)[0].usedPercent, 82.5);
  assert.equal(present(limits.windows)[0].resetsAt, new Date(1_800_000_000 * 1000).toISOString());
  assert.equal(present(limits.windows)[0].windowMinutes, 300);
  // Only the window that is really full is blocked.
  assert.equal(present(limits.windows)[0].blocked, false);
  assert.equal(present(limits.windows)[1].label, "Weekly");
  assert.equal(present(limits.windows)[1].blocked, true);
  assert.equal(present(limits.windows)[1].resetsAt, undefined);
});

test("a snapshot without credits offers none, and windows without a length use the account's name", () => {
  const limits = map.usageLimits({
    rateLimits: { primary: { usedPercent: 10.0, windowDurationMins: 60 } },
    rateLimitResetCredits: { availableCount: 0 },
  });
  assert.equal(limits.resetCredits, undefined, "zero credits is not an offer");
  assert.equal(present(limits.windows)[0].label, "Hourly");
  const named = map.usageLimits({
    rateLimits: { limitName: "Pro", primary: { usedPercent: 1 }, secondary: { usedPercent: 1 } },
  });
  assert.deepEqual(
    present(named.windows).map((window) => window.label),
    ["Pro", "Pro"],
  );
  const bare = map.usageLimits({
    rateLimits: { primary: { usedPercent: 1 }, secondary: { usedPercent: 1, windowDurationMins: 1440 } },
  });
  assert.deepEqual(
    present(bare.windows).map((window) => window.label),
    ["Current limit", "Daily"],
  );
});

test("usage recovery never derives permission or identity from meters", () => {
  for (const usedPercent of [0, 99, 100]) {
    const recovery = map.usageRecovery({ rateLimits: { primary: { usedPercent, resetsAt: 1_900_000_000 } } });
    assert.equal(recovery.availability, "unknown");
    assert.equal(recovery.identity, undefined);
    assert.equal(recovery.resetsAt, undefined);
  }
  for (const ordinaryUsageAllowed of [true, false, null]) {
    const recovery = map.usageRecovery({ ordinaryUsageAllowed, accountId: "native-account" });
    assert.equal(
      recovery.availability,
      ordinaryUsageAllowed === true ? "allowed" : ordinaryUsageAllowed === false ? "blocked" : "unknown",
    );
    assert.equal(recovery.identity, "native-account");
  }
  const sparse = map.usageRecovery(
    { ordinaryUsageAllowed: true, accountId: "not-in-notification" },
    "codex/account/rateLimits/updated",
  );
  assert.equal(sparse.availability, "unknown");
  assert.equal(sparse.identity, undefined);
});

test("quota recovery waits for every responsible window and never times a credit purchase", () => {
  const primary = { usedPercent: 100, resetsAt: 1_900_000_000 };
  const secondary = { usedPercent: 100, resetsAt: 1_900_100_000 };
  const snapshot = { limitId: "codex", rateLimitReachedType: "rate_limit_reached", primary, secondary };
  const latest = map.usageRecovery({ ordinaryUsageAllowed: false, rateLimits: snapshot });
  assert.equal(latest.resetsAt, new Date(secondary.resetsAt * 1000).toISOString());
  const recovered = map.usageRecovery({
    ordinaryUsageAllowed: true,
    rateLimits: { ...snapshot, rateLimitReachedType: null },
  });
  assert.equal(latest.scope, undefined, "native permission/rejection is account-wide");
  assert.equal(recovered.scope, latest.scope, "a bucket recovering must not change recovery identity");
  for (const missing of [{ usedPercent: 100 }, { usedPercent: 100, resetsAt: null }]) {
    assert.equal(map.usageRecovery({ rateLimits: { ...snapshot, secondary: missing } }).resetsAt, undefined);
  }
  assert.equal(map.usageRecovery({ rateLimits: { ...snapshot, primary: null, secondary: null } }).resetsAt, undefined);
  const multiple = map.usageRecovery({
    rateLimits: snapshot,
    rateLimitsByLimitId: {
      codex: snapshot,
      other: { ...snapshot, limitId: "other", secondary: { ...secondary, resetsAt: 1_900_200_000 } },
    },
  });
  assert.equal(multiple.resetsAt, new Date(1_900_200_000_000).toISOString());
  assert.equal(multiple.scope, undefined);
  const missingBucket = map.usageRecovery({
    rateLimitsByLimitId: {
      codex: snapshot,
      other: { ...snapshot, limitId: "other", primary: { usedPercent: 100 }, secondary: null },
    },
  });
  assert.equal(missingBucket.resetsAt, undefined);
  for (const rateLimitReachedType of [
    "workspace_owner_credits_depleted",
    "workspace_member_credits_depleted",
    "workspace_owner_usage_limit_reached",
    "workspace_member_usage_limit_reached",
  ]) {
    assert.equal(map.usageRecovery({ rateLimits: { ...snapshot, rateLimitReachedType } }).resetsAt, undefined);
  }
  assert.equal(
    map.usageRecovery({
      rateLimits: { ...snapshot, spendControlReached: true, individualLimit: { resetsAt: 1_900_200_000 } },
    }).resetsAt,
    new Date(1_900_200_000_000).toISOString(),
  );
  assert.equal(map.usageRecovery({ rateLimits: { ...snapshot, spendControlReached: true } }).resetsAt, undefined);
});

test("reasoning prefers the summary over the raw content", () => {
  assert.equal(map.reasoningText(["done"], ["raw"]), "done");
  assert.equal(map.reasoningText([], ["raw"]), "raw");
  assert.equal(map.reasoningText(["a", "b"], []), "a\n\nb");
});

test("the transcript maps every role, and the title comes from the first message", () => {
  const turns = [
    {
      id: "t1",
      status: "completed",
      error: null,
      items: [
        { type: "userMessage", id: "u1", clientId: null, content: [{ type: "text", text: "hi", text_elements: [] }] },
        { type: "reasoning", id: "r1", summary: ["thinking"], content: [] },
        {
          type: "agentMessage",
          id: "m1",
          text: "hello",
          phase: null,
          memoryCitation: null,
          delivery: null,
          questions: null,
        },
        {
          type: "commandExecution",
          id: "e1",
          command: "ls",
          cwd: "/w",
          status: "completed",
          commandActions: [{ type: "listFiles", command: "ls", path: "/w" }],
          aggregatedOutput: "a\n",
          exitCode: 0,
        },
        { type: "plan", id: "p1", text: "1. do it" },
        { type: "contextCompaction", id: "cc1" },
      ],
    },
  ];
  const items = map.transcriptWithSubagents(turns, new Map(), null);
  assert.deepEqual(
    items.map((item) => item.role),
    ["user", "reasoning", "assistant", "tool", "plan", "notice"],
  );
  assert.ok(items[3].role === "tool" && items[4].role === "plan" && items[5].role === "notice");
  assert.equal(items[3].call.kind, "search");
  assert.deepEqual(items[3].call.locations, [{ path: "/w" }]);
  assert.deepEqual(items[4].plan, { entries: [{ content: "1. do it", status: "pending" }] });
  assert.equal(items[5].text, "Context compacted");
  assert.equal(map.threadTitle(null, "", turns), "hi");
  assert.equal(map.threadTitle("Named", "preview", turns), "Named");
  assert.equal(map.threadTitle(null, "  First line\nsecond", turns), "First line");
  assert.equal(map.threadTitle(null, "", []), null);
});

test("user messages keep text and paths, and drop inline images", () => {
  assert.deepEqual(
    map.userBlocks([
      { type: "text", text: "look" },
      { type: "localImage", path: "/a.png" },
      { type: "mention", path: "/b.rs" },
      { type: "skill", path: "/skills/x" },
      { type: "image", url: "data:..." },
      { type: "future" },
    ]),
    [
      { type: "text", text: "look" },
      { type: "file_ref", path: "/a.png" },
      { type: "file_ref", path: "/b.rs" },
      { type: "file_ref", path: "/skills/x" },
    ],
  );
});

test("the stdin notice of codex exec is not reasoning", () => {
  assert.equal(
    map.transcriptItem({
      type: "reasoning",
      id: "r",
      summary: ["Reading additional input from stdin..."],
      content: [],
    }),
    null,
  );
  assert.equal(map.isStdinNotice("  Reading additional input from stdin...\n"), true);
  assert.equal(map.isStdinNotice("Reading additional input from stdin... and more"), false);
});

test("dynamic tool calls show their input and result", () => {
  const started = map.toolCallFromItem({
    type: "dynamicToolCall",
    id: "call-1",
    namespace: null,
    tool: "execute",
    arguments: { code: "return 1 + 1" },
    status: "inProgress",
    contentItems: null,
    success: null,
    durationMs: null,
  });
  assert.ok(started);
  assert.equal(started.title, "execute");
  assert.equal(started.name, "execute");
  assert.equal(started.status, "running");
  assert.deepEqual(started.input, { code: "return 1 + 1" }, "the code stays visible");
  assert.equal(started.content, undefined);

  const completed = map.toolCallFromItem({
    type: "dynamicToolCall",
    id: "call-1",
    namespace: null,
    tool: "execute",
    arguments: { code: "return 1 + 1" },
    status: "completed",
    contentItems: [
      { type: "inputText", text: "2" },
      { type: "inputImage", imageUrl: "data:image/png;base64,AA==" },
    ],
    success: true,
    durationMs: 12,
  });
  assert.ok(completed);
  assert.equal(completed.status, "completed");
  assert.deepEqual(completed.content, [
    { type: "text", text: "2" },
    { type: "image", mimeType: "image/png", data: "AA==" },
  ]);

  const failed = map.toolCallFromItem({
    type: "dynamicToolCall",
    id: "call-2",
    tool: "now",
    arguments: {},
    status: "completed",
    contentItems: [{ type: "inputText", text: "no clock" }],
    success: false,
  });
  assert.ok(failed);
  assert.equal(failed.status, "failed", "success: false is a failure whatever the status says");
});

test("mcp calls, web searches and image views are tool rows", () => {
  const mcp = map.toolCallFromItem({
    type: "mcpToolCall",
    id: "m",
    server: "gh",
    tool: "pr",
    status: "completed",
    arguments: { n: 1 },
    result: { ok: 1 },
    error: null,
  });
  assert.ok(mcp);
  assert.deepEqual([mcp.name, mcp.kind, mcp.status], ["gh.pr", "other", "completed"]);
  const mcpContent = present(mcp.content)[0];
  assert.ok(mcpContent.type === "text");
  assert.equal(mcpContent.text, '{\n  "ok": 1\n}');
  const broken = map.toolCallFromItem({
    type: "mcpToolCall",
    id: "m",
    server: "gh",
    tool: "pr",
    status: "completed",
    error: "denied",
  });
  assert.ok(broken);
  const brokenContent = present(broken.content)[0];
  assert.ok(brokenContent.type === "text");
  assert.deepEqual([broken.status, brokenContent.text], ["failed", "denied"]);
  const search = map.toolCallFromItem({ type: "webSearch", id: "w", query: "" });
  assert.ok(search);
  assert.deepEqual([search.kind, search.title], ["fetch", "Web search"]);
  const image = map.toolCallFromItem({ type: "imageView", id: "i", path: "/a.png" });
  assert.ok(image);
  assert.deepEqual([image.kind, image.locations], ["read", [{ path: "/a.png" }]]);
});

test("tool results become codex content items", () => {
  assert.deepEqual(
    map.dynamicToolResponse({
      content: [
        { type: "text", text: "hi" },
        { type: "image", data: "AA==", mimeType: "image/png" },
      ],
    }),
    {
      contentItems: [
        { type: "inputText", text: "hi" },
        { type: "inputImage", imageUrl: "data:image/png;base64,AA==" },
      ],
      success: true,
    },
  );
  assert.equal(map.dynamicToolResponse({ content: [{ type: "text", text: "boom" }], isError: true }).success, false);
});

test("plan steps map to plan entries", () => {
  const plan = map.planFromSteps([
    { step: "one", status: "completed" },
    { step: "two", status: "inProgress" },
    { step: "three", status: "pending" },
  ]);
  assert.deepEqual(
    present(plan.entries).map((entry) => entry.status),
    ["completed", "in_progress", "pending"],
  );
});

test("subagent threads are not listed as chats", () => {
  const threads = [
    { id: "root", preview: "hi", createdAt: 1, updatedAt: 1, parentThreadId: null },
    { id: "child", preview: "x", createdAt: 1, updatedAt: 1, parentThreadId: "root" },
    {
      id: "old-child",
      preview: "x",
      createdAt: 1,
      updatedAt: 1,
      source: { subagent: { thread_spawn: { parent_thread_id: "root", depth: 1 } } },
    },
  ];
  assert.deepEqual(
    threads.filter((thread) => map.threadParent(thread) === null).map((thread) => thread.id),
    ["root"],
  );
});

test("a first line is cut at 80 characters", () => {
  const long = "é".repeat(100);
  assert.equal(map.firstLine(long), `${"é".repeat(80)}…`);
  assert.equal(map.firstLine("  a\r\nb"), "a");
});

function present<T>(value: T | null | undefined): T {
  assert.ok(value != null);
  return value;
}

Versions

VersionPublishedPlugin APISizePermissionsStatus
0.2.0latestOct 5, 2026>=2 <378.1 KB4 permissionsListed

Reviews and comments

0 threads · 0 reviews

No comments yet.