stageflow 0.15.0 → 0.16.0

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.
@@ -1,10 +1,13 @@
1
1
  import type { StageEnvelope } from "../types/envelope.js";
2
+ import type { StageUsage } from "../types/usage.js";
2
3
  import type { StageLogEvent, StageSnapshot } from "./port.js";
3
4
  export type StageExecutionPatch = {
4
5
  status?: StageSnapshot["status"];
5
6
  started_at?: string;
6
7
  finished_at?: string;
7
8
  envelope?: StageEnvelope | null;
9
+ cost_usd?: number;
10
+ usage?: StageUsage;
8
11
  };
9
12
  export declare function executionStatusFromEvents(events: StageLogEvent[]): StageSnapshot["status"];
10
13
  export declare function deriveExecutionPatchFromEvent(event: StageLogEvent, current: Pick<StageSnapshot, "status"> & {
@@ -14,9 +14,13 @@ export function deriveExecutionPatchFromEvent(event, current) {
14
14
  else if (eventName === "waiting_for_input") {
15
15
  patch.status = "waiting_for_input";
16
16
  }
17
- else if (eventName === "succeeded" || eventName === "failed") {
18
- patch.status = eventName;
17
+ else if (event.event === "succeeded" || event.event === "failed") {
18
+ patch.status = event.event;
19
19
  patch.finished_at = at;
20
+ if (event.usage) {
21
+ patch.cost_usd = event.usage.costUsd;
22
+ patch.usage = event.usage;
23
+ }
20
24
  }
21
25
  return patch;
22
26
  }
@@ -18,11 +18,14 @@ export async function buildStageSnapshotFromStore(store, runId, stageId, workspa
18
18
  let status;
19
19
  let envelope;
20
20
  let attempt_count;
21
+ let cost_usd;
21
22
  if (latest !== null) {
22
23
  events = await store.listStageEvents(runId, stageId, latest.attempt);
23
24
  status = stageStatusFromEvents(events);
24
25
  envelope = await tryReadEnvelope(store, runId, stageId, latest);
25
26
  attempt_count = await store.countStageAttempts(runId, stageId);
27
+ const executions = await store.listStageExecutions(runId, stageId);
28
+ cost_usd = sumStageExecutionCost(executions);
26
29
  }
27
30
  else {
28
31
  events = await store.listStageEvents(runId, stageId);
@@ -42,5 +45,13 @@ export async function buildStageSnapshotFromStore(store, runId, stageId, workspa
42
45
  last_at,
43
46
  attempt_count,
44
47
  ...(pending ? { pending_prompt: pending } : {}),
48
+ ...(cost_usd !== undefined ? { cost_usd } : {}),
45
49
  };
46
50
  }
51
+ /** Sum cost across every attempt — retries are real spend too, so a stage's total isn't just its latest attempt. */
52
+ function sumStageExecutionCost(executions) {
53
+ const costs = executions.map((e) => e.cost_usd).filter((c) => c !== undefined);
54
+ if (costs.length === 0)
55
+ return undefined;
56
+ return costs.reduce((sum, c) => sum + c, 0);
57
+ }
@@ -54,7 +54,7 @@ async function finalizeStageResult(options) {
54
54
  store,
55
55
  runId,
56
56
  stageId,
57
- event: { event: "failed", reason: result.reason },
57
+ event: { event: "failed", reason: result.reason, usage: result.usage },
58
58
  attemptCtx,
59
59
  });
60
60
  console.error(`Stage ${stageId} failed: ${result.reason}`);
@@ -64,7 +64,7 @@ async function finalizeStageResult(options) {
64
64
  store,
65
65
  runId,
66
66
  stageId,
67
- event: { event: "succeeded" },
67
+ event: { event: "succeeded", usage: result.usage },
68
68
  attemptCtx,
69
69
  });
70
70
  console.error(`Stage ${stageId} succeeded.`);
@@ -0,0 +1,16 @@
1
+ /** Per-model token/cost breakdown, as computed by the backend SDK itself. */
2
+ export type ModelUsageBreakdown = {
3
+ inputTokens: number;
4
+ outputTokens: number;
5
+ cacheReadInputTokens: number;
6
+ cacheCreationInputTokens: number;
7
+ costUsd: number;
8
+ };
9
+ /** Accumulated LLM usage/cost for one stage attempt, across every turn/call it made. */
10
+ export type StageUsage = {
11
+ costUsd: number;
12
+ models: Record<string, ModelUsageBreakdown>;
13
+ };
14
+ /** Merge one model's usage from a single call into an accumulator, in place. */
15
+ export declare function addModelUsage(usage: StageUsage, model: string, delta: Partial<ModelUsageBreakdown>): void;
16
+ export declare function emptyStageUsage(): StageUsage;
@@ -0,0 +1,24 @@
1
+ function emptyBreakdown() {
2
+ return {
3
+ inputTokens: 0,
4
+ outputTokens: 0,
5
+ cacheReadInputTokens: 0,
6
+ cacheCreationInputTokens: 0,
7
+ costUsd: 0,
8
+ };
9
+ }
10
+ /** Merge one model's usage from a single call into an accumulator, in place. */
11
+ export function addModelUsage(usage, model, delta) {
12
+ const current = usage.models[model] ?? emptyBreakdown();
13
+ usage.models[model] = {
14
+ inputTokens: current.inputTokens + (delta.inputTokens ?? 0),
15
+ outputTokens: current.outputTokens + (delta.outputTokens ?? 0),
16
+ cacheReadInputTokens: current.cacheReadInputTokens + (delta.cacheReadInputTokens ?? 0),
17
+ cacheCreationInputTokens: current.cacheCreationInputTokens + (delta.cacheCreationInputTokens ?? 0),
18
+ costUsd: current.costUsd + (delta.costUsd ?? 0),
19
+ };
20
+ usage.costUsd += delta.costUsd ?? 0;
21
+ }
22
+ export function emptyStageUsage() {
23
+ return { costUsd: 0, models: {} };
24
+ }