fix: checkpoint adapter usage and preserve unknown prices (#14991)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Devin FoleyandPaperclip authored and GitHub committed 2026-10-06 18:16:18 -07:00
1 parent 3ebd7bc0c9
commit eab93fd4a0
42 files changed
+1957 -561

No files matched your search

@@ -1,4 +1,5 @@
import { createProviderStoppedBoundary } from "@paperclipai/adapter-utils/provider-stopped-boundary";
import { createUsageCheckpointLog } from "@paperclipai/adapter-utils/usage-checkpoint";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
@@ -52,7 +53,7 @@ import {
readPaperclipIssueWorkModeFromContext,
resolveLegacyPaperclipDesiredSkillNames,
} from "@paperclipai/adapter-utils/server-utils";
import { isOpenCodeUnknownSessionError, parseOpenCodeJsonl } from "./parse.js";
import { isOpenCodeUnknownSessionError, parseOpenCodeJsonl, createOpenCodeJsonlParser } from "./parse.js";
import {
ensureOpenCodeModelConfiguredAndAvailable,
isTruthyEnvFlag,
@@ -83,7 +84,7 @@ function parseModelProvider(model: string | null): string | null {
}
function resolveOpenCodeBiller(env: Record<string, string>, provider: string | null): string {
return inferOpenAiCompatibleBiller(env, null) ?? provider ?? "unknown";
return provider === "openai" ? inferOpenAiCompatibleBiller(env, "openai") ?? "unknown" : provider ?? "unknown";
}
const REMOTE_OPENCODE_MODELS_PROBE_DEFAULT_TIMEOUT_SEC = 20;
@@ -645,6 +646,16 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
});
}
const consumeAccounting = createOpenCodeJsonlParser();
let hasAccounting = false;
const accountingLog = createUsageCheckpointLog(onLog, ctx.onUsage ?? (async () => {}), stdout => {
hasAccounting = true;
const parsed = consumeAccounting(stdout);
const provider = parseModelProvider(model || null);
return { usage: parsed.usageReported ? parsed.usage : undefined, costUsd: parsed.costUsd,
costStatus: parsed.usageComplete || parsed.costUsd != null ? undefined : "unpriced",
usageBasis: "per_run", provider, biller: resolveOpenCodeBiller(runtimeEnv, provider), billingType: "unknown", model, complete: false };
});
const proc = await runAdapterExecutionTargetProcess(runId, runtimeExecutionTarget, command, args, {
onProcessStopped: providerStop.beginInvocation(),
cwd,
@@ -654,15 +665,27 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
graceSec,
onSpawn,
onRuntimeProgress: ctx.onRuntimeProgress,
onLog,
onLog: accountingLog,
runLogTail: paperclipBridge?.runLogTail,
settleRunDisposition: paperclipBridge?.settleRunDisposition,
});
return {
proc,
rawStderr: proc.stderr,
parsed: parseOpenCodeJsonl(proc.stdout),
};
// Parse any unterminated final record before deciding whether its usage
// is complete. A clean exit alone cannot turn absent counters into zero.
await accountingLog.flush();
const retainedAccounting = consumeAccounting("");
await accountingLog.flush({ complete: proc.exitCode === 0 && !proc.timedOut && !proc.signal
&& (retainedAccounting.usageComplete || retainedAccounting.costUsd != null) });
// Display output is capped by the process transport. Keep accounting
// from the full stream, including when no checkpoint callback is installed.
const parsed = parseOpenCodeJsonl(proc.stdout);
if (hasAccounting) {
const retained = consumeAccounting("");
parsed.usage = retained.usage;
parsed.usageReported = retained.usageReported;
parsed.usageComplete = retained.usageComplete;
parsed.costUsd = retained.costUsd;
}
return { proc, rawStderr: proc.stderr, parsed };
};
const toResult = (
@@ -678,6 +701,15 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
exitCode: attempt.proc.exitCode,
signal: attempt.proc.signal,
timedOut: true,
usageComplete: false,
usage: attempt.parsed.usageReported ? attempt.parsed.usage : undefined,
usageBasis: "per_run",
provider: parseModelProvider(model || null),
biller: resolveOpenCodeBiller(runtimeEnv, parseModelProvider(model || null)),
model,
billingType: "unknown",
costUsd: attempt.parsed.costUsd,
costStatus: attempt.parsed.usageComplete || attempt.parsed.costUsd != null ? undefined : "unpriced",
errorMessage: `Timed out after ${timeoutSec}s`,
clearSession: clearSessionOnMissingSession,
};
@@ -715,16 +747,15 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
exitCode: synthesizedExitCode,
signal: attempt.proc.signal,
timedOut: false,
usageComplete: attempt.proc.exitCode === 0 && !attempt.proc.signal
&& (attempt.parsed.usageComplete || attempt.parsed.costUsd != null),
usageBasis: "per_run",
errorMessage: (synthesizedExitCode ?? 0) === 0 ? null : fallbackErrorMessage,
// Forward the transport-level error code from the run-disposition seam.
// A lost duplex control channel surfaces the typed `duplex_channel_lost`
// code; every other result carries no code here.
errorCode: attempt.proc.errorCode ?? null,
usage: {
inputTokens: attempt.parsed.usage.inputTokens,
outputTokens: attempt.parsed.usage.outputTokens,
cachedInputTokens: attempt.parsed.usage.cachedInputTokens,
},
usage: attempt.parsed.usageReported ? attempt.parsed.usage : undefined,
sessionId: resolvedSessionId,
sessionParams: resolvedSessionParams,
sessionDisplayId: resolvedSessionId,
@@ -733,6 +764,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
model: modelId,
billingType: "unknown",
costUsd: attempt.parsed.costUsd,
costStatus: attempt.parsed.usageComplete || attempt.parsed.costUsd != null ? undefined : "unpriced",
resultJson: {
stdout: attempt.proc.stdout,
stderr: attempt.proc.stderr,