mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-08 22:13:59 +01:00
fix(native): measure resumed run usage from the prior run's total (#19161)
This commit is contained in:
1 parent
6452e964a3
commit
737db73275
8 files changed
+176
-5
No files matched your search
@@ -10983,6 +10983,7 @@ describe("native warm session supervision", () => {
|
||||
terminal: null,
|
||||
terminalTurns: [],
|
||||
pendingRuntimeRequests: [],
|
||||
codexUsageBaseline: { baseline: { inputTokens: 40, outputTokens: 4 }, latest: { inputTokens: 100, outputTokens: 10 } },
|
||||
};
|
||||
const result = {
|
||||
result: { summary: "completed" },
|
||||
@@ -11011,6 +11012,7 @@ describe("native warm session supervision", () => {
|
||||
driverSessionId: checkpoint.driverSessionId,
|
||||
providerSessionId: checkpoint.providerSessionId,
|
||||
activeTurnId: checkpoint.activeTurnId,
|
||||
codexUsageBaseline: checkpoint.codexUsageBaseline,
|
||||
}),
|
||||
);
|
||||
return result;
|
||||
@@ -11042,6 +11044,102 @@ describe("native warm session supervision", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ name: "every report accounted", accounted: { inputTokens: 100, outputTokens: 10 }, latest: { inputTokens: 100, outputTokens: 10 } },
|
||||
{ name: "late usage not yet charged", accounted: { inputTokens: 100, outputTokens: 10 }, latest: { inputTokens: 130, outputTokens: 12 } },
|
||||
{ name: "no accounted total", accounted: undefined, latest: { inputTokens: 100, outputTokens: 10 } },
|
||||
])("starts a new run's usage from the prior run's total when a warm checkpoint resumes: $name", async ({ accounted, latest }) => {
|
||||
const stateBase = await mkdtemp(join(tmpdir(), "paperclip-warm-next-run-usage-"));
|
||||
const previousPaperclipHome = process.env.PAPERCLIP_HOME;
|
||||
process.env.PAPERCLIP_HOME = stateBase;
|
||||
const firstRun = {
|
||||
...execution,
|
||||
binding: {
|
||||
...execution.binding,
|
||||
runId: "run-warm-next-run-usage-first",
|
||||
executionWorkspaceId: "workspace-warm-next-run-usage",
|
||||
},
|
||||
session: {
|
||||
...execution.session,
|
||||
normalizedSessionId: "session-warm-next-run-usage",
|
||||
lifecyclePolicy: { mode: "warm" as const, idleTimeoutMs: 20 },
|
||||
},
|
||||
} as NativeExecutionInputV1;
|
||||
const secondRun = {
|
||||
...firstRun,
|
||||
binding: { ...firstRun.binding, runId: "run-warm-next-run-usage-second" },
|
||||
} as NativeExecutionInputV1;
|
||||
const checkpoint = {
|
||||
identity: {
|
||||
runId: firstRun.binding.runId,
|
||||
sessionId: firstRun.session.normalizedSessionId,
|
||||
companyId: firstRun.binding.companyId,
|
||||
issueId: firstRun.binding.issueId,
|
||||
agentId: firstRun.binding.agentId,
|
||||
},
|
||||
sessionId: firstRun.session.normalizedSessionId,
|
||||
driverSessionId: "driver-warm-next-run-usage",
|
||||
providerSessionId: "provider-warm-next-run-usage",
|
||||
activeTurnId: null,
|
||||
semanticResult: null,
|
||||
terminal: null,
|
||||
terminalTurns: [],
|
||||
pendingRuntimeRequests: [],
|
||||
codexUsageBaseline: { baseline: {}, latest, ...(accounted ? { accounted } : {}) },
|
||||
};
|
||||
const result = {
|
||||
result: { summary: "completed" },
|
||||
terminal: { runTerminalState: "succeeded" },
|
||||
turnId: "provider-turn-warm-next-run-usage",
|
||||
normalizedSessionId: firstRun.session.normalizedSessionId,
|
||||
providerSessionId: checkpoint.providerSessionId,
|
||||
driverKind: "test",
|
||||
driverVersion: "1",
|
||||
nativeEventCount: 1,
|
||||
highestContiguousSourceSeq: 1,
|
||||
usage: null,
|
||||
};
|
||||
const firstClose = vi.fn(async () => undefined);
|
||||
let resumedBaseline: unknown;
|
||||
state.execute
|
||||
.mockReset()
|
||||
.mockImplementationOnce(async (options) => {
|
||||
await options.onCheckpoint?.(checkpoint);
|
||||
options.onSession?.({ close: firstClose });
|
||||
return result;
|
||||
})
|
||||
.mockImplementationOnce(async (options) => {
|
||||
resumedBaseline = options.persistedSession?.codexUsageBaseline;
|
||||
return result;
|
||||
});
|
||||
|
||||
try {
|
||||
await executePaperclipNativeSession({
|
||||
db: leaseDb(firstRun),
|
||||
execution: firstRun,
|
||||
runnerInstanceId: "runner-warm-next-run-usage",
|
||||
});
|
||||
await vi.waitFor(() => expect(firstClose).toHaveBeenCalled(), { timeout: 500 });
|
||||
await executePaperclipNativeSession({
|
||||
db: leaseDb(secondRun),
|
||||
execution: secondRun,
|
||||
runnerInstanceId: "runner-warm-next-run-usage",
|
||||
});
|
||||
expect(resumedBaseline).toEqual({
|
||||
baseline: accounted ?? latest,
|
||||
latest,
|
||||
...(accounted ? { accounted } : {}),
|
||||
});
|
||||
} finally {
|
||||
if (previousPaperclipHome === undefined) {
|
||||
delete process.env.PAPERCLIP_HOME;
|
||||
} else {
|
||||
process.env.PAPERCLIP_HOME = previousPaperclipHome;
|
||||
}
|
||||
await rm(stateBase, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("reuses one session across distinct governed runs and closes it after idle expiry", async () => {
|
||||
let releaseClose!: () => void;
|
||||
const closeBarrier = new Promise<void>(resolve => { releaseClose = resolve; });
|
||||
|
||||
@@ -57,6 +57,7 @@ import { isCloudManagedInstance } from "../cloud-instance.js";
|
||||
import { dotRunnerBroker } from "../dot-runner-broker.js";
|
||||
import { CURSOR_DISTRIBUTION_PINS, KIMI_DISTRIBUTION_CLOSURE_SHA256, QUALIFIED_ACPX_PROFILES, QUALIFIED_ACPX_VERSION } from "../../vendor/paperclip-runner/index.js";
|
||||
import { isProviderMode } from "../../vendor/paperclip-runner/index.js";
|
||||
import { codexUsageBaselineForNextRun } from "../../vendor/paperclip-runner/index.js";
|
||||
import { PROVIDER_PACK_COMPATIBILITY, bundledRemoteProviderPackManifestPath, bundledRemoteRunnerBinary, runnerBinaryTarget } from "../../vendor/paperclip-runner/index.js";
|
||||
import { nativeRetryCancellationEligible, rethrowNativeCancellationLockConflict, assertCancellationRequest, cancellationIntentId as callerCancellationIntentId, cancellationRequestId } from "./native-cancellation-request.js";
|
||||
import { readNativePlanWait } from "./native-plan-wait.js";
|
||||
@@ -7035,6 +7036,9 @@ function loadWarmNativeCheckpoint(
|
||||
terminalTurns: [],
|
||||
pendingRuntimeRequests: [],
|
||||
governedWait: undefined,
|
||||
...(envelope.snapshot.codexUsageBaseline
|
||||
? { codexUsageBaseline: codexUsageBaselineForNextRun(envelope.snapshot.codexUsageBaseline) }
|
||||
: {}),
|
||||
};
|
||||
if (path !== scopedPath || envelope.configDigest !== configDigest) {
|
||||
// Upgrade the validated checkpoint atomically. When moving from a legacy
|
||||
|
||||
+1
@@ -164,6 +164,7 @@ export const parseHarnessRuntimeRequestResolution =
|
||||
export const parseNativeExecutionInput = runner.parseNativeExecutionInput;
|
||||
export const buildNativeModelEnvelope = runner.buildNativeModelEnvelope;
|
||||
export const isProviderMode = runner.isProviderMode;
|
||||
export const codexUsageBaselineForNextRun = runner.codexUsageBaselineForNextRun;
|
||||
export const parseNativeRuntimeContext = runner.parseNativeRuntimeContext;
|
||||
export const parsePaperclipQuestionSet = runner.parsePaperclipQuestionSet;
|
||||
export const parsePaperclipQuestionResponse =
|
||||
|
||||
Reference in new issue
Block a user