mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-23 10:55:31 -06:00
refactor: compact recovery ownership and model metadata (#126751)
* refactor: compact recovery ownership and model metadata * chore: respect release-owned changelog gate * test: skip unsupported Windows permission assertions
This commit is contained in:
committed by
GitHub
parent
aafa61be65
commit
3c5cecee06
@@ -86,16 +86,9 @@
|
||||
"models": [
|
||||
{
|
||||
"id": "kimi-k2.5",
|
||||
"name": "kimi-k2.5",
|
||||
"status": "deprecated",
|
||||
"reasoning": true,
|
||||
"input": ["text", "image"],
|
||||
"cost": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"contextWindow": 262144,
|
||||
"maxTokens": 8192,
|
||||
"compat": {
|
||||
@@ -105,15 +98,8 @@
|
||||
},
|
||||
{
|
||||
"id": "kimi-k2.6",
|
||||
"name": "kimi-k2.6",
|
||||
"reasoning": true,
|
||||
"input": ["text", "image"],
|
||||
"cost": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"contextWindow": 262144,
|
||||
"maxTokens": 8192,
|
||||
"compat": {
|
||||
@@ -123,15 +109,8 @@
|
||||
},
|
||||
{
|
||||
"id": "kimi-k2.7-code",
|
||||
"name": "kimi-k2.7-code",
|
||||
"reasoning": true,
|
||||
"input": ["text", "image"],
|
||||
"cost": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"contextWindow": 262144,
|
||||
"maxTokens": 8192,
|
||||
"compat": {
|
||||
@@ -141,7 +120,6 @@
|
||||
},
|
||||
{
|
||||
"id": "kimi-k3",
|
||||
"name": "kimi-k3",
|
||||
"reasoning": true,
|
||||
"input": ["text", "image"],
|
||||
"cost": {
|
||||
@@ -159,15 +137,8 @@
|
||||
},
|
||||
{
|
||||
"id": "deepseek-v4-flash",
|
||||
"name": "deepseek-v4-flash",
|
||||
"reasoning": true,
|
||||
"input": ["text"],
|
||||
"cost": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"contextWindow": 1048576,
|
||||
"maxTokens": 8192,
|
||||
"compat": {
|
||||
@@ -178,15 +149,8 @@
|
||||
},
|
||||
{
|
||||
"id": "deepseek-v4-flash:0731",
|
||||
"name": "deepseek-v4-flash:0731",
|
||||
"reasoning": true,
|
||||
"input": ["text"],
|
||||
"cost": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"contextWindow": 1048576,
|
||||
"maxTokens": 8192,
|
||||
"compat": {
|
||||
@@ -196,15 +160,8 @@
|
||||
},
|
||||
{
|
||||
"id": "deepseek-v4-flash:preview",
|
||||
"name": "deepseek-v4-flash:preview",
|
||||
"reasoning": true,
|
||||
"input": ["text"],
|
||||
"cost": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"contextWindow": 1048576,
|
||||
"maxTokens": 8192,
|
||||
"compat": {
|
||||
@@ -214,15 +171,8 @@
|
||||
},
|
||||
{
|
||||
"id": "deepseek-v4-pro",
|
||||
"name": "deepseek-v4-pro",
|
||||
"reasoning": true,
|
||||
"input": ["text"],
|
||||
"cost": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"contextWindow": 1048576,
|
||||
"maxTokens": 8192,
|
||||
"compat": {
|
||||
@@ -233,15 +183,8 @@
|
||||
},
|
||||
{
|
||||
"id": "deepseek-v4-pro:0813",
|
||||
"name": "deepseek-v4-pro:0813",
|
||||
"reasoning": true,
|
||||
"input": ["text"],
|
||||
"cost": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"contextWindow": 1048576,
|
||||
"maxTokens": 8192,
|
||||
"compat": {
|
||||
@@ -251,15 +194,8 @@
|
||||
},
|
||||
{
|
||||
"id": "deepseek-v4-pro:preview",
|
||||
"name": "deepseek-v4-pro:preview",
|
||||
"reasoning": true,
|
||||
"input": ["text"],
|
||||
"cost": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"contextWindow": 524288,
|
||||
"maxTokens": 8192,
|
||||
"compat": {
|
||||
@@ -269,15 +205,8 @@
|
||||
},
|
||||
{
|
||||
"id": "gemma4",
|
||||
"name": "gemma4",
|
||||
"reasoning": true,
|
||||
"input": ["text", "image"],
|
||||
"cost": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"contextWindow": 262144,
|
||||
"maxTokens": 8192,
|
||||
"compat": {
|
||||
@@ -287,15 +216,8 @@
|
||||
},
|
||||
{
|
||||
"id": "gemma4:31b",
|
||||
"name": "gemma4:31b",
|
||||
"reasoning": true,
|
||||
"input": ["text", "image"],
|
||||
"cost": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"contextWindow": 262144,
|
||||
"maxTokens": 8192,
|
||||
"compat": {
|
||||
@@ -305,15 +227,8 @@
|
||||
},
|
||||
{
|
||||
"id": "glm-5.1",
|
||||
"name": "glm-5.1",
|
||||
"reasoning": true,
|
||||
"input": ["text"],
|
||||
"cost": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"contextWindow": 202752,
|
||||
"maxTokens": 8192,
|
||||
"compat": {
|
||||
@@ -324,15 +239,8 @@
|
||||
},
|
||||
{
|
||||
"id": "glm-5.2",
|
||||
"name": "glm-5.2",
|
||||
"reasoning": true,
|
||||
"input": ["text"],
|
||||
"cost": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"contextWindow": 1000000,
|
||||
"maxTokens": 8192,
|
||||
"compat": {
|
||||
@@ -343,15 +251,8 @@
|
||||
},
|
||||
{
|
||||
"id": "gpt-oss:120b",
|
||||
"name": "gpt-oss:120b",
|
||||
"reasoning": true,
|
||||
"input": ["text"],
|
||||
"cost": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"contextWindow": 131072,
|
||||
"maxTokens": 8192,
|
||||
"compat": {
|
||||
@@ -361,15 +262,8 @@
|
||||
},
|
||||
{
|
||||
"id": "gpt-oss:20b",
|
||||
"name": "gpt-oss:20b",
|
||||
"reasoning": true,
|
||||
"input": ["text"],
|
||||
"cost": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"contextWindow": 131072,
|
||||
"maxTokens": 8192,
|
||||
"compat": {
|
||||
@@ -379,15 +273,8 @@
|
||||
},
|
||||
{
|
||||
"id": "minimax-m2.7",
|
||||
"name": "minimax-m2.7",
|
||||
"reasoning": true,
|
||||
"input": ["text"],
|
||||
"cost": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"contextWindow": 196608,
|
||||
"maxTokens": 8192,
|
||||
"compat": {
|
||||
@@ -397,15 +284,8 @@
|
||||
},
|
||||
{
|
||||
"id": "minimax-m3",
|
||||
"name": "minimax-m3",
|
||||
"reasoning": true,
|
||||
"input": ["text", "image"],
|
||||
"cost": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"contextWindow": 524288,
|
||||
"maxTokens": 8192,
|
||||
"compat": {
|
||||
@@ -415,15 +295,8 @@
|
||||
},
|
||||
{
|
||||
"id": "mistral-large-3:675b",
|
||||
"name": "mistral-large-3:675b",
|
||||
"reasoning": false,
|
||||
"input": ["text", "image"],
|
||||
"cost": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"contextWindow": 262144,
|
||||
"maxTokens": 8192,
|
||||
"compat": {
|
||||
@@ -433,15 +306,8 @@
|
||||
},
|
||||
{
|
||||
"id": "nemotron-3-nano:30b",
|
||||
"name": "nemotron-3-nano:30b",
|
||||
"reasoning": true,
|
||||
"input": ["text"],
|
||||
"cost": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"contextWindow": 262144,
|
||||
"maxTokens": 8192,
|
||||
"compat": {
|
||||
@@ -451,15 +317,8 @@
|
||||
},
|
||||
{
|
||||
"id": "nemotron-3-super",
|
||||
"name": "nemotron-3-super",
|
||||
"reasoning": true,
|
||||
"input": ["text"],
|
||||
"cost": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"contextWindow": 262144,
|
||||
"maxTokens": 8192,
|
||||
"compat": {
|
||||
@@ -469,15 +328,8 @@
|
||||
},
|
||||
{
|
||||
"id": "nemotron-3-ultra",
|
||||
"name": "nemotron-3-ultra",
|
||||
"reasoning": true,
|
||||
"input": ["text"],
|
||||
"cost": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"contextWindow": 262144,
|
||||
"maxTokens": 8192,
|
||||
"compat": {
|
||||
@@ -487,15 +339,8 @@
|
||||
},
|
||||
{
|
||||
"id": "qwen3.5",
|
||||
"name": "qwen3.5",
|
||||
"reasoning": true,
|
||||
"input": ["text", "image"],
|
||||
"cost": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"contextWindow": 262144,
|
||||
"maxTokens": 8192,
|
||||
"compat": {
|
||||
@@ -505,15 +350,8 @@
|
||||
},
|
||||
{
|
||||
"id": "qwen3.5:397b",
|
||||
"name": "qwen3.5:397b",
|
||||
"reasoning": true,
|
||||
"input": ["text", "image"],
|
||||
"cost": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"contextWindow": 262144,
|
||||
"maxTokens": 8192,
|
||||
"compat": {
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
getAgentEventLifecycleGeneration,
|
||||
rotateAgentEventLifecycleGeneration,
|
||||
} from "../../infra/agent-events.js";
|
||||
import * as recoveryOwnerRelease from "./main-session-recovery-owner-release.js";
|
||||
import {
|
||||
claimMainSessionRecoveryOwner,
|
||||
commitMainSessionRecovery,
|
||||
@@ -403,10 +404,10 @@ describe("main session recovery store", () => {
|
||||
},
|
||||
);
|
||||
|
||||
const onDeferredSuccess = vi.fn();
|
||||
const immediateRelease = releaseMainSessionRecoveryOwner(claim.lease, {
|
||||
onDeferredSuccess,
|
||||
});
|
||||
const schedulePending = vi
|
||||
.spyOn(recoveryOwnerRelease, "scheduleMainSessionRecoveryPendingTarget")
|
||||
.mockImplementation(() => {});
|
||||
const immediateRelease = releaseMainSessionRecoveryOwner(claim.lease);
|
||||
const immediateReleaseRejected = expect(immediateRelease).rejects.toThrow(
|
||||
"transient session-store failure",
|
||||
);
|
||||
@@ -418,11 +419,13 @@ describe("main session recovery store", () => {
|
||||
await vi.waitFor(() => {
|
||||
expect(read().mainRestartRecovery?.foregroundClaims).toBeUndefined();
|
||||
});
|
||||
expect(onDeferredSuccess).toHaveBeenCalledWith({
|
||||
sessionId: "session-1",
|
||||
sessionKey,
|
||||
storePath,
|
||||
});
|
||||
await vi.waitFor(() =>
|
||||
expect(schedulePending).toHaveBeenCalledWith({
|
||||
sessionId: "session-1",
|
||||
sessionKey,
|
||||
storePath,
|
||||
}),
|
||||
);
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
|
||||
@@ -340,35 +340,8 @@ async function releaseMainSessionRecoveryOwnerWithRetries(
|
||||
return { sessionId: entry.sessionId, sessionKey, storePath: lease.storePath };
|
||||
}
|
||||
|
||||
function scheduleMainSessionRecoveryOwnerRelease(
|
||||
lease: MainSessionRecoveryOwnerLease,
|
||||
onDeferredSuccess?: (
|
||||
pending: MainSessionRecoveryPendingTarget | undefined,
|
||||
) => void | Promise<void>,
|
||||
): void {
|
||||
// A token is process-owned but durably blocks recovery. Keep exact-token
|
||||
// cleanup alive through transient writer outages until release or restart.
|
||||
scheduleMainSessionRecoveryMutation({
|
||||
mutation: () => releaseMainSessionRecoveryOwnerWithRetries(lease),
|
||||
onSuccess:
|
||||
onDeferredSuccess ??
|
||||
(async (pending) => {
|
||||
if (pending) {
|
||||
const { scheduleMainSessionRecoveryPendingTarget } =
|
||||
await import("./main-session-recovery-owner-release.js");
|
||||
scheduleMainSessionRecoveryPendingTarget(pending);
|
||||
}
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
export async function releaseMainSessionRecoveryOwner(
|
||||
lease: MainSessionRecoveryOwnerLease | undefined,
|
||||
options?: {
|
||||
onDeferredSuccess?: (
|
||||
pending: MainSessionRecoveryPendingTarget | undefined,
|
||||
) => void | Promise<void>;
|
||||
},
|
||||
): Promise<MainSessionRecoveryPendingTarget | undefined> {
|
||||
if (!lease) {
|
||||
return undefined;
|
||||
@@ -376,7 +349,17 @@ export async function releaseMainSessionRecoveryOwner(
|
||||
try {
|
||||
return await releaseMainSessionRecoveryOwnerWithRetries(lease);
|
||||
} catch (error) {
|
||||
scheduleMainSessionRecoveryOwnerRelease(lease, options?.onDeferredSuccess);
|
||||
// Exact-token cleanup survives transient writer outages without blocking its caller.
|
||||
scheduleMainSessionRecoveryMutation({
|
||||
mutation: () => releaseMainSessionRecoveryOwnerWithRetries(lease),
|
||||
onSuccess: async (pending) => {
|
||||
if (pending) {
|
||||
const { scheduleMainSessionRecoveryPendingTarget } =
|
||||
await import("./main-session-recovery-owner-release.js");
|
||||
scheduleMainSessionRecoveryPendingTarget(pending);
|
||||
}
|
||||
},
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Lifecycle retry-grace e2e tests cover completion delivery retry behavior when
|
||||
// lifecycle events race gateway waits or transient announce failures.
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { SessionDeliveryState } from "../../../config/sessions/types.js";
|
||||
import type { AgentRunTerminalReplySnapshot } from "../../agent-run-terminal-reply.js";
|
||||
import { testing as subagentAnnounceDeliveryTesting } from "../announce/subagent-announce-delivery.test-support.js";
|
||||
import { testing as subagentAnnounceOutputTesting } from "../announce/subagent-announce-output.test-support.js";
|
||||
import { testing as subagentAnnounceTesting } from "../announce/subagent-announce.js";
|
||||
@@ -17,6 +19,7 @@ type LifecycleData = {
|
||||
endedAt?: number;
|
||||
aborted?: boolean;
|
||||
error?: string;
|
||||
terminalReply?: AgentRunTerminalReplySnapshot;
|
||||
};
|
||||
type LifecycleEvent = {
|
||||
stream?: string;
|
||||
@@ -28,10 +31,7 @@ type LifecycleEvent = {
|
||||
type SessionStoreEntry = {
|
||||
sessionId: string;
|
||||
updatedAt: number;
|
||||
channel?: string;
|
||||
lastChannel?: string;
|
||||
to?: string;
|
||||
accountId?: string;
|
||||
delivery?: SessionDeliveryState;
|
||||
};
|
||||
|
||||
type GatewayAgentInternalEvent = {
|
||||
@@ -149,10 +149,12 @@ describe("subagent registry lifecycle error grace", () => {
|
||||
"agent:main:main": {
|
||||
sessionId: "sess-main",
|
||||
updatedAt: 1,
|
||||
channel: "discord",
|
||||
lastChannel: "discord",
|
||||
to: "user-1",
|
||||
accountId: "default",
|
||||
delivery: {
|
||||
kind: "external",
|
||||
route: { channel: "discord", accountId: "default", target: { to: "user-1" } },
|
||||
context: { channel: "discord", to: "user-1", accountId: "default" },
|
||||
origin: { provider: "discord", to: "user-1", accountId: "default" },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -394,7 +396,11 @@ describe("subagent registry lifecycle error grace", () => {
|
||||
registerCompletionRun(runId, "completed-before-yield", "finish once", requesterTurnRunId);
|
||||
setAssistantOutput(childSessionKey, "child complete");
|
||||
|
||||
emitLifecycleEvent(runId, { phase: "end", endedAt: Date.now() });
|
||||
emitLifecycleEvent(runId, {
|
||||
phase: "end",
|
||||
endedAt: Date.now(),
|
||||
terminalReply: { disposition: "visible", text: "child complete" },
|
||||
});
|
||||
await waitForDeliveredCleanup(runId);
|
||||
|
||||
const completed = mod
|
||||
|
||||
@@ -47,10 +47,9 @@ vi.mock(
|
||||
...actual,
|
||||
releaseMainSessionRecoveryOwner: async (
|
||||
lease: Parameters<typeof actual.releaseMainSessionRecoveryOwner>[0],
|
||||
options: Parameters<typeof actual.releaseMainSessionRecoveryOwner>[1],
|
||||
) => {
|
||||
await recoveryOwnerReleaseMocks.beforeRelease();
|
||||
return await actual.releaseMainSessionRecoveryOwner(lease, options);
|
||||
return await actual.releaseMainSessionRecoveryOwner(lease);
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
@@ -63,9 +63,7 @@ async function releaseReplyRecoveryOwner(
|
||||
return undefined;
|
||||
}
|
||||
try {
|
||||
return await releaseMainSessionRecoveryOwner(lease, {
|
||||
onDeferredSuccess: scheduleMainSessionRecoveryPendingTarget,
|
||||
});
|
||||
return await releaseMainSessionRecoveryOwner(lease);
|
||||
} catch (error) {
|
||||
log.warn(`failed to release main-session recovery reply owner: ${formatErrorMessage(error)}`);
|
||||
// The durable owner schedules exact-token retries. A completed reply must
|
||||
|
||||
@@ -166,15 +166,12 @@ async function applySqliteSessionEntryReplacementProjection<T, TReplacement>(
|
||||
(transactionDb) => {
|
||||
const transactionEntries = new Map<string, SessionEntry>();
|
||||
for (const sessionKey of validationKeys) {
|
||||
const transactionEntry = readExactSessionEntryRow(transactionDb, sessionKey)?.entry;
|
||||
if (
|
||||
readExactSessionEntryJson(transactionDb, sessionKey) !==
|
||||
expectedEntryJson.get(sessionKey)
|
||||
) {
|
||||
const transactionRow = readExactSessionEntryRow(transactionDb, sessionKey);
|
||||
if (transactionRow?.row.entry_json !== expectedEntryJson.get(sessionKey)) {
|
||||
throw new Error(`SQLite session entry changed before replacement for ${sessionKey}`);
|
||||
}
|
||||
if (transactionEntry) {
|
||||
transactionEntries.set(sessionKey, transactionEntry);
|
||||
if (transactionRow) {
|
||||
transactionEntries.set(sessionKey, transactionRow.entry);
|
||||
}
|
||||
}
|
||||
for (const replacement of applicable) {
|
||||
|
||||
@@ -340,7 +340,7 @@ export async function runCronIsolatedAgentTurn(params: {
|
||||
} finally {
|
||||
try {
|
||||
if (!cronRunSessionCleanupAttempted) {
|
||||
const cleanupOutcome = await cleanupCronRunSessionAfterRun({
|
||||
await cleanupCronRunSessionAfterRun({
|
||||
job: params.job,
|
||||
agentSessionKey: prepared.context.agentSessionKey,
|
||||
sessionId: prepared.context.currentRunSessionId(),
|
||||
@@ -349,7 +349,6 @@ export async function runCronIsolatedAgentTurn(params: {
|
||||
beforeDelete: prepared.context.sessionWorkAdmission.release,
|
||||
reason: "cron-delete-after-run-finally",
|
||||
});
|
||||
cronRunSessionCleanupAttempted = cleanupOutcome !== "not-requested";
|
||||
}
|
||||
} finally {
|
||||
// Release runtime references after the run completes (success or failure).
|
||||
|
||||
@@ -747,10 +747,12 @@ describe("node worker transfer client", () => {
|
||||
await expect(fs.readFile(path.join(workspaceDir, "tracked.txt"), "utf8")).resolves.toBe(
|
||||
changed ? "changed on gateway\n" : "tracked from gateway\n",
|
||||
);
|
||||
expect((await fs.stat(path.join(workspaceDir, "tracked.txt"))).mode & 0o777).toBe(
|
||||
changed ? 0o755 : 0o644,
|
||||
);
|
||||
expect((await fs.stat(path.join(workspaceDir, "script.sh"))).mode & 0o777).toBe(0o755);
|
||||
if (process.platform !== "win32") {
|
||||
expect((await fs.stat(path.join(workspaceDir, "tracked.txt"))).mode & 0o777).toBe(
|
||||
changed ? 0o755 : 0o644,
|
||||
);
|
||||
expect((await fs.stat(path.join(workspaceDir, "script.sh"))).mode & 0o777).toBe(0o755);
|
||||
}
|
||||
await expect(fs.readlink(path.join(workspaceDir, "tracked-link"))).resolves.toBe(
|
||||
changed ? "script.sh" : "tracked.txt",
|
||||
);
|
||||
|
||||
@@ -751,7 +751,18 @@ describe("resolvePluginDiscoveryProvidersRuntime", () => {
|
||||
expect(mocks.resolvePluginProvidersCore).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("defaults missing manifest model costs for static discovery entries", async () => {
|
||||
it.each([
|
||||
{
|
||||
name: "missing",
|
||||
cost: undefined,
|
||||
expectedCost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
},
|
||||
{
|
||||
name: "partial",
|
||||
cost: { input: 3, output: 15, cacheRead: 0.3 },
|
||||
expectedCost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 0 },
|
||||
},
|
||||
])("defaults only $name manifest model cost components", async ({ cost, expectedCost }) => {
|
||||
mocks.resolveDiscoveredProviderPluginIds.mockReturnValue(["anthropic"]);
|
||||
mocks.loadPluginMetadataSnapshot.mockReturnValue({
|
||||
index: { plugins: [] },
|
||||
@@ -772,6 +783,7 @@ describe("resolvePluginDiscoveryProvidersRuntime", () => {
|
||||
input: ["text"],
|
||||
contextWindow: 200000,
|
||||
maxTokens: 64000,
|
||||
...(cost ? { cost } : {}),
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -799,7 +811,7 @@ describe("resolvePluginDiscoveryProvidersRuntime", () => {
|
||||
models: [
|
||||
expect.objectContaining({
|
||||
id: "claude-sonnet-4-6",
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
cost: expectedCost,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
|
||||
@@ -114,26 +114,13 @@ function hasProviderAuthEnvCredential(
|
||||
function modelDefinitionCostFromManifestRow(
|
||||
row: NormalizedModelCatalogRow,
|
||||
): ModelDefinitionConfig["cost"] {
|
||||
if (
|
||||
!row.cost ||
|
||||
row.cost.input === undefined ||
|
||||
row.cost.output === undefined ||
|
||||
row.cost.cacheRead === undefined ||
|
||||
row.cost.cacheWrite === undefined
|
||||
) {
|
||||
return {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
};
|
||||
}
|
||||
const cost = row.cost;
|
||||
return {
|
||||
input: row.cost.input,
|
||||
output: row.cost.output,
|
||||
cacheRead: row.cost.cacheRead,
|
||||
cacheWrite: row.cost.cacheWrite,
|
||||
...(row.cost.tieredPricing ? { tieredPricing: row.cost.tieredPricing } : {}),
|
||||
input: cost?.input ?? 0,
|
||||
output: cost?.output ?? 0,
|
||||
cacheRead: cost?.cacheRead ?? 0,
|
||||
cacheWrite: cost?.cacheWrite ?? 0,
|
||||
...(cost?.tieredPricing ? { tieredPricing: cost.tieredPricing } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user