style: apply biome formatting to merged changes

This commit is contained in:
can1357
2026-07-30 01:28:07 +02:00
parent 038d8372d7
commit 091f670ea0
15 changed files with 439 additions and 434 deletions
@@ -1746,39 +1746,36 @@ describe("AuthStorage codex oauth ranking", () => {
test.each([
["gpt-5.6-terra", "free", "enterprise"],
["gpt-5.6-terra-pro", "go", "pro"],
])(
"%s keeps a less-used %s account in ordinary ranking ahead of %s",
async (modelId, lowUsagePlan, highUsagePlan) => {
if (!authStorage) throw new Error("test setup failed");
])("%s keeps a less-used %s account in ordinary ranking ahead of %s", async (modelId, lowUsagePlan, highUsagePlan) => {
if (!authStorage) throw new Error("test setup failed");
await authStorage.set("openai-codex", [
{ type: "oauth", ...createCredential("acct-low-usage", "low-usage@example.com") },
{ type: "oauth", ...createCredential("acct-high-usage", "high-usage@example.com") },
]);
await authStorage.set("openai-codex", [
{ type: "oauth", ...createCredential("acct-low-usage", "low-usage@example.com") },
{ type: "oauth", ...createCredential("acct-high-usage", "high-usage@example.com") },
]);
usageByAccount.set(
"acct-low-usage",
createCodexUsageReport({
accountId: "acct-low-usage",
primary: { usedFraction: 0.01, resetInMs: 30 * 60 * 1000 },
secondary: { usedFraction: 0.01, resetInMs: 6 * 24 * 60 * 60 * 1000 },
metadata: { planType: lowUsagePlan, email: "low-usage@example.com" },
}),
);
usageByAccount.set(
"acct-high-usage",
createCodexUsageReport({
accountId: "acct-high-usage",
primary: { usedFraction: 0.8, resetInMs: 30 * 60 * 1000 },
secondary: { usedFraction: 0.8, resetInMs: 6 * 24 * 60 * 60 * 1000 },
metadata: { planType: highUsagePlan, email: "high-usage@example.com" },
}),
);
usageByAccount.set(
"acct-low-usage",
createCodexUsageReport({
accountId: "acct-low-usage",
primary: { usedFraction: 0.01, resetInMs: 30 * 60 * 1000 },
secondary: { usedFraction: 0.01, resetInMs: 6 * 24 * 60 * 60 * 1000 },
metadata: { planType: lowUsagePlan, email: "low-usage@example.com" },
}),
);
usageByAccount.set(
"acct-high-usage",
createCodexUsageReport({
accountId: "acct-high-usage",
primary: { usedFraction: 0.8, resetInMs: 30 * 60 * 1000 },
secondary: { usedFraction: 0.8, resetInMs: 6 * 24 * 60 * 60 * 1000 },
metadata: { planType: highUsagePlan, email: "high-usage@example.com" },
}),
);
const apiKey = await authStorage.getApiKey("openai-codex", undefined, { modelId });
expect(apiKey).toBe("api-acct-low-usage");
},
);
const apiKey = await authStorage.getApiKey("openai-codex", undefined, { modelId });
expect(apiKey).toBe("api-acct-low-usage");
});
test("keeps an eligible Codex session credential when usage headroom makes its sibling rank better", async () => {
if (!authStorage) throw new Error("test setup failed");
@@ -2365,65 +2362,62 @@ describe("AuthStorage codex oauth ranking", () => {
test.each([
["gpt-5.6-sol", 0.06, 0.09, true, false, 1, 1, false, true, "spark"],
["gpt-5.3-codex-spark", 1, 1, false, true, 0.06, 0.09, true, false, "chat"],
] as const)(
"reports %s healthy after splitting a legacy shared block when only its meter has headroom",
async (modelId, chatPrimary, chatSecondary, chatAllowed, chatLimitReached, sparkPrimary, sparkSecondary, sparkAllowed, sparkLimitReached, remainingBlockScope) => {
if (!authStorage || !store?.listCredentialBlocks) throw new Error("test setup failed");
await authStorage.set("openai-codex", [
{ type: "oauth", ...createCredential("acct-legacy-meter", "legacy-meter@example.com") },
]);
const [row] = store.listAuthCredentials("openai-codex");
if (!row) throw new Error("expected credential row");
const blockedUntilMs = Date.now() + WEEK_MS;
insertLegacyCodexSharedBlock(
dbPath,
row.id,
blockedUntilMs,
Math.floor((Date.now() - STALE_BLOCK_GUARD_MS) / 1000),
);
usageByAccount.set(
"acct-legacy-meter",
addSparkUsage(
createCodexUsageReport({
] as const)("reports %s healthy after splitting a legacy shared block when only its meter has headroom", async (modelId, chatPrimary, chatSecondary, chatAllowed, chatLimitReached, sparkPrimary, sparkSecondary, sparkAllowed, sparkLimitReached, remainingBlockScope) => {
if (!authStorage || !store?.listCredentialBlocks) throw new Error("test setup failed");
await authStorage.set("openai-codex", [
{ type: "oauth", ...createCredential("acct-legacy-meter", "legacy-meter@example.com") },
]);
const [row] = store.listAuthCredentials("openai-codex");
if (!row) throw new Error("expected credential row");
const blockedUntilMs = Date.now() + WEEK_MS;
insertLegacyCodexSharedBlock(
dbPath,
row.id,
blockedUntilMs,
Math.floor((Date.now() - STALE_BLOCK_GUARD_MS) / 1000),
);
usageByAccount.set(
"acct-legacy-meter",
addSparkUsage(
createCodexUsageReport({
accountId: "acct-legacy-meter",
primary: { usedFraction: chatPrimary, resetInMs: FIVE_HOUR_MS },
secondary: { usedFraction: chatSecondary, resetInMs: WEEK_MS },
metadata: {
allowed: chatAllowed,
limitReached: chatLimitReached,
planType: "pro",
email: "legacy-meter@example.com",
accountId: "acct-legacy-meter",
primary: { usedFraction: chatPrimary, resetInMs: FIVE_HOUR_MS },
secondary: { usedFraction: chatSecondary, resetInMs: WEEK_MS },
metadata: {
allowed: chatAllowed,
limitReached: chatLimitReached,
planType: "pro",
email: "legacy-meter@example.com",
accountId: "acct-legacy-meter",
},
}),
sparkPrimary,
sparkSecondary,
{ allowed: sparkAllowed, limitReached: sparkLimitReached },
),
);
const health = await authStorage.getModelUsageHealth("openai-codex", {
modelId,
reserveFraction: 0.1,
});
expect(health).toMatchObject({
state: "healthy",
accounts: [
{
credentialId: row.id,
credentialType: "oauth",
state: "healthy",
},
],
});
expect(health.accounts[0]?.remainingFraction).toBeCloseTo(0.91, 10);
expect(store.listCredentialBlocks([row.id]).map(block => [block.blockScope, block.blockedUntilMs])).toEqual([
[remainingBlockScope, blockedUntilMs],
]);
expect(readLegacyCodexSharedBlock(dbPath, row.id)).toBe(blockedUntilMs);
},
);
}),
sparkPrimary,
sparkSecondary,
{ allowed: sparkAllowed, limitReached: sparkLimitReached },
),
);
const health = await authStorage.getModelUsageHealth("openai-codex", {
modelId,
reserveFraction: 0.1,
});
expect(health).toMatchObject({
state: "healthy",
accounts: [
{
credentialId: row.id,
credentialType: "oauth",
state: "healthy",
},
],
});
expect(health.accounts[0]?.remainingFraction).toBeCloseTo(0.91, 10);
expect(store.listCredentialBlocks([row.id]).map(block => [block.blockScope, block.blockedUntilMs])).toEqual([
[remainingBlockScope, blockedUntilMs],
]);
expect(readLegacyCodexSharedBlock(dbPath, row.id)).toBe(blockedUntilMs);
});
test("deletes only the recovered persisted Codex meter block", async () => {
if (!authStorage || !store?.upsertCredentialBlock || !store.getCredentialBlock) {
@@ -201,24 +201,25 @@ describe("openai-codex reasoning.context", () => {
// gpt-5.1-codex / gpt-5.3-codex / gpt-5.3-codex-spark reject `all_turns`
// ("Unsupported value: 'all_turns' is not supported with this model").
it.each(["gpt-5.1-codex", "gpt-5.3-codex", "gpt-5.3-codex-spark"])(
"omits the all_turns default for pre-5.4 model %s",
async modelId => {
const model = createCodexModel(modelId);
it.each([
"gpt-5.1-codex",
"gpt-5.3-codex",
"gpt-5.3-codex-spark",
])("omits the all_turns default for pre-5.4 model %s", async modelId => {
const model = createCodexModel(modelId);
const defaulted = await transformRequestBody({ model: model.id }, model, { reasoningEffort: "medium" });
expect(defaulted.reasoning).toBeDefined();
expect(defaulted.reasoning?.context).toBeUndefined();
expect("context" in (defaulted.reasoning ?? {})).toBe(false);
const defaulted = await transformRequestBody({ model: model.id }, model, { reasoningEffort: "medium" });
expect(defaulted.reasoning).toBeDefined();
expect(defaulted.reasoning?.context).toBeUndefined();
expect("context" in (defaulted.reasoning ?? {})).toBe(false);
// A supported override (current_turn/auto) is still honored.
const overridden = await transformRequestBody({ model: model.id }, model, {
reasoningEffort: "medium",
reasoningContext: "current_turn",
});
expect(overridden.reasoning?.context).toBe("current_turn");
},
);
// A supported override (current_turn/auto) is still honored.
const overridden = await transformRequestBody({ model: model.id }, model, {
reasoningEffort: "medium",
reasoningContext: "current_turn",
});
expect(overridden.reasoning?.context).toBe("current_turn");
});
it("suppresses an explicit all_turns override on a pre-5.4 model", async () => {
const model = createCodexModel("gpt-5.3-codex-spark");
@@ -254,23 +255,24 @@ describe("openai-codex reasoning.summary", () => {
// gpt-5.1-codex / gpt-5.3-codex / gpt-5.3-codex-spark reject `reasoning.summary`
// ("Unsupported parameter: 'reasoning.summary' is not supported with this model").
it.each(["gpt-5.1-codex", "gpt-5.3-codex", "gpt-5.3-codex-spark"])(
"omits reasoning.summary for pre-5.4 model %s",
async modelId => {
const model = createCodexModel(modelId);
it.each([
"gpt-5.1-codex",
"gpt-5.3-codex",
"gpt-5.3-codex-spark",
])("omits reasoning.summary for pre-5.4 model %s", async modelId => {
const model = createCodexModel(modelId);
const defaulted = await transformRequestBody({ model: model.id }, model, { reasoningEffort: "medium" });
expect(defaulted.reasoning).toBeDefined();
expect("summary" in (defaulted.reasoning ?? {})).toBe(false);
const defaulted = await transformRequestBody({ model: model.id }, model, { reasoningEffort: "medium" });
expect(defaulted.reasoning).toBeDefined();
expect("summary" in (defaulted.reasoning ?? {})).toBe(false);
// Even an explicit summary level is suppressed on unsupported ids.
const forced = await transformRequestBody({ model: model.id }, model, {
reasoningEffort: "medium",
reasoningSummary: "detailed",
});
expect("summary" in (forced.reasoning ?? {})).toBe(false);
},
);
// Even an explicit summary level is suppressed on unsupported ids.
const forced = await transformRequestBody({ model: model.id }, model, {
reasoningEffort: "medium",
reasoningSummary: "detailed",
});
expect("summary" in (forced.reasoning ?? {})).toBe(false);
});
});
describe("openai-codex Responses Lite input shaping", () => {
@@ -375,21 +377,23 @@ describe("openai-codex Responses Lite input shaping", () => {
expect(disabled.tools).toBeUndefined();
});
it.each(["gpt-5.3-codex-spark", "gpt-5.6-luna", "gpt-5.6-terra", "gpt-5.6-sol"])(
"preserves a forced computer function through Lite for %s",
async modelId => {
const model = createCodexModel(modelId);
const computer = { type: "function", name: "computer", parameters: { type: "object" } };
const other = { type: "function", name: "read", parameters: { type: "object" } };
const body = await transformRequestBody(
{ model: model.id, tools: [computer, other], tool_choice: { type: "function", name: "computer" } },
model,
{ responsesLite: true },
);
expect(body.input?.[0]).toEqual({ type: "additional_tools", role: "developer", tools: [computer] });
expect(body.tool_choice).toBe("required");
},
);
it.each([
"gpt-5.3-codex-spark",
"gpt-5.6-luna",
"gpt-5.6-terra",
"gpt-5.6-sol",
])("preserves a forced computer function through Lite for %s", async modelId => {
const model = createCodexModel(modelId);
const computer = { type: "function", name: "computer", parameters: { type: "object" } };
const other = { type: "function", name: "read", parameters: { type: "object" } };
const body = await transformRequestBody(
{ model: model.id, tools: [computer, other], tool_choice: { type: "function", name: "computer" } },
model,
{ responsesLite: true },
);
expect(body.input?.[0]).toEqual({ type: "additional_tools", role: "developer", tools: [computer] });
expect(body.tool_choice).toBe("required");
});
it("moves instructions and tools into input items under lite", async () => {
const model = createCodexModel("gpt-5.6-terra");
+14 -14
View File
@@ -38,23 +38,23 @@ describe("opencode-zen/-go resolver routes MiniMax M3 to openai-completions (iss
const npmAnthropic: ModelsDevModel = { provider: { npm: "@ai-sdk/anthropic" }, tool_call: true };
describe("opencode-zen", () => {
test.each([["minimax-m3"], ["minimax-m3-free"]])(
"%s resolves to openai-completions on /v1/chat/completions",
modelId => {
const resolved = zenDescriptor?.resolveApi?.(modelId, npmAnthropic);
expect(resolved).toEqual({ api: "openai-completions", baseUrl: OPENCODE_ZEN_BASE });
},
);
test.each([
["minimax-m3"],
["minimax-m3-free"],
])("%s resolves to openai-completions on /v1/chat/completions", modelId => {
const resolved = zenDescriptor?.resolveApi?.(modelId, npmAnthropic);
expect(resolved).toEqual({ api: "openai-completions", baseUrl: OPENCODE_ZEN_BASE });
});
});
describe("opencode-go", () => {
test.each([["minimax-m3"], ["minimax-m3-free"]])(
"%s resolves to openai-completions on /v1/chat/completions",
modelId => {
const resolved = goDescriptor?.resolveApi?.(modelId, npmAnthropic);
expect(resolved).toEqual({ api: "openai-completions", baseUrl: OPENCODE_GO_BASE });
},
);
test.each([
["minimax-m3"],
["minimax-m3-free"],
])("%s resolves to openai-completions on /v1/chat/completions", modelId => {
const resolved = goDescriptor?.resolveApi?.(modelId, npmAnthropic);
expect(resolved).toEqual({ api: "openai-completions", baseUrl: OPENCODE_GO_BASE });
});
});
test("opencode-zen /v1/models refresh routes a freshly-discovered M3 to openai-completions", async () => {
@@ -26,13 +26,14 @@ describe("opencode-go resolver routes 404-ing ids to openai-completions (issue #
// would route them to /v1/messages on opencode.ai/zen/go which 404s.
const npmAnthropic: ModelsDevModel = { provider: { npm: "@ai-sdk/anthropic" }, tool_call: true };
test.each([["minimax-m2.7"], ["qwen3.5-plus"], ["qwen3.6-plus"]])(
"%s resolves to openai-completions on /v1/chat/completions",
modelId => {
const resolved = descriptor?.resolveApi?.(modelId, npmAnthropic);
expect(resolved).toEqual({ api: "openai-completions", baseUrl: OPENCODE_GO_BASE });
},
);
test.each([
["minimax-m2.7"],
["qwen3.5-plus"],
["qwen3.6-plus"],
])("%s resolves to openai-completions on /v1/chat/completions", modelId => {
const resolved = descriptor?.resolveApi?.(modelId, npmAnthropic);
expect(resolved).toEqual({ api: "openai-completions", baseUrl: OPENCODE_GO_BASE });
});
test("minimax-m2.5 (control: works empirically) also resolves to openai-completions", () => {
// models.dev currently lists minimax-m2.5 without an explicit provider.npm,
+52 -51
View File
@@ -428,60 +428,61 @@ describe("LiteLLM provider discovery", () => {
expect(models?.find(model => model.id === "params-tools")?.supportsTools).toBe(true);
});
test.each([["all-team-models"], ["all-proxy-models"], ["no-default-models"]])(
"falls back from %s placeholder to v2 model info",
async sentinelModelId => {
const calls: string[] = [];
const fetchMock = vi.fn(async (input: string | URL | Request) => {
const url = inputUrl(input);
calls.push(url);
if (url === MODELS_DEV_URL) {
return Response.json({});
}
if (url === "http://primary:4000/model_group/info") {
return Response.json({ data: [makeLiteLLMSentinelPlaceholder(sentinelModelId)] });
}
if (url === "http://primary:4000/v2/model/info") {
return Response.json({
data: [
{
model_name: "example-real-model",
model_info: {
max_input_tokens: 200_000,
max_output_tokens: 12_000,
supports_vision: false,
supports_reasoning: true,
},
test.each([
["all-team-models"],
["all-proxy-models"],
["no-default-models"],
])("falls back from %s placeholder to v2 model info", async sentinelModelId => {
const calls: string[] = [];
const fetchMock = vi.fn(async (input: string | URL | Request) => {
const url = inputUrl(input);
calls.push(url);
if (url === MODELS_DEV_URL) {
return Response.json({});
}
if (url === "http://primary:4000/model_group/info") {
return Response.json({ data: [makeLiteLLMSentinelPlaceholder(sentinelModelId)] });
}
if (url === "http://primary:4000/v2/model/info") {
return Response.json({
data: [
{
model_name: "example-real-model",
model_info: {
max_input_tokens: 200_000,
max_output_tokens: 12_000,
supports_vision: false,
supports_reasoning: true,
},
],
});
}
if (url === "http://primary:4000/v1/models") {
throw new Error("/v1/models should not be called when v2 metadata succeeds");
}
throw new Error(`Unexpected URL: ${url}`);
}) as FetchImpl;
const options = litellmModelManagerOptions({
apiKey: "sk-rich",
baseUrl: "http://primary:4000/v1",
fetch: fetchMock,
});
},
],
});
}
if (url === "http://primary:4000/v1/models") {
throw new Error("/v1/models should not be called when v2 metadata succeeds");
}
throw new Error(`Unexpected URL: ${url}`);
}) as FetchImpl;
const options = litellmModelManagerOptions({
apiKey: "sk-rich",
baseUrl: "http://primary:4000/v1",
fetch: fetchMock,
});
const models = await options.fetchDynamicModels?.();
const models = await options.fetchDynamicModels?.();
expect(calls).toContain("http://primary:4000/model_group/info");
expect(calls).toContain("http://primary:4000/v2/model/info");
expect(calls).not.toContain("http://primary:4000/v1/models");
expect(models?.map(model => model.id)).toEqual(["example-real-model"]);
expect(models?.[0]).toMatchObject({
id: "example-real-model",
contextWindow: 200_000,
maxTokens: 12_000,
input: ["text"],
reasoning: true,
});
},
);
expect(calls).toContain("http://primary:4000/model_group/info");
expect(calls).toContain("http://primary:4000/v2/model/info");
expect(calls).not.toContain("http://primary:4000/v1/models");
expect(models?.map(model => model.id)).toEqual(["example-real-model"]);
expect(models?.[0]).toMatchObject({
id: "example-real-model",
contextWindow: 200_000,
maxTokens: 12_000,
input: ["text"],
reasoning: true,
});
});
test("filters all-team-models placeholder from mixed model_group info", async () => {
const calls: string[] = [];
@@ -4035,60 +4035,60 @@ describe("advisor", () => {
expect(promptInputs[1]).toContain("keep me");
});
it.each(["success", "error"] as const)(
"releases blocked %s hooks so reset can run replacement work",
async hookKind => {
const hookStarted = Promise.withResolvers<void>();
const releaseHook = Promise.withResolvers<void>();
const replacementPromptStarted = Promise.withResolvers<void>();
let promptCalls = 0;
let hookCalls = 0;
const blockHook = async () => {
if (++hookCalls !== 1) return;
hookStarted.resolve();
await releaseHook.promise;
};
const agent: AdvisorAgent = {
prompt: async () => {
promptCalls++;
if (promptCalls === 1 && hookKind === "error") throw new Error("provider failure");
if (promptCalls === 2) replacementPromptStarted.resolve();
},
abort: () => {},
reset: () => {},
state: { messages: [] },
};
const runtime = new AdvisorRuntime(agent, {
snapshotMessages: () => [],
enqueueAdvice: () => {},
...(hookKind === "success"
? { onTurnSuccess: blockHook }
: {
onTurnError: async () => {
await blockHook();
return false;
},
}),
});
it.each([
"success",
"error",
] as const)("releases blocked %s hooks so reset can run replacement work", async hookKind => {
const hookStarted = Promise.withResolvers<void>();
const releaseHook = Promise.withResolvers<void>();
const replacementPromptStarted = Promise.withResolvers<void>();
let promptCalls = 0;
let hookCalls = 0;
const blockHook = async () => {
if (++hookCalls !== 1) return;
hookStarted.resolve();
await releaseHook.promise;
};
const agent: AdvisorAgent = {
prompt: async () => {
promptCalls++;
if (promptCalls === 1 && hookKind === "error") throw new Error("provider failure");
if (promptCalls === 2) replacementPromptStarted.resolve();
},
abort: () => {},
reset: () => {},
state: { messages: [] },
};
const runtime = new AdvisorRuntime(agent, {
snapshotMessages: () => [],
enqueueAdvice: () => {},
...(hookKind === "success"
? { onTurnSuccess: blockHook }
: {
onTurnError: async () => {
await blockHook();
return false;
},
}),
});
runtime.onTurnEnd([{ role: "user", content: "old session", timestamp: 1 } as AgentMessage]);
await hookStarted.promise;
const pause = runtime.pauseForSessionTransition();
const pausedQuickly = await Promise.race([pause.then(() => true), Bun.sleep(50).then(() => false)]);
runtime.reset();
runtime.onTurnEnd([{ role: "user", content: "replacement session", timestamp: 2 } as AgentMessage]);
const replacementRan = await Promise.race([
replacementPromptStarted.promise.then(() => true),
Bun.sleep(50).then(() => false),
]);
releaseHook.resolve();
await pause;
runtime.dispose();
runtime.onTurnEnd([{ role: "user", content: "old session", timestamp: 1 } as AgentMessage]);
await hookStarted.promise;
const pause = runtime.pauseForSessionTransition();
const pausedQuickly = await Promise.race([pause.then(() => true), Bun.sleep(50).then(() => false)]);
runtime.reset();
runtime.onTurnEnd([{ role: "user", content: "replacement session", timestamp: 2 } as AgentMessage]);
const replacementRan = await Promise.race([
replacementPromptStarted.promise.then(() => true),
Bun.sleep(50).then(() => false),
]);
releaseHook.resolve();
await pause;
runtime.dispose();
expect(pausedQuickly).toBe(true);
expect(replacementRan).toBe(true);
},
);
expect(pausedQuickly).toBe(true);
expect(replacementRan).toBe(true);
});
it("aborts retry backoff before pausing for a session transition", async () => {
const recoveryStarted = Promise.withResolvers<void>();
const agent: AdvisorAgent = {
@@ -849,57 +849,56 @@ it("allow_always: caches decision and calls bridge only once for subsequent exec
expect(bashTool.executeCalls).toBe(2);
});
it.each(boundaryCases)(
"%s permission decisions prompt again after a successful %s session boundary",
async (decision, transition) => {
const bashTool = makeFakeTool("bash");
const bridge = makeBridge({ outcome: "selected", optionId: decision, kind: decision });
const permissionSpy = spyOn(bridge, "requestPermission");
session = await createSession([bashTool], bridge, {}, { persist: true });
it.each(
boundaryCases,
)("%s permission decisions prompt again after a successful %s session boundary", async (decision, transition) => {
const bashTool = makeFakeTool("bash");
const bridge = makeBridge({ outcome: "selected", optionId: decision, kind: decision });
const permissionSpy = spyOn(bridge, "requestPermission");
session = await createSession([bashTool], bridge, {}, { persist: true });
await session.setActiveToolsByName(["bash"]);
const wrappedBash = session.agent.state.tools.find(tool => tool.name === "bash");
if (!wrappedBash) throw new Error("Expected wrapped bash tool");
await session.setActiveToolsByName(["bash"]);
const wrappedBash = session.agent.state.tools.find(tool => tool.name === "bash");
if (!wrappedBash) throw new Error("Expected wrapped bash tool");
for (let callIndex = 0; callIndex < 2; callIndex++) {
if (callIndex === 1) {
if (transition === "new") {
expect(await session.newSession()).toBe(true);
} else {
const targetId = `permission-target-${Bun.nanoseconds()}`;
const targetPath = `${tempDir.path()}/${targetId}.jsonl`;
await Bun.write(
targetPath,
`${JSON.stringify({
type: "session",
version: 3,
id: targetId,
timestamp: new Date().toISOString(),
cwd: tempDir.path(),
})}\n`,
);
expect(await session.switchSession(targetPath)).toBe(true);
}
}
const execution = wrappedBash.execute(
`call-${callIndex}`,
{ command: "echo boundary" },
undefined,
undefined as never,
undefined as never,
);
if (decision === "reject_always") {
await expect(execution).rejects.toThrow(/rejected by user/);
for (let callIndex = 0; callIndex < 2; callIndex++) {
if (callIndex === 1) {
if (transition === "new") {
expect(await session.newSession()).toBe(true);
} else {
await execution;
const targetId = `permission-target-${Bun.nanoseconds()}`;
const targetPath = `${tempDir.path()}/${targetId}.jsonl`;
await Bun.write(
targetPath,
`${JSON.stringify({
type: "session",
version: 3,
id: targetId,
timestamp: new Date().toISOString(),
cwd: tempDir.path(),
})}\n`,
);
expect(await session.switchSession(targetPath)).toBe(true);
}
}
expect(permissionSpy).toHaveBeenCalledTimes(2);
expect(bashTool.executeCalls).toBe(decision === "allow_always" ? 2 : 0);
},
);
const execution = wrappedBash.execute(
`call-${callIndex}`,
{ command: "echo boundary" },
undefined,
undefined as never,
undefined as never,
);
if (decision === "reject_always") {
await expect(execution).rejects.toThrow(/rejected by user/);
} else {
await execution;
}
}
expect(permissionSpy).toHaveBeenCalledTimes(2);
expect(bashTool.executeCalls).toBe(decision === "allow_always" ? 2 : 0);
});
// ---------------------------------------------------------------------------
// 4. Read tool not gated: bridge never called even when bridge is set
@@ -203,67 +203,68 @@ describe("AgentSession bash session ownership", () => {
).toBe(true);
});
it.each(["new", "switch", "branch"] as const)(
"records a late bash result in its original session after %s",
async transition => {
const sessionDir = path.join(tempDir.path(), "sessions");
const { completion, emitUserBash, extensionRunner } = createGatedBashRunner();
createSession(SessionManager.create(tempDir.path(), sessionDir), extensionRunner);
const oldSessionFile = await seedPersistedSession();
const oldSessionId = session.sessionId;
it.each([
"new",
"switch",
"branch",
] as const)("records a late bash result in its original session after %s", async transition => {
const sessionDir = path.join(tempDir.path(), "sessions");
const { completion, emitUserBash, extensionRunner } = createGatedBashRunner();
createSession(SessionManager.create(tempDir.path(), sessionDir), extensionRunner);
const oldSessionFile = await seedPersistedSession();
const oldSessionId = session.sessionId;
const bashPromise = session.executeBash("old-session-command");
expect(emitUserBash).toHaveBeenCalledTimes(1);
const bashPromise = session.executeBash("old-session-command");
expect(emitUserBash).toHaveBeenCalledTimes(1);
switch (transition) {
case "new":
await session.newSession();
break;
case "switch": {
const targetManager = SessionManager.create(tempDir.path(), sessionDir);
targetManager.appendMessage({ role: "user", content: "target", timestamp: Date.now() });
targetManager.appendMessage(createAssistantMessage("target reply"));
await targetManager.ensureOnDisk();
const targetFile = targetManager.getSessionFile();
if (!targetFile) throw new Error("Expected target session file");
await targetManager.close();
await session.switchSession(targetFile);
break;
}
case "branch": {
const userEntry = session.sessionManager
.getEntries()
.find(entry => entry.type === "message" && entry.message.role === "user");
if (!userEntry) throw new Error("Expected user entry for branch");
await session.branch(userEntry.id);
break;
}
switch (transition) {
case "new":
await session.newSession();
break;
case "switch": {
const targetManager = SessionManager.create(tempDir.path(), sessionDir);
targetManager.appendMessage({ role: "user", content: "target", timestamp: Date.now() });
targetManager.appendMessage(createAssistantMessage("target reply"));
await targetManager.ensureOnDisk();
const targetFile = targetManager.getSessionFile();
if (!targetFile) throw new Error("Expected target session file");
await targetManager.close();
await session.switchSession(targetFile);
break;
}
case "branch": {
const userEntry = session.sessionManager
.getEntries()
.find(entry => entry.type === "message" && entry.message.role === "user");
if (!userEntry) throw new Error("Expected user entry for branch");
await session.branch(userEntry.id);
break;
}
}
expect(session.sessionId).not.toBe(oldSessionId);
completion.resolve({ result: bashResult });
await bashPromise;
expect(session.sessionId).not.toBe(oldSessionId);
completion.resolve({ result: bashResult });
await bashPromise;
expect(
session.messages.some(
message => message.role === "bashExecution" && message.command === "old-session-command",
),
).toBe(false);
expect(
session.messages.some(
message => message.role === "bashExecution" && message.command === "old-session-command",
),
).toBe(false);
const oldSession = await SessionManager.open(oldSessionFile, sessionDir, undefined, {
initialCwd: tempDir.path(),
suppressBreadcrumb: true,
});
additionalManagers.push(oldSession);
const oldMessages = oldSession.getBranch().flatMap(entry => (entry.type === "message" ? [entry.message] : []));
expect(oldMessages.slice(-3).map(message => message.role)).toEqual(["user", "assistant", "bashExecution"]);
expect(oldMessages.at(-1)).toMatchObject({
role: "bashExecution",
command: "old-session-command",
output: "old-output",
});
},
);
const oldSession = await SessionManager.open(oldSessionFile, sessionDir, undefined, {
initialCwd: tempDir.path(),
suppressBreadcrumb: true,
});
additionalManagers.push(oldSession);
const oldMessages = oldSession.getBranch().flatMap(entry => (entry.type === "message" ? [entry.message] : []));
expect(oldMessages.slice(-3).map(message => message.role)).toEqual(["user", "assistant", "bashExecution"]);
expect(oldMessages.at(-1)).toMatchObject({
role: "bashExecution",
command: "old-session-command",
output: "old-output",
});
});
it("stores minimized bash output with the originating session", async () => {
const sessionDir = path.join(tempDir.path(), "sessions");
@@ -1878,13 +1878,14 @@ describe("InteractiveMode plan review rendering", () => {
// `#approvePlan`'s `finally`. No aborted message_end is required to consume it,
// so a stranded flag could otherwise silence the next unrelated abort. One
// parametrized case per outcome keeps ok/cancelled/failed each covered.
it.each(["ok", "cancelled", "failed"] as const)(
"B1-B3: Approve and compact context + %s outcome → flag cleared by finally",
async outcome => {
await approveWithCompact(outcome);
expect(session.isPlanInternalAbortPending).toBe(false);
},
);
it.each([
"ok",
"cancelled",
"failed",
] as const)("B1-B3: Approve and compact context + %s outcome → flag cleared by finally", async outcome => {
await approveWithCompact(outcome);
expect(session.isPlanInternalAbortPending).toBe(false);
});
it("B4: Approve and compact context + handleCompactCommand throws → showError surfaces the failure AND flag cleared by finally before the outer catch", async () => {
// `handlePlanApproval` wraps `#approvePlan` in a try/catch
@@ -315,27 +315,27 @@ describe("MemoryProtocolHandler", () => {
});
});
it.each(["memory://root/skills/**/../*.md", "memory://root/skills/**/%2e%2e/*.md"])(
"rejects traversal in a memory glob suffix: %s",
async pattern => {
await withMemoryFixture(async ({ cwd }) => {
await expect(createGlobTool(cwd).execute("memory-glob-traversal", { path: pattern })).rejects.toThrow(
/traversal/i,
);
});
},
);
it.each([
"memory://root/skills/**/../*.md",
"memory://root/skills/**/%2e%2e/*.md",
])("rejects traversal in a memory glob suffix: %s", async pattern => {
await withMemoryFixture(async ({ cwd }) => {
await expect(createGlobTool(cwd).execute("memory-glob-traversal", { path: pattern })).rejects.toThrow(
/traversal/i,
);
});
});
it.each(["memory://root/skills/**/demo%2fnested/*.md", "memory://root/skills/**/demo%5cnested/*.md"])(
"rejects encoded separators in a memory glob suffix: %s",
async pattern => {
await withMemoryFixture(async ({ cwd }) => {
await expect(createGlobTool(cwd).execute("memory-glob-separator", { path: pattern })).rejects.toThrow(
/encoded path separator/i,
);
});
},
);
it.each([
"memory://root/skills/**/demo%2fnested/*.md",
"memory://root/skills/**/demo%5cnested/*.md",
])("rejects encoded separators in a memory glob suffix: %s", async pattern => {
await withMemoryFixture(async ({ cwd }) => {
await expect(createGlobTool(cwd).execute("memory-glob-separator", { path: pattern })).rejects.toThrow(
/encoded path separator/i,
);
});
});
it("throws clear error for missing files", async () => {
await withMemoryFixture(async () => {
+1 -4
View File
@@ -322,10 +322,7 @@ describe("RPC fast mode with unsupported Fireworks model and priority tier", ()
beforeEach(async () => {
sessionDir = path.join(os.tmpdir(), `omp-rpc-fast-mode-test-${Snowflake.next()}`);
await Bun.write(
path.join(sessionDir, "config.yml"),
["providers:", " fireworksTier: priority", ""].join("\n"),
);
await Bun.write(path.join(sessionDir, "config.yml"), ["providers:", " fireworksTier: priority", ""].join("\n"));
client = new RpcClient({
cliPath: path.join(import.meta.dir, "..", "src", "cli.ts"),
cwd: path.join(import.meta.dir, ".."),
@@ -6,8 +6,8 @@ import { AsyncJobManager } from "@oh-my-pi/pi-coding-agent/async/job-manager";
import { ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry";
import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings";
import { createAgentSession, type ExtensionFactory } from "@oh-my-pi/pi-coding-agent/sdk";
import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage";
import type { AsyncJobSnapshot } from "@oh-my-pi/pi-coding-agent/session/agent-session";
import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage";
import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils";
import { type } from "arktype";
@@ -40,10 +40,7 @@ describe("AsyncJobManager singleton across concurrent top-level sessions", () =>
AsyncJobManager.resetForTests();
});
async function spawnTopLevelSession(
extraSettings?: Record<string, unknown>,
extensions: ExtensionFactory[] = [],
) {
async function spawnTopLevelSession(extraSettings?: Record<string, unknown>, extensions: ExtensionFactory[] = []) {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), `pi-sdk-async-singleton-${Snowflake.next()}-`));
tempDirs.push(tempDir);
const cwd = path.join(tempDir, `project-${Snowflake.next()}`);
@@ -97,19 +97,22 @@ describe("task async preflight", () => {
spawns: "scout",
expectation: "Cannot spawn 'task'",
},
])(
"returns $name policy errors before registering an async job",
async ({ name, params, settings, spawns, expectation }) => {
mockDiscovery();
const jobs = manager();
const tool = await TaskTool.create(createSession({ manager: jobs, settings, spawns }));
])("returns $name policy errors before registering an async job", async ({
name,
params,
settings,
spawns,
expectation,
}) => {
mockDiscovery();
const jobs = manager();
const tool = await TaskTool.create(createSession({ manager: jobs, settings, spawns }));
const result = await tool.execute("preflight", params as TaskParams);
const result = await tool.execute("preflight", params as TaskParams);
expect(textOf(result)).toContain(expectation);
expect(jobs.getJob(name)).toBeUndefined();
},
);
expect(textOf(result)).toContain(expectation);
expect(jobs.getJob(name)).toBeUndefined();
});
it("rejects an invalid async batch atomically before dispatching any item", async () => {
mockDiscovery();
@@ -427,24 +427,25 @@ describe("title generator", () => {
expect(title).toBe("Fix login button on mobile");
});
it.each(["Here's a thinking process:", "Thinking process:", "Reasoning process:"])(
"rejects a markerless prose thinking preamble: %s",
async responseText => {
const model = getModelFor("deepseek", "deepseek-v4-pro");
vi.spyOn(ai, "completeSimple").mockResolvedValue({
stopReason: "stop",
content: [{ type: "text", text: responseText }],
} as never);
it.each([
"Here's a thinking process:",
"Thinking process:",
"Reasoning process:",
])("rejects a markerless prose thinking preamble: %s", async responseText => {
const model = getModelFor("deepseek", "deepseek-v4-pro");
vi.spyOn(ai, "completeSimple").mockResolvedValue({
stopReason: "stop",
content: [{ type: "text", text: responseText }],
} as never);
const title = await generateSessionTitle(
"the login button is broken on mobile",
createRegistry(model),
createSettings(model),
);
const title = await generateSessionTitle(
"the login button is broken on mobile",
createRegistry(model),
createSettings(model),
);
expect(title).toBeNull();
},
);
expect(title).toBeNull();
});
it("preserves a markerless title that mentions a <think> tag", async () => {
const model = getModelFor("deepseek", "deepseek-v4-pro");
@@ -114,21 +114,27 @@ describe("default echo/printf redirect rule", () => {
describe("default hub start rules", () => {
const tools = ["hub"];
it.each(["bun run dev", "vite --host 0.0.0.0", "lldb ./app", "bun test --watch", "nohup server", "server &"])(
"routes %s to hub start",
command => {
const result = checkBashInterception(command, tools, DEFAULT_BASH_INTERCEPTOR_RULES);
expect(result.block).toBe(true);
expect(result.suggestedTool).toBe("hub");
},
);
it.each([
"bun run dev",
"vite --host 0.0.0.0",
"lldb ./app",
"bun test --watch",
"nohup server",
"server &",
])("routes %s to hub start", command => {
const result = checkBashInterception(command, tools, DEFAULT_BASH_INTERCEPTOR_RULES);
expect(result.block).toBe(true);
expect(result.suggestedTool).toBe("hub");
});
it.each(["git diff -w", "docker compose up -d", "bun test", "printf 'server &'"])(
"does not misclassify finite command %s",
command => {
expect(checkBashInterception(command, tools, DEFAULT_BASH_INTERCEPTOR_RULES).block).toBe(false);
},
);
it.each([
"git diff -w",
"docker compose up -d",
"bun test",
"printf 'server &'",
])("does not misclassify finite command %s", command => {
expect(checkBashInterception(command, tools, DEFAULT_BASH_INTERCEPTOR_RULES).block).toBe(false);
});
});
describe("BashTool argument validation", () => {