Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions evals/capability/lib.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -667,8 +667,8 @@ describe("resolveRequestedProviderModel", () => {
expect(requested).toEqual({ provider: raw.provider, model: raw.model });

const fallback = detectProviderFallback({
requestedProvider: requested.provider,
requestedModel: requested.model,
...(requested.provider !== undefined ? { requestedProvider: requested.provider } : {}),
...(requested.model !== undefined ? { requestedModel: requested.model } : {}),
resolvedProvider: cell!.provider,
resolvedModel: cell!.model,
});
Expand All @@ -695,7 +695,7 @@ describe("resolveRequestedProviderModel", () => {
{},
{ provider: "(default)", model: "(default)" },
);
expect(requested).toEqual({ provider: undefined, model: undefined });
expect(requested).toEqual({});
});
});

Expand Down
6 changes: 4 additions & 2 deletions evals/capability/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -477,9 +477,11 @@ export function resolveRequestedProviderModel(
): { provider?: string; model?: string } {
const requested = (v?: string): string | undefined =>
v === undefined || v === "(default)" ? undefined : v;
const provider = variant.provider ?? requested(labels.provider);
const model = variant.model ?? requested(labels.model);
return {
provider: variant.provider ?? requested(labels.provider),
model: variant.model ?? requested(labels.model),
...(provider !== undefined ? { provider } : {}),
...(model !== undefined ? { model } : {}),
};
}

Expand Down
4 changes: 2 additions & 2 deletions scripts/eval-capability.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { join } from "node:path";
import { execFile } from "node:child_process";
import { promisify } from "node:util";

import { initEvalGitRepo, mapPool, parseArgs, buildEvalDiagnostics } from "./eval-capability.ts";
import type { Config } from "../src/config/index.ts";
import { initEvalGitRepo, mapPool, parseArgs, buildEvalDiagnostics } from "./eval-capability.js";
import type { Config } from "../src/config/index.js";

const execFileAsync = promisify(execFile);

Expand Down
24 changes: 14 additions & 10 deletions scripts/eval-capability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,13 +257,20 @@ export function parseArgs(argv: readonly string[]): CliOptions {
return opts;
}

// exactOptionalPropertyTypes forbids passing an explicit `undefined` for an
// optional field, so build the fallback object with the key present only
// when the CLI option was actually given.
function providerModelFallback(opts: CliOptions): { provider?: string; model?: string } {
return {
...(opts.provider !== undefined ? { provider: opts.provider } : {}),
...(opts.model !== undefined ? { model: opts.model } : {}),
};
}

function requireExplicitModelPair(opts: CliOptions): void {
const matrix = opts.matrix?.trim();
if (matrix !== undefined && matrix.length > 0) {
parseMatrix(matrix, {
provider: opts.provider,
model: opts.model,
});
parseMatrix(matrix, providerModelFallback(opts));
return;
}
if (!opts.provider && !opts.model) {
Expand Down Expand Up @@ -705,8 +712,8 @@ async function runCase(
const resolvedProvider = execResult.provider ?? config.providerName ?? labels.provider;
const resolvedModel = execResult.model ?? config.model ?? labels.model;
providerFallback = detectProviderFallback({
requestedProvider: requested.provider,
requestedModel: requested.model,
...(requested.provider !== undefined ? { requestedProvider: requested.provider } : {}),
...(requested.model !== undefined ? { requestedModel: requested.model } : {}),
resolvedProvider,
resolvedModel,
});
Expand Down Expand Up @@ -852,10 +859,7 @@ async function main(): Promise<number> {
}
const all = await loadEvalCases(CASES_ROOT);
const selected = filterCases(all, opts.caseSelector);
const variants = parseMatrix(opts.matrix, {
provider: opts.provider,
model: opts.model,
});
const variants = parseMatrix(opts.matrix, providerModelFallback(opts));
const plan = expandMatrix(selected, variants);

console.log(
Expand Down
2 changes: 1 addition & 1 deletion scripts/eval-public-swe-one.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, test } from "bun:test";

import { parseArgs } from "./eval-public-swe-one.ts";
import { parseArgs } from "./eval-public-swe-one.js";

describe("parseArgs", () => {
test("--help does not require provider or model", () => {
Expand Down
4 changes: 3 additions & 1 deletion src/cost/pricing-fetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ export interface PricingCache {
export interface PricingFetcherOptions {
cachePath?: string;
endpoint?: string;
fetchImpl?: typeof fetch;
// The plain call signature, not `typeof fetch` — `typeof fetch` also carries
// static members (e.g. `preconnect`) that a test double has no reason to implement.
fetchImpl?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
fetchTimeoutMs?: number;
now?: () => number;
refreshIntervalMs?: number;
Expand Down
4 changes: 2 additions & 2 deletions tests/fixtures/plugins/implement-feature/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { implementFeature } from "./workflows/implement-feature.js";
import type { CommandPlugin } from "../../../src/tui/commands/registry.js";
import type { WorkflowPlugin } from "../../../src/workflows/definition.js";
import type { CommandPlugin } from "../../../../../src/tui/commands/registry.js";
import type { WorkflowPlugin } from "../../../../../src/workflows/definition.js";

export const workflowPlugin: WorkflowPlugin = {
workflows: [implementFeature],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Workflow } from "../../../../src/workflows/definition.js";
import type { Workflow } from "../../../../../../src/workflows/definition.js";

export const implementFeature: Workflow = {
name: "implement-feature",
Expand Down
14 changes: 12 additions & 2 deletions tests/integration/vendored-carry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,12 @@ describe("integration — vendored feature carry", () => {

const requests = session.harness.scenario.matchedRequests();
expect(requests.length).toBeGreaterThan(0);
const bodies = await Promise.all(requests.map((r) => r.clone().text()));
// HarnessRequest resolves to a body-less fallback shape under this project's
// DOM-less lib config, even though it carries a real body at runtime; cast
// through the Fetch Request shape to read it.
const bodies = await Promise.all(
requests.map((r) => (r.clone() as unknown as Request).text()),
);
expect(bodies.some((b) => b.includes(TRANSFORM_MARKER))).toBe(true);
} finally {
await closeIntegrationSession(session);
Expand Down Expand Up @@ -148,7 +153,12 @@ describe("integration — vendored feature carry", () => {

const requests = harness.scenario.matchedRequests();
expect(requests.length).toBeGreaterThan(0);
const bodies = await Promise.all(requests.map((r) => r.clone().text()));
// HarnessRequest resolves to a body-less fallback shape under this project's
// DOM-less lib config, even though it carries a real body at runtime; cast
// through the Fetch Request shape to read it.
const bodies = await Promise.all(
requests.map((r) => (r.clone() as unknown as Request).text()),
);
expect(bodies.some((b) => b.includes(NUDGE_MARKER))).toBe(true);

// Prompt-only: the nudge must not be persisted.
Expand Down
9 changes: 5 additions & 4 deletions tests/unit/codex-session.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ describe("getValidCodexToken", () => {
await withHome(async (home) => {
globalThis.fetch = (() => {
throw new Error("should not be called");
}) as typeof fetch;
}) as unknown as typeof fetch;
await saveCodexProfile(
{
name: "p",
Expand All @@ -69,7 +69,7 @@ describe("getValidCodexToken", () => {
await withHome(async (home) => {
globalThis.fetch = (() => {
throw new Error("should not be called");
}) as typeof fetch;
}) as unknown as typeof fetch;
await saveCodexProfile(
{
name: "p",
Expand Down Expand Up @@ -97,7 +97,7 @@ describe("getValidCodexToken", () => {
status: 200,
headers: { "content-type": "application/json" },
},
)) as typeof fetch;
)) as unknown as typeof fetch;
const token = await getValidCodexToken("p", 5_000, home);
expect(token.access).toBe("fresh");
const stored = await loadCodexProfile("p", home);
Expand All @@ -122,7 +122,8 @@ describe("getValidCodexToken", () => {
{ name: "p", createdAt: 0, tokens: { access: "old", refresh: "bad", expiresAt: 1_000 } },
home,
);
globalThis.fetch = (async () => new Response("revoked", { status: 400 })) as typeof fetch;
globalThis.fetch = (async () =>
new Response("revoked", { status: 400 })) as unknown as typeof fetch;
const err = await getValidCodexToken("p", 5_000, home).catch((e: unknown) => e);
expect(err).toBeInstanceOf(CodexAuthError);
expect((err as CodexAuthError).reason).toBe("refresh-failed");
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ test("loadSettings cannot silently drop a known optional key", async () => {
expect(loaded).not.toBeNull();
for (const key of GLOBAL_SETTINGS_OPTIONAL_KEYS) {
expect(loaded).toHaveProperty(key);
expect((loaded as Record<string, unknown>)[key]).not.toBeUndefined();
expect((loaded as unknown as Record<string, unknown>)[key]).not.toBeUndefined();
}
// Undefined optionals stay absent (not { foo: undefined }).
const minimalPath = join(cwd, "minimal.json");
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/corbits-skills-catalog.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { existsSync } from "node:fs";
import { readdir } from "node:fs/promises";
import { join } from "node:path";
import { expect, test } from "bun:test";
import { loadSkillCommands } from "../../src/plugins/skill-commands.ts";
import { loadSkillCommands } from "../../src/plugins/skill-commands.js";

const pluginRoot = join(import.meta.dirname, "../../plugins/corbits-skills");

Expand Down
2 changes: 1 addition & 1 deletion tests/unit/director.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const usage: TokenUsage = {
};

const source: LastCycleSource = {
id: "test",
sourceId: "test",
provider: "openai",
model: "test-model",
};
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/hooks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const usage: TokenUsage = {
};

const source: LastCycleSource = {
id: "test-source",
sourceId: "test-source",
provider: "openai",
model: "test-model",
};
Expand Down
3 changes: 3 additions & 0 deletions tests/unit/mcp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ describe("mcpClientToAgentTools (production gated path)", () => {

expect(capturedName).toBe("do_thing");
expect(capturedArgs).toEqual({ x: 1 });
if (typeof result === "string") throw new Error("expected structured ToolResult");
expect(result.content).toBe("done");
expect(result.isError).toBeUndefined();
});
Expand All @@ -187,6 +188,7 @@ describe("mcpClientToAgentTools (production gated path)", () => {
new AbortController().signal,
);

if (typeof result === "string") throw new Error("expected structured ToolResult");
expect(result.isError).toBe(true);
expect(result.content).toBe("server error");
});
Expand All @@ -210,6 +212,7 @@ describe("mcpClientToAgentTools (production gated path)", () => {
new AbortController().signal,
);
expect(asked).toBe(1);
if (typeof result === "string") throw new Error("expected structured ToolResult");
expect(result.isError).toBe(true);
expect(result.content).toContain("Blocked by permission policy");
});
Expand Down
5 changes: 4 additions & 1 deletion tests/unit/permission/cross-commit-composition.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ describe("comment normalization x exact full-command grants", () => {
const gate = gateWith(async (req) => {
prompts++;
const exact = req.scopes.find((s) => s.id === "exact");
return { allow: true, persist: { pattern: exact?.pattern ?? null, grant: "session" } };
return {
allow: true,
...(exact !== undefined ? { persist: { ...exact, grant: "session" as const } } : {}),
};
});
const withComment = "# build then test\ngit fetch origin && git rebase origin/main";
expect((await gate.evaluate(call(withComment))).allowed).toBe(true);
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/plugin-register.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ test("isPluginModuleEnabled: missing settings + repo without flag is off", () =>

test("isPluginModuleEnabled: marketplace/path/user defaultEnabled is ignored", () => {
const flagged = {
manifest: { id: "mkt", name: "mkt", kind: "command", defaultEnabled: true },
manifest: { id: "mkt", name: "mkt", kind: "command" as const, defaultEnabled: true },
};
expect(isPluginModuleEnabled({ ...flagged, origin: "user" }, {})).toBe(false);
expect(isPluginModuleEnabled({ ...flagged, origin: "path" }, {})).toBe(false);
Expand Down
9 changes: 6 additions & 3 deletions tests/unit/resolve-inference-spec.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ import type { InferenceSpec } from "../../src/agent/profile-types.js";

const baseSettings: Settings = {
providers: {
anthropic: { models: ["claude-sonnet-4", "claude-haiku-4"] },
xai: { models: ["grok-4"] },
local: { models: [] }, // empty models list = unrestricted
anthropic: {
baseURL: "https://api.anthropic.com",
models: ["claude-sonnet-4", "claude-haiku-4"],
},
xai: { baseURL: "https://api.x.ai", models: ["grok-4"] },
local: { baseURL: "http://localhost:11434", models: [] }, // empty models list = unrestricted
},
};

Expand Down
19 changes: 11 additions & 8 deletions tests/unit/run-agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,32 @@ async function* makeStream(events: ReactorEmittedEvent[]): AsyncIterable<Reactor
}

test("consumeStream calls sink for every event", async () => {
// Cast each event as a whole rather than just `data` — casting `data` alone still
// leaves it typed as the union across all event kinds, which does not line up with
// the `type` discriminant on the surrounding object.
const events: ReactorEmittedEvent[] = [
{ type: "reactor.start", seq: 1, data: {} as unknown as ReactorEmittedEvent["data"] },
{ type: "reactor.start", seq: 1, data: {} } as unknown as ReactorEmittedEvent,
{
type: "inference.tool_call.start",
seq: 2,
data: { name: "read_file" } as unknown as ReactorEmittedEvent["data"],
},
data: { name: "read_file" },
} as unknown as ReactorEmittedEvent,
{
type: "tool.done",
seq: 3,
data: {
result: { callId: "c1", content: "ok", isError: false },
} as unknown as ReactorEmittedEvent["data"],
},
},
} as unknown as ReactorEmittedEvent,
];

const received: ReactorEmittedEvent[] = [];
await consumeStream(makeStream(events), (event) => received.push(event));

expect(received.length).toBe(3);
expect(received[0].type).toBe("reactor.start");
expect(received[1].type).toBe("inference.tool_call.start");
expect(received[2].type).toBe("tool.done");
expect(received[0]?.type).toBe("reactor.start");
expect(received[1]?.type).toBe("inference.tool_call.start");
expect(received[2]?.type).toBe("tool.done");
});

test("consumeStream handles empty stream", async () => {
Expand Down
3 changes: 2 additions & 1 deletion tests/unit/subagent-session-store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,8 @@ describe("createTaskTool session recording", () => {
},
signal ?? new AbortController().signal,
);
return result.content;
if (typeof result === "string") return result;
return typeof result.content === "string" ? result.content : JSON.stringify(result.content);
}

test("records a session on spawn and completes it with the report", async () => {
Expand Down
Loading
Loading