@mduan/paseo-cli 0.11.0-fork.1 → 0.11.0-fork.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/node_modules/@getpaseo/client/package.json +3 -3
- package/node_modules/@getpaseo/highlight/package.json +1 -1
- package/node_modules/@getpaseo/plugin/package.json +3 -3
- package/node_modules/@getpaseo/protocol/dist/agent-types.d.ts +2 -0
- package/node_modules/@getpaseo/protocol/dist/generated/validation/ws-outbound.aot.js +106 -1
- package/node_modules/@getpaseo/protocol/dist/messages.js +3 -0
- package/node_modules/@getpaseo/protocol/package.json +1 -1
- package/node_modules/@getpaseo/relay/package.json +1 -1
- package/node_modules/@getpaseo/server/dist/server/builtin-plugins/package.json +1 -1
- package/node_modules/@getpaseo/server/dist/server/server/agent/agent-manager.d.ts +2 -2
- package/node_modules/@getpaseo/server/dist/server/server/agent/agent-projections.js +3 -0
- package/node_modules/@getpaseo/server/dist/server/server/agent/agent-sdk-types.d.ts +3 -1
- package/node_modules/@getpaseo/server/dist/server/server/agent/agent-storage.d.ts +1 -0
- package/node_modules/@getpaseo/server/dist/server/server/agent/agent-storage.js +1 -0
- package/node_modules/@getpaseo/server/dist/server/server/agent/provider-subagents/subtitle-format.d.ts +5 -0
- package/node_modules/@getpaseo/server/dist/server/server/agent/provider-subagents/subtitle-format.js +16 -0
- package/node_modules/@getpaseo/server/dist/server/server/agent/providers/appended-lines-reader.d.ts +20 -0
- package/node_modules/@getpaseo/server/dist/server/server/agent/providers/appended-lines-reader.js +59 -0
- package/node_modules/@getpaseo/server/dist/server/server/agent/providers/claude/agent.js +61 -2
- package/node_modules/@getpaseo/server/dist/server/server/agent/providers/claude/subagents/live-source.d.ts +2 -0
- package/node_modules/@getpaseo/server/dist/server/server/agent/providers/claude/subagents/live-source.js +7 -0
- package/node_modules/@getpaseo/server/dist/server/server/agent/providers/claude/subagents/presentation.d.ts +2 -0
- package/node_modules/@getpaseo/server/dist/server/server/agent/providers/claude/subagents/presentation.js +3 -8
- package/node_modules/@getpaseo/server/dist/server/server/agent/providers/claude/subagents/transcript-cost.d.ts +18 -0
- package/node_modules/@getpaseo/server/dist/server/server/agent/providers/claude/subagents/transcript-cost.js +50 -0
- package/node_modules/@getpaseo/server/dist/server/server/agent/providers/codex/pricing.d.ts +15 -1
- package/node_modules/@getpaseo/server/dist/server/server/agent/providers/codex/pricing.js +32 -1
- package/node_modules/@getpaseo/server/dist/server/server/agent/providers/codex/rollout-cost.d.ts +4 -8
- package/node_modules/@getpaseo/server/dist/server/server/agent/providers/codex/rollout-cost.js +11 -51
- package/node_modules/@getpaseo/server/dist/server/server/agent/providers/codex-app-server-agent.d.ts +5 -1
- package/node_modules/@getpaseo/server/dist/server/server/agent/providers/codex-app-server-agent.js +82 -11
- package/node_modules/@getpaseo/server/dist/server/server/agent/turn-diffs/snapshot.d.ts +14 -0
- package/node_modules/@getpaseo/server/dist/server/server/agent/turn-diffs/snapshot.js +91 -4
- package/node_modules/@getpaseo/server/dist/server/server/agent/turn-diffs/store.d.ts +3 -3
- package/node_modules/@getpaseo/server/dist/server/server/agent/turn-diffs/store.js +24 -18
- package/node_modules/@getpaseo/server/dist/server/web-ui/_expo/static/js/web/{index-9489c4e1587c4252f0bd3b6e4b1abab3.js → index-23bdbee37d31a9422c12f2a801979611.js} +21 -21
- package/node_modules/@getpaseo/server/dist/server/web-ui/_expo/static/js/web/index-23bdbee37d31a9422c12f2a801979611.js.br +0 -0
- package/node_modules/@getpaseo/server/dist/server/web-ui/_expo/static/js/web/{index-9489c4e1587c4252f0bd3b6e4b1abab3.js.gz → index-23bdbee37d31a9422c12f2a801979611.js.gz} +0 -0
- package/node_modules/@getpaseo/server/dist/server/web-ui/index.html +1 -1
- package/node_modules/@getpaseo/server/dist/server/web-ui/index.html.br +0 -0
- package/node_modules/@getpaseo/server/dist/server/web-ui/index.html.gz +0 -0
- package/node_modules/@getpaseo/server/package.json +6 -6
- package/package.json +7 -7
- package/node_modules/@getpaseo/server/dist/server/web-ui/_expo/static/js/web/index-9489c4e1587c4252f0bd3b6e4b1abab3.js.br +0 -0
|
@@ -15,6 +15,8 @@ import { observeReplaySubagents, parseClaudeSubagentMeta, } from "./subagents/re
|
|
|
15
15
|
import { foldSubagentObservations } from "./subagents/observation.js";
|
|
16
16
|
import { observeReplayWorkflows, parseClaudeWorkflowRun, } from "./subagents/workflow-replay-source.js";
|
|
17
17
|
import { readClaudeWorkflowResultFile } from "./subagents/workflow-output.js";
|
|
18
|
+
import { ClaudeTranscriptCost } from "./subagents/transcript-cost.js";
|
|
19
|
+
import { getSharedCodexPriceList } from "../codex/pricing.js";
|
|
18
20
|
import { buildClaudeFeatures, claudeModelSupportsFastMode } from "./feature-definitions.js";
|
|
19
21
|
import { buildBinaryDiagnosticRows, buildCommandResolutionDiagnosticRows, formatProviderDiagnostic, formatProviderDiagnosticError, } from "../diagnostic-utils.js";
|
|
20
22
|
import { appendOrReplaceGrowingAssistantMessage, runProviderTurn } from "../provider-runner.js";
|
|
@@ -1534,6 +1536,8 @@ class ClaudeAgentSession {
|
|
|
1534
1536
|
getToolInput: (toolUseId) => this.toolUseCache.get(toolUseId)?.input ?? null,
|
|
1535
1537
|
readWorkflowResult: readClaudeWorkflowResultFile,
|
|
1536
1538
|
});
|
|
1539
|
+
/** Task id -> cost reader over that subagent's transcript. */
|
|
1540
|
+
this.subagentCosts = new Map();
|
|
1537
1541
|
this.sidechainTracker = new ClaudeSidechainTracker({
|
|
1538
1542
|
getToolInput: (toolUseId) => this.toolUseCache.get(toolUseId)?.input ?? null,
|
|
1539
1543
|
// Releases that predate the task protocol announce nothing, so the tracker keeps deriving
|
|
@@ -2130,6 +2134,7 @@ class ClaudeAgentSession {
|
|
|
2130
2134
|
this.turnState = "idle";
|
|
2131
2135
|
this.sidechainTracker.clear();
|
|
2132
2136
|
this.taskProtocolSource.reset();
|
|
2137
|
+
this.subagentCosts.clear();
|
|
2133
2138
|
this.input?.end();
|
|
2134
2139
|
this.query?.close?.();
|
|
2135
2140
|
await this.awaitWithTimeout(this.query?.interrupt?.(), "close query interrupt");
|
|
@@ -3325,6 +3330,7 @@ class ClaudeAgentSession {
|
|
|
3325
3330
|
for (const event of foldSubagentObservations(subagentObservations)) {
|
|
3326
3331
|
events.push({ type: "provider_subagent", provider: "claude", event });
|
|
3327
3332
|
}
|
|
3333
|
+
this.refreshSubagentCost(message);
|
|
3328
3334
|
for (const observation of subagentObservations) {
|
|
3329
3335
|
if (observation.kind !== "declared")
|
|
3330
3336
|
continue;
|
|
@@ -3851,6 +3857,53 @@ class ClaudeAgentSession {
|
|
|
3851
3857
|
pushEvent(event) {
|
|
3852
3858
|
this.notifySubscribers(event);
|
|
3853
3859
|
}
|
|
3860
|
+
/**
|
|
3861
|
+
* Claude reports only a token count per subagent, so each usage report re-prices the
|
|
3862
|
+
* subagent's transcript from the point the previous report reached.
|
|
3863
|
+
*/
|
|
3864
|
+
refreshSubagentCost(message) {
|
|
3865
|
+
if (message.type !== "system")
|
|
3866
|
+
return;
|
|
3867
|
+
if (message.subtype !== "task_progress" && message.subtype !== "task_notification")
|
|
3868
|
+
return;
|
|
3869
|
+
const taskId = message.task_id;
|
|
3870
|
+
if (typeof taskId !== "string" || !this.taskProtocolSource.isDeclaredTask(taskId))
|
|
3871
|
+
return;
|
|
3872
|
+
void this.readSubagentCost(taskId);
|
|
3873
|
+
}
|
|
3874
|
+
async readSubagentCost(taskId) {
|
|
3875
|
+
try {
|
|
3876
|
+
const transcript = this.subagentTranscriptCost(taskId);
|
|
3877
|
+
if (!transcript)
|
|
3878
|
+
return;
|
|
3879
|
+
const prices = await getSharedCodexPriceList(this.logger).get();
|
|
3880
|
+
const costUsd = prices ? await transcript.read(prices) : undefined;
|
|
3881
|
+
if (costUsd === undefined)
|
|
3882
|
+
return;
|
|
3883
|
+
for (const event of foldSubagentObservations(this.taskProtocolSource.observeCost(taskId, costUsd))) {
|
|
3884
|
+
this.notifySubscribers({ type: "provider_subagent", provider: "claude", event });
|
|
3885
|
+
}
|
|
3886
|
+
}
|
|
3887
|
+
catch (error) {
|
|
3888
|
+
this.logger.debug({ err: error, taskId }, "Failed to estimate Claude subagent cost");
|
|
3889
|
+
}
|
|
3890
|
+
}
|
|
3891
|
+
subagentTranscriptCost(taskId) {
|
|
3892
|
+
const existing = this.subagentCosts.get(taskId);
|
|
3893
|
+
if (existing)
|
|
3894
|
+
return existing;
|
|
3895
|
+
const historyPath = this.claudeSessionId ? this.resolveHistoryPath(this.claudeSessionId) : null;
|
|
3896
|
+
if (!historyPath)
|
|
3897
|
+
return undefined;
|
|
3898
|
+
// Claude Code names a subagent's transcript after the same id the task protocol calls task_id.
|
|
3899
|
+
const transcriptPath = path.join(claudeSubagentsDirectory(historyPath), `agent-${taskId}.jsonl`);
|
|
3900
|
+
// Workflows and transcript-less tasks never get one; don't fetch prices for them.
|
|
3901
|
+
if (!fs.existsSync(transcriptPath))
|
|
3902
|
+
return undefined;
|
|
3903
|
+
const transcript = new ClaudeTranscriptCost(transcriptPath);
|
|
3904
|
+
this.subagentCosts.set(taskId, transcript);
|
|
3905
|
+
return transcript;
|
|
3906
|
+
}
|
|
3854
3907
|
/**
|
|
3855
3908
|
* Effort is reachable only through hooks.
|
|
3856
3909
|
*
|
|
@@ -4698,9 +4751,15 @@ function readClaudeReplayParentFacts(parentEntries) {
|
|
|
4698
4751
|
};
|
|
4699
4752
|
}
|
|
4700
4753
|
const CLAUDE_SUBAGENT_META_FILE = /^agent-(.+)\.meta\.json$/;
|
|
4754
|
+
function claudeSessionDirectory(historyPath) {
|
|
4755
|
+
return path.join(path.dirname(historyPath), path.basename(historyPath, ".jsonl"));
|
|
4756
|
+
}
|
|
4757
|
+
function claudeSubagentsDirectory(historyPath) {
|
|
4758
|
+
return path.join(claudeSessionDirectory(historyPath), "subagents");
|
|
4759
|
+
}
|
|
4701
4760
|
function readClaudeSidechainHistory(historyPath) {
|
|
4702
|
-
const sessionDirectory =
|
|
4703
|
-
const sidechainDirectory =
|
|
4761
|
+
const sessionDirectory = claudeSessionDirectory(historyPath);
|
|
4762
|
+
const sidechainDirectory = claudeSubagentsDirectory(historyPath);
|
|
4704
4763
|
const history = {
|
|
4705
4764
|
contents: [],
|
|
4706
4765
|
workflowContents: [],
|
|
@@ -126,6 +126,8 @@ export declare class ClaudeTaskProtocolSource {
|
|
|
126
126
|
*/
|
|
127
127
|
private observeTaskProgress;
|
|
128
128
|
private observeUsage;
|
|
129
|
+
/** Cost estimated from the subagent's transcript, which the session reads as usage grows. */
|
|
130
|
+
observeCost(taskId: string, costUsd: number): SubagentObservation[];
|
|
129
131
|
/**
|
|
130
132
|
* Effort as reported by a hook firing inside a subagent.
|
|
131
133
|
*
|
|
@@ -335,6 +335,13 @@ export class ClaudeTaskProtocolSource {
|
|
|
335
335
|
return [];
|
|
336
336
|
return this.updatePresentation(id, { usage });
|
|
337
337
|
}
|
|
338
|
+
/** Cost estimated from the subagent's transcript, which the session reads as usage grows. */
|
|
339
|
+
observeCost(taskId, costUsd) {
|
|
340
|
+
const id = this.subagentIdByTaskId.get(taskId);
|
|
341
|
+
if (!id)
|
|
342
|
+
return [];
|
|
343
|
+
return this.updatePresentation(id, { costUsd });
|
|
344
|
+
}
|
|
338
345
|
/**
|
|
339
346
|
* Effort as reported by a hook firing inside a subagent.
|
|
340
347
|
*
|
|
@@ -6,6 +6,8 @@ export interface ClaudeSubagentPresentationFacts {
|
|
|
6
6
|
model?: string;
|
|
7
7
|
effort?: string;
|
|
8
8
|
usage?: ClaudeSubagentUsage;
|
|
9
|
+
/** Estimated from list prices; Claude reports no cost per subagent. */
|
|
10
|
+
costUsd?: number;
|
|
9
11
|
}
|
|
10
12
|
/** Build the complete compact subtitle Claude exposes to provider-neutral clients. */
|
|
11
13
|
export declare function buildClaudeSubagentSubtitle(facts: ClaudeSubagentPresentationFacts): string | undefined;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { formatSubagentCost, formatSubagentTokens, } from "../../../provider-subagents/subtitle-format.js";
|
|
1
2
|
import { findClaudeModel } from "../models.js";
|
|
2
3
|
/** Build the complete compact subtitle Claude exposes to provider-neutral clients. */
|
|
3
4
|
export function buildClaudeSubagentSubtitle(facts) {
|
|
@@ -5,7 +6,8 @@ export function buildClaudeSubagentSubtitle(facts) {
|
|
|
5
6
|
readPart(facts.title),
|
|
6
7
|
formatModel(facts.model),
|
|
7
8
|
formatEffort(facts.effort),
|
|
8
|
-
|
|
9
|
+
formatSubagentTokens(facts.usage?.totalTokens),
|
|
10
|
+
formatSubagentCost(facts.costUsd),
|
|
9
11
|
].filter((part) => part !== undefined);
|
|
10
12
|
return parts.length > 0 ? parts.join(" · ") : undefined;
|
|
11
13
|
}
|
|
@@ -31,11 +33,4 @@ function formatEffort(effort) {
|
|
|
31
33
|
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
32
34
|
.join(" ");
|
|
33
35
|
}
|
|
34
|
-
function formatTokens(totalTokens) {
|
|
35
|
-
if (typeof totalTokens !== "number" || totalTokens <= 0)
|
|
36
|
-
return undefined;
|
|
37
|
-
if (totalTokens < 1000)
|
|
38
|
-
return `${Math.round(totalTokens)} tokens`;
|
|
39
|
-
return `${Math.round(totalTokens / 100) / 10}k tokens`;
|
|
40
|
-
}
|
|
41
36
|
//# sourceMappingURL=presentation.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { CodexPrices } from "../../codex/pricing.js";
|
|
2
|
+
/**
|
|
3
|
+
* Sums the estimated cost of every model request recorded in a Claude subagent transcript.
|
|
4
|
+
*
|
|
5
|
+
* Claude Code reports only a token count per subagent, but its transcript records each request's
|
|
6
|
+
* model and usage split into input, cache writes, cache reads, and output, which is what a price
|
|
7
|
+
* needs. One request can span several entries that share a message id, so each id is priced once,
|
|
8
|
+
* from its latest entry.
|
|
9
|
+
*/
|
|
10
|
+
export declare class ClaudeTranscriptCost {
|
|
11
|
+
private readonly lines;
|
|
12
|
+
private readonly costByMessageId;
|
|
13
|
+
constructor(filePath: string);
|
|
14
|
+
/** Returns the cost so far, or undefined when no request used a priced model. */
|
|
15
|
+
read(prices: CodexPrices): Promise<number | undefined>;
|
|
16
|
+
private applyLine;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=transcript-cost.d.ts.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { AppendedLinesReader, parseRecordLine, readCount, toRecord, } from "../../appended-lines-reader.js";
|
|
2
|
+
/**
|
|
3
|
+
* Sums the estimated cost of every model request recorded in a Claude subagent transcript.
|
|
4
|
+
*
|
|
5
|
+
* Claude Code reports only a token count per subagent, but its transcript records each request's
|
|
6
|
+
* model and usage split into input, cache writes, cache reads, and output, which is what a price
|
|
7
|
+
* needs. One request can span several entries that share a message id, so each id is priced once,
|
|
8
|
+
* from its latest entry.
|
|
9
|
+
*/
|
|
10
|
+
export class ClaudeTranscriptCost {
|
|
11
|
+
constructor(filePath) {
|
|
12
|
+
this.costByMessageId = new Map();
|
|
13
|
+
this.lines = new AppendedLinesReader(filePath);
|
|
14
|
+
}
|
|
15
|
+
/** Returns the cost so far, or undefined when no request used a priced model. */
|
|
16
|
+
async read(prices) {
|
|
17
|
+
await this.lines.read((line) => this.applyLine(line, prices));
|
|
18
|
+
if (this.costByMessageId.size === 0)
|
|
19
|
+
return undefined;
|
|
20
|
+
let total = 0;
|
|
21
|
+
for (const cost of this.costByMessageId.values())
|
|
22
|
+
total += cost;
|
|
23
|
+
return total;
|
|
24
|
+
}
|
|
25
|
+
applyLine(line, prices) {
|
|
26
|
+
// Skip user entries and tool results without parsing them.
|
|
27
|
+
if (!line.includes('"assistant"') || !line.includes('"usage"'))
|
|
28
|
+
return;
|
|
29
|
+
const entry = parseRecordLine(line);
|
|
30
|
+
if (entry?.type !== "assistant")
|
|
31
|
+
return;
|
|
32
|
+
const message = toRecord(entry.message);
|
|
33
|
+
const usage = toRecord(message?.usage);
|
|
34
|
+
if (typeof message?.id !== "string" || !usage)
|
|
35
|
+
return;
|
|
36
|
+
const cacheWrite = readCount(usage.cache_creation_input_tokens);
|
|
37
|
+
const cacheWrite1h = readCount(toRecord(usage.cache_creation)?.ephemeral_1h_input_tokens);
|
|
38
|
+
const cost = prices.claudeRequestCostUsd({
|
|
39
|
+
model: typeof message.model === "string" ? message.model : undefined,
|
|
40
|
+
inputTokens: readCount(usage.input_tokens),
|
|
41
|
+
cacheWrite5mTokens: Math.max(0, cacheWrite - cacheWrite1h),
|
|
42
|
+
cacheWrite1hTokens: cacheWrite1h,
|
|
43
|
+
cacheReadTokens: readCount(usage.cache_read_input_tokens),
|
|
44
|
+
outputTokens: readCount(usage.output_tokens),
|
|
45
|
+
});
|
|
46
|
+
if (cost !== undefined)
|
|
47
|
+
this.costByMessageId.set(message.id, cost);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=transcript-cost.js.map
|
|
@@ -6,12 +6,25 @@ export interface CodexRequestUsage {
|
|
|
6
6
|
cachedInputTokens: number;
|
|
7
7
|
outputTokens: number;
|
|
8
8
|
}
|
|
9
|
-
|
|
9
|
+
export interface ClaudeRequestUsage {
|
|
10
|
+
model: string | undefined;
|
|
11
|
+
inputTokens: number;
|
|
12
|
+
cacheWrite5mTokens: number;
|
|
13
|
+
cacheWrite1hTokens: number;
|
|
14
|
+
cacheReadTokens: number;
|
|
15
|
+
outputTokens: number;
|
|
16
|
+
}
|
|
17
|
+
/** List prices for one model request, keyed by Codex model slug or Claude model id. */
|
|
10
18
|
export declare class CodexPrices {
|
|
11
19
|
private readonly entries;
|
|
12
20
|
constructor(entries: Record<string, unknown>);
|
|
13
21
|
/** Returns undefined for a model missing from the price list. */
|
|
14
22
|
requestCostUsd({ model, serviceTier, inputTokens, cachedInputTokens, outputTokens, }: CodexRequestUsage): number | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Returns undefined for a model missing from the price list.
|
|
25
|
+
* ponytail: ignores Anthropic's long-context rate, which LiteLLM lists only for some models.
|
|
26
|
+
*/
|
|
27
|
+
claudeRequestCostUsd({ model, inputTokens, cacheWrite5mTokens, cacheWrite1hTokens, cacheReadTokens, outputTokens, }: ClaudeRequestUsage): number | undefined;
|
|
15
28
|
}
|
|
16
29
|
export interface CodexPriceListOptions {
|
|
17
30
|
logger: Logger;
|
|
@@ -39,4 +52,5 @@ export declare class CodexPriceList {
|
|
|
39
52
|
private load;
|
|
40
53
|
private readSavedCopy;
|
|
41
54
|
}
|
|
55
|
+
export declare function getSharedCodexPriceList(logger: Logger): CodexPriceList;
|
|
42
56
|
//# sourceMappingURL=pricing.d.ts.map
|
|
@@ -13,7 +13,7 @@ const FETCH_TIMEOUT_MS = 15000;
|
|
|
13
13
|
const LONG_CONTEXT_THRESHOLD_TOKENS = 272000;
|
|
14
14
|
const LONG_CONTEXT_SUFFIX = "_above_272k_tokens";
|
|
15
15
|
const PRIORITY_SUFFIX = "_priority";
|
|
16
|
-
/**
|
|
16
|
+
/** List prices for one model request, keyed by Codex model slug or Claude model id. */
|
|
17
17
|
export class CodexPrices {
|
|
18
18
|
constructor(entries) {
|
|
19
19
|
this.entries = entries;
|
|
@@ -38,6 +38,31 @@ export class CodexPrices {
|
|
|
38
38
|
cachedInputTokens * cachedInputRate +
|
|
39
39
|
outputTokens * outputRate);
|
|
40
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Returns undefined for a model missing from the price list.
|
|
43
|
+
* ponytail: ignores Anthropic's long-context rate, which LiteLLM lists only for some models.
|
|
44
|
+
*/
|
|
45
|
+
claudeRequestCostUsd({ model, inputTokens, cacheWrite5mTokens, cacheWrite1hTokens, cacheReadTokens, outputTokens, }) {
|
|
46
|
+
const entry = model ? toRecord(this.entries[model]) : undefined;
|
|
47
|
+
if (!entry)
|
|
48
|
+
return undefined;
|
|
49
|
+
const inputRate = readNumber(entry.input_cost_per_token);
|
|
50
|
+
const outputRate = readNumber(entry.output_cost_per_token);
|
|
51
|
+
if (inputRate === undefined || outputRate === undefined)
|
|
52
|
+
return undefined;
|
|
53
|
+
const cacheWrite5mRate = readNumber(entry.cache_creation_input_token_cost) ?? inputRate;
|
|
54
|
+
const cacheWrite1hRate = readNumber(entry.cache_creation_input_token_cost_above_1hr) ?? cacheWrite5mRate;
|
|
55
|
+
const cacheReadRate = readNumber(entry.cache_read_input_token_cost) ?? inputRate;
|
|
56
|
+
// Anthropic counts cache reads and writes apart from input tokens.
|
|
57
|
+
return (inputTokens * inputRate +
|
|
58
|
+
cacheWrite5mTokens * cacheWrite5mRate +
|
|
59
|
+
cacheWrite1hTokens * cacheWrite1hRate +
|
|
60
|
+
cacheReadTokens * cacheReadRate +
|
|
61
|
+
outputTokens * outputRate);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function readNumber(value) {
|
|
65
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
41
66
|
}
|
|
42
67
|
// Prefer the most specific rate LiteLLM lists: a model without a long-context or priority variant
|
|
43
68
|
// bills that request at its plain rate.
|
|
@@ -123,6 +148,12 @@ export class CodexPriceList {
|
|
|
123
148
|
}
|
|
124
149
|
}
|
|
125
150
|
}
|
|
151
|
+
let sharedCodexPriceList;
|
|
152
|
+
// One price list per daemon, so every session that estimates cost shares one fetch.
|
|
153
|
+
export function getSharedCodexPriceList(logger) {
|
|
154
|
+
sharedCodexPriceList ?? (sharedCodexPriceList = new CodexPriceList({ logger }));
|
|
155
|
+
return sharedCodexPriceList;
|
|
156
|
+
}
|
|
126
157
|
function parsePriceList(text) {
|
|
127
158
|
const entries = toRecord(JSON.parse(text));
|
|
128
159
|
if (!entries) {
|
package/node_modules/@getpaseo/server/dist/server/server/agent/providers/codex/rollout-cost.d.ts
CHANGED
|
@@ -5,19 +5,16 @@ import type { CodexPrices } from "./pricing.js";
|
|
|
5
5
|
* Codex writes each request's token counts and the model of each turn to the rollout, and the file
|
|
6
6
|
* survives compaction, resume, and daemon restarts. Pricing request by request applies the
|
|
7
7
|
* long-context rate and mid-session model switches, which pricing the thread total cannot.
|
|
8
|
-
* Each read parses only the bytes appended since the previous read.
|
|
9
8
|
*/
|
|
10
9
|
export declare class CodexRolloutCost {
|
|
11
|
-
private readonly
|
|
12
|
-
private
|
|
13
|
-
private readonly decoder;
|
|
14
|
-
private pendingLine;
|
|
15
|
-
private model;
|
|
10
|
+
private readonly lines;
|
|
11
|
+
private currentModel;
|
|
16
12
|
private highestTotalTokens;
|
|
17
13
|
private costUsd;
|
|
18
14
|
private pricedRequests;
|
|
19
|
-
private queue;
|
|
20
15
|
constructor(filePath: string);
|
|
16
|
+
/** The model of the latest turn read so far. */
|
|
17
|
+
get model(): string | undefined;
|
|
21
18
|
/**
|
|
22
19
|
* Returns the session cost so far, or undefined when no request used a priced model.
|
|
23
20
|
* ponytail: prices every request at the current service tier; the rollout does not record it.
|
|
@@ -26,7 +23,6 @@ export declare class CodexRolloutCost {
|
|
|
26
23
|
prices: CodexPrices;
|
|
27
24
|
serviceTier: string | null;
|
|
28
25
|
}): Promise<number | undefined>;
|
|
29
|
-
private readAppended;
|
|
30
26
|
private applyLine;
|
|
31
27
|
}
|
|
32
28
|
//# sourceMappingURL=rollout-cost.d.ts.map
|
package/node_modules/@getpaseo/server/dist/server/server/agent/providers/codex/rollout-cost.js
CHANGED
|
@@ -1,62 +1,39 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { StringDecoder } from "node:string_decoder";
|
|
1
|
+
import { AppendedLinesReader, parseRecordLine, readCount, toRecord, } from "../appended-lines-reader.js";
|
|
3
2
|
/**
|
|
4
3
|
* Sums the estimated cost of every model request recorded in a Codex rollout file.
|
|
5
4
|
*
|
|
6
5
|
* Codex writes each request's token counts and the model of each turn to the rollout, and the file
|
|
7
6
|
* survives compaction, resume, and daemon restarts. Pricing request by request applies the
|
|
8
7
|
* long-context rate and mid-session model switches, which pricing the thread total cannot.
|
|
9
|
-
* Each read parses only the bytes appended since the previous read.
|
|
10
8
|
*/
|
|
11
9
|
export class CodexRolloutCost {
|
|
12
10
|
constructor(filePath) {
|
|
13
|
-
this.filePath = filePath;
|
|
14
|
-
this.offset = 0;
|
|
15
|
-
this.decoder = new StringDecoder("utf8");
|
|
16
|
-
this.pendingLine = "";
|
|
17
11
|
this.highestTotalTokens = -1;
|
|
18
12
|
this.costUsd = 0;
|
|
19
13
|
this.pricedRequests = 0;
|
|
20
|
-
this.
|
|
14
|
+
this.lines = new AppendedLinesReader(filePath);
|
|
15
|
+
}
|
|
16
|
+
/** The model of the latest turn read so far. */
|
|
17
|
+
get model() {
|
|
18
|
+
return this.currentModel;
|
|
21
19
|
}
|
|
22
20
|
/**
|
|
23
21
|
* Returns the session cost so far, or undefined when no request used a priced model.
|
|
24
22
|
* ponytail: prices every request at the current service tier; the rollout does not record it.
|
|
25
23
|
*/
|
|
26
|
-
read({ prices, serviceTier, }) {
|
|
27
|
-
|
|
28
|
-
this.queue = result.catch(() => undefined);
|
|
29
|
-
return result;
|
|
30
|
-
}
|
|
31
|
-
async readAppended(prices, serviceTier) {
|
|
32
|
-
const handle = await fs.open(this.filePath, "r");
|
|
33
|
-
try {
|
|
34
|
-
const { size } = await handle.stat();
|
|
35
|
-
if (size > this.offset) {
|
|
36
|
-
const buffer = Buffer.alloc(size - this.offset);
|
|
37
|
-
const { bytesRead } = await handle.read(buffer, 0, buffer.length, this.offset);
|
|
38
|
-
this.offset += bytesRead;
|
|
39
|
-
const lines = (this.pendingLine + this.decoder.write(buffer.subarray(0, bytesRead))).split("\n");
|
|
40
|
-
this.pendingLine = lines.pop() ?? "";
|
|
41
|
-
for (const line of lines) {
|
|
42
|
-
this.applyLine(line, prices, serviceTier);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
finally {
|
|
47
|
-
await handle.close();
|
|
48
|
-
}
|
|
24
|
+
async read({ prices, serviceTier, }) {
|
|
25
|
+
await this.lines.read((line) => this.applyLine(line, prices, serviceTier));
|
|
49
26
|
return this.pricedRequests > 0 ? this.costUsd : undefined;
|
|
50
27
|
}
|
|
51
28
|
applyLine(line, prices, serviceTier) {
|
|
52
29
|
// Skip the bulk of the rollout (messages, tool output) without parsing it.
|
|
53
30
|
if (!line.includes('"turn_context"') && !line.includes('"token_count"'))
|
|
54
31
|
return;
|
|
55
|
-
const entry =
|
|
32
|
+
const entry = parseRecordLine(line);
|
|
56
33
|
const payload = toRecord(entry?.payload);
|
|
57
34
|
if (entry?.type === "turn_context") {
|
|
58
35
|
if (typeof payload?.model === "string")
|
|
59
|
-
this.
|
|
36
|
+
this.currentModel = payload.model;
|
|
60
37
|
return;
|
|
61
38
|
}
|
|
62
39
|
if (payload?.type !== "token_count")
|
|
@@ -71,7 +48,7 @@ export class CodexRolloutCost {
|
|
|
71
48
|
return;
|
|
72
49
|
this.highestTotalTokens = totalTokens;
|
|
73
50
|
const cost = prices.requestCostUsd({
|
|
74
|
-
model: this.
|
|
51
|
+
model: this.currentModel,
|
|
75
52
|
serviceTier,
|
|
76
53
|
inputTokens: readCount(last.input_tokens),
|
|
77
54
|
cachedInputTokens: readCount(last.cached_input_tokens),
|
|
@@ -83,21 +60,4 @@ export class CodexRolloutCost {
|
|
|
83
60
|
this.pricedRequests += 1;
|
|
84
61
|
}
|
|
85
62
|
}
|
|
86
|
-
function parseRecord(line) {
|
|
87
|
-
try {
|
|
88
|
-
return toRecord(JSON.parse(line));
|
|
89
|
-
}
|
|
90
|
-
catch {
|
|
91
|
-
// A torn or foreign line only drops that line from an estimate.
|
|
92
|
-
return undefined;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
function toRecord(value) {
|
|
96
|
-
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
97
|
-
? value
|
|
98
|
-
: undefined;
|
|
99
|
-
}
|
|
100
|
-
function readCount(value) {
|
|
101
|
-
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
102
|
-
}
|
|
103
63
|
//# sourceMappingURL=rollout-cost.js.map
|
package/node_modules/@getpaseo/server/dist/server/server/agent/providers/codex-app-server-agent.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { type AgentCapabilityFlags, type AgentClient, type AgentCreateSessionOpt
|
|
|
2
2
|
import type { Logger } from "pino";
|
|
3
3
|
import type { ChildProcessWithoutNullStreams } from "node:child_process";
|
|
4
4
|
import { Dirent } from "node:fs";
|
|
5
|
-
import { CodexPriceList } from "./codex/pricing.js";
|
|
5
|
+
import { type CodexPriceList } from "./codex/pricing.js";
|
|
6
6
|
import { type ProviderRuntimeSettings } from "../provider-launch-config.js";
|
|
7
7
|
import { type CodexThreadForkParams, type CodexThreadForkResponse, type CodexThreadRollbackParams, type CodexThreadRollbackResponse, type CodexAppServerTraceContext } from "./codex/app-server-transport.js";
|
|
8
8
|
import type { WorkspaceGitService } from "../../workspace-git-service.js";
|
|
@@ -173,6 +173,7 @@ export declare class CodexAppServerAgentSession implements AgentSession {
|
|
|
173
173
|
private emittedProviderSubagentUserMessageKeys;
|
|
174
174
|
private subAgentCallsByCallId;
|
|
175
175
|
private subAgentCallIdByChildThreadId;
|
|
176
|
+
private subAgentUsageByThreadId;
|
|
176
177
|
private pendingSubAgentNotificationsByThreadId;
|
|
177
178
|
private warnedUnknownNotificationMethods;
|
|
178
179
|
private warnedInvalidNotificationPayloads;
|
|
@@ -336,6 +337,9 @@ export declare class CodexAppServerAgentSession implements AgentSession {
|
|
|
336
337
|
private resetTurnTrackingState;
|
|
337
338
|
private handlePlanUpdatedNotification;
|
|
338
339
|
private handleTokenUsageUpdatedNotification;
|
|
340
|
+
private handleSubAgentTokenUsage;
|
|
341
|
+
private refreshSubAgentCost;
|
|
342
|
+
private emitSubAgentSubtitle;
|
|
339
343
|
private refreshRolloutCost;
|
|
340
344
|
private applyCostEstimate;
|
|
341
345
|
private openRolloutCost;
|
package/node_modules/@getpaseo/server/dist/server/server/agent/providers/codex-app-server-agent.js
CHANGED
|
@@ -11,8 +11,9 @@ import { renderPromptAttachmentAsText } from "../prompt-attachments.js";
|
|
|
11
11
|
import { composeSystemPromptParts } from "../system-prompt.js";
|
|
12
12
|
import { curateAgentActivity } from "../activity-curator.js";
|
|
13
13
|
import { CodexAsyncQuestions, codexAsyncQuestionToTimeline } from "./codex/async-questions.js";
|
|
14
|
-
import {
|
|
14
|
+
import { getSharedCodexPriceList } from "./codex/pricing.js";
|
|
15
15
|
import { CodexRolloutCost } from "./codex/rollout-cost.js";
|
|
16
|
+
import { formatSubagentCost, formatSubagentTokens } from "../provider-subagents/subtitle-format.js";
|
|
16
17
|
import { mapCodexToolCallEnvelope, mapCodexToolCallFromThreadItem, normalizeCommandExecutionCommand, splitCodexMcpToolResultImages, } from "./codex/tool-call-mapper.js";
|
|
17
18
|
import { checkProviderLaunchAvailable, createProviderEnv, createProviderEnvSpec, resolveProviderLaunch, } from "../provider-launch-config.js";
|
|
18
19
|
import { findExecutable, probeExecutable, } from "../../../executable-resolution/executable-resolution.js";
|
|
@@ -723,12 +724,6 @@ function filterCodexThreadsByCwd(threads, cwd) {
|
|
|
723
724
|
const belongsToWorkspace = createPathEquivalenceMatcher(cwd);
|
|
724
725
|
return threads.filter((thread) => typeof thread.cwd === "string" && belongsToWorkspace(thread.cwd));
|
|
725
726
|
}
|
|
726
|
-
let sharedCodexPriceList;
|
|
727
|
-
// One price list per daemon, so every Codex session and custom Codex provider shares one fetch.
|
|
728
|
-
function getSharedCodexPriceList(logger) {
|
|
729
|
-
sharedCodexPriceList ?? (sharedCodexPriceList = new CodexPriceList({ logger }));
|
|
730
|
-
return sharedCodexPriceList;
|
|
731
|
-
}
|
|
732
727
|
var CodexCostStatus;
|
|
733
728
|
(function (CodexCostStatus) {
|
|
734
729
|
/** No priced request yet, an unpriced model, or no rollout file. */
|
|
@@ -750,16 +745,31 @@ function isSameCostEstimate(left, right) {
|
|
|
750
745
|
}
|
|
751
746
|
return left.status === right.status;
|
|
752
747
|
}
|
|
753
|
-
//
|
|
748
|
+
// All cost fields are always written, undefined included, so the agent manager's usage merge on
|
|
754
749
|
// turn completion cannot keep a stale value from the previous state.
|
|
755
750
|
function withCostEstimate(usage, estimate) {
|
|
756
751
|
switch (estimate.status) {
|
|
757
752
|
case CodexCostStatus.Estimated:
|
|
758
|
-
return {
|
|
753
|
+
return {
|
|
754
|
+
...usage,
|
|
755
|
+
totalCostUsd: estimate.costUsd,
|
|
756
|
+
totalCostUnavailable: false,
|
|
757
|
+
totalCostEstimated: true,
|
|
758
|
+
};
|
|
759
759
|
case CodexCostStatus.Unavailable:
|
|
760
|
-
return {
|
|
760
|
+
return {
|
|
761
|
+
...usage,
|
|
762
|
+
totalCostUsd: undefined,
|
|
763
|
+
totalCostUnavailable: true,
|
|
764
|
+
totalCostEstimated: undefined,
|
|
765
|
+
};
|
|
761
766
|
case CodexCostStatus.Pending:
|
|
762
|
-
return {
|
|
767
|
+
return {
|
|
768
|
+
...usage,
|
|
769
|
+
totalCostUsd: undefined,
|
|
770
|
+
totalCostUnavailable: undefined,
|
|
771
|
+
totalCostEstimated: undefined,
|
|
772
|
+
};
|
|
763
773
|
}
|
|
764
774
|
}
|
|
765
775
|
export function toAgentUsage(tokenUsage) {
|
|
@@ -2598,6 +2608,7 @@ export class CodexAppServerAgentSession {
|
|
|
2598
2608
|
this.emittedProviderSubagentUserMessageKeys = new Set();
|
|
2599
2609
|
this.subAgentCallsByCallId = new Map();
|
|
2600
2610
|
this.subAgentCallIdByChildThreadId = new Map();
|
|
2611
|
+
this.subAgentUsageByThreadId = new Map();
|
|
2601
2612
|
this.pendingSubAgentNotificationsByThreadId = new Map();
|
|
2602
2613
|
this.warnedUnknownNotificationMethods = new Set();
|
|
2603
2614
|
this.warnedInvalidNotificationPayloads = new Set();
|
|
@@ -3000,6 +3011,7 @@ export class CodexAppServerAgentSession {
|
|
|
3000
3011
|
const { timeline, subAgentRoutes } = history;
|
|
3001
3012
|
this.subAgentCallsByCallId.clear();
|
|
3002
3013
|
this.subAgentCallIdByChildThreadId.clear();
|
|
3014
|
+
this.subAgentUsageByThreadId.clear();
|
|
3003
3015
|
this.pendingSubAgentNotificationsByThreadId.clear();
|
|
3004
3016
|
this.persistedProviderSubagentEvents = [];
|
|
3005
3017
|
this.loadingPersistedHistory = true;
|
|
@@ -4271,6 +4283,9 @@ export class CodexAppServerAgentSession {
|
|
|
4271
4283
|
}
|
|
4272
4284
|
dispatchSubAgentNotification(parsed, callId) {
|
|
4273
4285
|
switch (parsed.kind) {
|
|
4286
|
+
case "token_usage_updated":
|
|
4287
|
+
this.handleSubAgentTokenUsage(parsed);
|
|
4288
|
+
return;
|
|
4274
4289
|
case "thread_started":
|
|
4275
4290
|
this.emitSubAgentActivityUpdate(callId, "running", { reopen: true });
|
|
4276
4291
|
return;
|
|
@@ -4947,6 +4962,62 @@ export class CodexAppServerAgentSession {
|
|
|
4947
4962
|
}
|
|
4948
4963
|
void this.refreshRolloutCost();
|
|
4949
4964
|
}
|
|
4965
|
+
handleSubAgentTokenUsage(parsed) {
|
|
4966
|
+
const threadId = parsed.threadId;
|
|
4967
|
+
if (!threadId)
|
|
4968
|
+
return;
|
|
4969
|
+
let usage = this.subAgentUsageByThreadId.get(threadId);
|
|
4970
|
+
if (!usage) {
|
|
4971
|
+
usage = {};
|
|
4972
|
+
this.subAgentUsageByThreadId.set(threadId, usage);
|
|
4973
|
+
}
|
|
4974
|
+
// The last request's context, which is what the Claude subagent rows report as tokens too.
|
|
4975
|
+
usage.tokens = toAgentUsage(parsed.tokenUsage)?.contextWindowUsedTokens;
|
|
4976
|
+
this.emitSubAgentSubtitle(threadId, usage);
|
|
4977
|
+
void this.refreshSubAgentCost(threadId, usage);
|
|
4978
|
+
}
|
|
4979
|
+
async refreshSubAgentCost(threadId, usage) {
|
|
4980
|
+
const priceList = this.deps.codexPriceList;
|
|
4981
|
+
if (!priceList)
|
|
4982
|
+
return;
|
|
4983
|
+
try {
|
|
4984
|
+
usage.rolloutCost ?? (usage.rolloutCost = this.openRolloutCost(threadId));
|
|
4985
|
+
const rolloutCost = await usage.rolloutCost;
|
|
4986
|
+
const prices = await priceList.get();
|
|
4987
|
+
if (!rolloutCost || !prices)
|
|
4988
|
+
return;
|
|
4989
|
+
usage.costUsd = await rolloutCost.read({ prices, serviceTier: this.serviceTier });
|
|
4990
|
+
usage.model = rolloutCost.model;
|
|
4991
|
+
if (this.subAgentUsageByThreadId.get(threadId) !== usage)
|
|
4992
|
+
return;
|
|
4993
|
+
this.emitSubAgentSubtitle(threadId, usage);
|
|
4994
|
+
}
|
|
4995
|
+
catch (error) {
|
|
4996
|
+
usage.rolloutCost = undefined;
|
|
4997
|
+
this.logger.warn({ error, threadId }, "Failed to read a Codex subagent rollout for its cost");
|
|
4998
|
+
}
|
|
4999
|
+
}
|
|
5000
|
+
emitSubAgentSubtitle(threadId, usage) {
|
|
5001
|
+
// A provider subtitle replaces the row's agent type, so it leads with it.
|
|
5002
|
+
const callId = this.subAgentCallIdByChildThreadId.get(threadId);
|
|
5003
|
+
const detail = callId ? this.subAgentCallsByCallId.get(callId)?.toolCall.detail : undefined;
|
|
5004
|
+
const subtitle = [
|
|
5005
|
+
detail?.type === "sub_agent" ? (detail.subAgentType ?? "Codex subagent") : undefined,
|
|
5006
|
+
usage.model,
|
|
5007
|
+
formatSubagentTokens(usage.tokens),
|
|
5008
|
+
formatSubagentCost(usage.costUsd),
|
|
5009
|
+
]
|
|
5010
|
+
.filter((part) => part !== undefined)
|
|
5011
|
+
.join(" · ");
|
|
5012
|
+
if (!subtitle || subtitle === usage.subtitle)
|
|
5013
|
+
return;
|
|
5014
|
+
usage.subtitle = subtitle;
|
|
5015
|
+
this.emitEvent({
|
|
5016
|
+
type: "provider_subagent",
|
|
5017
|
+
provider: CODEX_PROVIDER,
|
|
5018
|
+
event: { type: "upsert", id: threadId, subtitle },
|
|
5019
|
+
});
|
|
5020
|
+
}
|
|
4950
5021
|
async refreshRolloutCost() {
|
|
4951
5022
|
const threadId = this.currentThreadId;
|
|
4952
5023
|
const priceList = this.deps.codexPriceList;
|
|
@@ -3,12 +3,26 @@ export interface WorkingTreeSnapshot {
|
|
|
3
3
|
repoRoot: string;
|
|
4
4
|
tree: string;
|
|
5
5
|
}
|
|
6
|
+
export type TurnStartSnapshot = WorkingTreeSnapshot & {
|
|
7
|
+
head?: string;
|
|
8
|
+
headLogPath: string;
|
|
9
|
+
headLogSize: number;
|
|
10
|
+
};
|
|
6
11
|
/**
|
|
7
12
|
* Writes the whole working tree (tracked changes plus untracked, non-ignored files up to 1 MiB)
|
|
8
13
|
* as a git tree object. Staging goes through a copy of the index, so the user's index, HEAD and
|
|
9
14
|
* branches are untouched; only git objects are written.
|
|
10
15
|
*/
|
|
11
16
|
export declare function snapshotWorkingTree(cwd: string): Promise<WorkingTreeSnapshot | null>;
|
|
17
|
+
/** A working tree snapshot plus where HEAD and its reflog stood, to separate out branch moves. */
|
|
18
|
+
export declare function snapshotTurnStart(cwd: string): Promise<TurnStartSnapshot | null>;
|
|
19
|
+
/**
|
|
20
|
+
* The tree to diff a turn from. When the turn moved HEAD to another commit, the start snapshot's
|
|
21
|
+
* uncommitted changes are replayed onto where HEAD landed, so the diff holds the agent's edits and
|
|
22
|
+
* commits and none of the content the move brought in. Undefined when that can't be worked out
|
|
23
|
+
* (no reflog, unborn branch, replay conflict); the turn then gets no diff.
|
|
24
|
+
*/
|
|
25
|
+
export declare function resolveTurnFromTree(start: TurnStartSnapshot): Promise<string | undefined>;
|
|
12
26
|
export declare function diffTreeStats(params: {
|
|
13
27
|
repoRoot: string;
|
|
14
28
|
fromTree: string;
|