Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions packages/client/src/effect/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -986,6 +986,15 @@ export type SessionLogOutput =
| undefined
readonly text: string
readonly recent: string
readonly cost?: (number & Brand.Brand<"Money.USD">) | undefined
readonly tokens?:
| {
readonly input: number
readonly output: number
readonly reasoning: number
readonly cache: { readonly read: number; readonly write: number }
}
| undefined
}
}
| {
Expand All @@ -1000,6 +1009,15 @@ export type SessionLogOutput =
readonly reason: "auto" | "manual"
readonly error: { readonly type: string; readonly message: string; readonly status?: number | undefined }
readonly inputID?: SessionMessage.ID | undefined
readonly cost?: (number & Brand.Brand<"Money.USD">) | undefined
readonly tokens?:
| {
readonly input: number
readonly output: number
readonly reasoning: number
readonly cache: { readonly read: number; readonly write: number }
}
| undefined
}
}
| {
Expand Down
57 changes: 56 additions & 1 deletion packages/client/src/promise/generated/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,8 @@ export type SessionMessageCompactionFailed = {
status: "failed"
reason: "auto" | "manual"
error: SessionStructuredError
cost?: MoneyUSD
tokens?: TokenUsageInfo
}

export type SessionProviderContext = { version: 1; provenance: SessionProviderContextProvenance; messages: JsonValue }
Expand Down Expand Up @@ -808,7 +810,14 @@ export type SessionCompactionFailed = {
type: "session.compaction.failed"
durable: { aggregateID: string; seq: number; version: 1 }
location?: LocationRef
data: { sessionID: string; reason: "auto" | "manual"; error: SessionStructuredError; inputID?: string }
data: {
sessionID: string
reason: "auto" | "manual"
error: SessionStructuredError
inputID?: string
cost?: MoneyUSD
tokens?: TokenUsageInfo
}
}

export type SessionRevertCleared = {
Expand Down Expand Up @@ -1738,6 +1747,8 @@ export type SessionMessageCompactionCompleted = {
summary: string
recent: string
providerContext?: SessionProviderContext
cost?: MoneyUSD
tokens?: TokenUsageInfo
}

export type SessionCompactionEnded = {
Expand All @@ -1755,6 +1766,8 @@ export type SessionCompactionEnded = {
providerContext?: SessionProviderContext
text: string
recent: string
cost?: MoneyUSD
tokens?: TokenUsageInfo
}
}

Expand Down Expand Up @@ -3111,6 +3124,13 @@ export type SessionImportInput = {
}
readonly messages: JsonValue
}
readonly cost?: number
readonly tokens?: {
readonly input: number
readonly output: number
readonly reasoning: number
readonly cache: { readonly read: number; readonly write: number }
}
}
| {
readonly type: "compaction"
Expand All @@ -3120,6 +3140,13 @@ export type SessionImportInput = {
readonly status: "failed"
readonly reason: "auto" | "manual"
readonly error: { readonly type: string; readonly message: string; readonly status?: number }
readonly cost?: number
readonly tokens?: {
readonly input: number
readonly output: number
readonly reasoning: number
readonly cache: { readonly read: number; readonly write: number }
}
}
)
>
Expand Down Expand Up @@ -3402,6 +3429,13 @@ export type SessionImportInput = {
}
readonly messages: JsonValue
}
readonly cost?: number
readonly tokens?: {
readonly input: number
readonly output: number
readonly reasoning: number
readonly cache: { readonly read: number; readonly write: number }
}
}
| {
readonly type: "compaction"
Expand All @@ -3411,6 +3445,13 @@ export type SessionImportInput = {
readonly status: "failed"
readonly reason: "auto" | "manual"
readonly error: { readonly type: string; readonly message: string; readonly status?: number }
readonly cost?: number
readonly tokens?: {
readonly input: number
readonly output: number
readonly reasoning: number
readonly cache: { readonly read: number; readonly write: number }
}
}
)
>
Expand Down Expand Up @@ -3693,6 +3734,13 @@ export type SessionImportInput = {
}
readonly messages: JsonValue
}
readonly cost?: number
readonly tokens?: {
readonly input: number
readonly output: number
readonly reasoning: number
readonly cache: { readonly read: number; readonly write: number }
}
}
| {
readonly type: "compaction"
Expand All @@ -3702,6 +3750,13 @@ export type SessionImportInput = {
readonly status: "failed"
readonly reason: "auto" | "manual"
readonly error: { readonly type: string; readonly message: string; readonly status?: number }
readonly cost?: number
readonly tokens?: {
readonly input: number
readonly output: number
readonly reasoning: number
readonly cache: { readonly read: number; readonly write: number }
}
}
)
>
Expand Down
8 changes: 8 additions & 0 deletions packages/client/src/solid/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1083,8 +1083,11 @@ export function createData(config: CreateDataInput) {
reason: event.data.reason,
model: event.data.model,
providerState: event.data.providerState,
providerContext: event.data.providerContext,
summary: event.data.text,
recent: event.data.recent,
cost: event.data.cost,
tokens: event.data.tokens,
})
return
}
Expand All @@ -1095,8 +1098,11 @@ export function createData(config: CreateDataInput) {
reason: event.data.reason,
model: event.data.model,
providerState: event.data.providerState,
providerContext: event.data.providerContext,
summary: event.data.text,
recent: event.data.recent,
cost: event.data.cost,
tokens: event.data.tokens,
time: { created: event.created },
})
})
Expand All @@ -1116,6 +1122,8 @@ export function createData(config: CreateDataInput) {
message: "Compaction failed before recording an error",
},
metadata: current?.type === "compaction" ? current.metadata : event.metadata,
cost: event.data.cost,
tokens: event.data.tokens,
time: current?.type === "compaction" ? current.time : { created: event.created },
}
if (current?.type === "compaction") {
Expand Down
37 changes: 35 additions & 2 deletions packages/client/test/solid-compaction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,46 @@ test.each(["started", "cancelled", "failed"])(
expect(fixture.data.session.message.list(sessionID)).toMatchObject([{ type: "compaction", status: "running" }])
const model = { providerID: "demo", id: "model" }
const providerState = { responseId: "summary-response" }
const tokens = { input: 10, output: 4, reasoning: 0, cache: { read: 3, write: 0 } }
const providerContext = {
version: 1 as const,
provenance: {
providerID: "demo",
provider: "demo",
modelID: "model",
route: "demo-responses",
protocol: "demo",
endpoint: "digest",
},
messages: [],
}
fixture.emit({
...event,
type: "session.compaction.ended",
data: { sessionID, reason: "manual", model, providerState, text: "Summary", recent: "Recent" },
data: {
sessionID,
reason: "manual",
model,
providerState,
providerContext,
text: "Summary",
recent: "Recent",
cost: 0.01,
tokens,
},
})
// The live fold carries the provider window and request usage so the label matches a reloaded session.
expect(fixture.data.session.message.list(sessionID)).toMatchObject([
{ type: "compaction", status: "completed", summary: "Summary", model, providerState },
{
type: "compaction",
status: "completed",
summary: "Summary",
model,
providerState,
providerContext,
cost: 0.01,
tokens,
},
])
}
},
Expand Down
15 changes: 7 additions & 8 deletions packages/core/src/session/compaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -383,12 +383,7 @@ export const layer = Layer.effect(
},
}),
})
const failed = Effect.fnUntraced(function* (input: {
readonly sessionID: SessionSchema.ID
readonly reason: SessionMessage.Compaction["reason"]
readonly error: SessionError.Error
readonly inputID?: SessionMessage.ID
}) {
const failed = Effect.fnUntraced(function* (input: SessionEvent.Compaction.Failed["data"]) {
yield* bus.publish(SessionEvent.Compaction.Failed, input)
return { status: "failed" as const, error: input.error }
})
Expand Down Expand Up @@ -504,11 +499,12 @@ export const layer = Layer.effect(
)
}),
)
if (result.usage)
const usage = result.usage ? SessionUsage.record(result.usage, context.model.cost) : undefined
if (usage)
yield* bus.publish(SessionEvent.UsageRecorded, {
sessionID: context.session.id,
source: "compaction" as const,
...SessionUsage.record(result.usage, context.model.cost),
...usage,
})
yield* bus.publish(SessionEvent.Compaction.Ended, {
sessionID: context.session.id,
Expand All @@ -517,6 +513,7 @@ export const layer = Layer.effect(
text: "",
recent: "",
providerContext: SessionProviderContext.encode(provenance, result.replacement),
...usage,
})
return { status: "completed" as const }
}),
Expand Down Expand Up @@ -662,6 +659,7 @@ export const layer = Layer.effect(
reason: input.reason,
error,
inputID: input.inputID,
...usage,
})
}
yield* bus.publish(SessionEvent.Compaction.Ended, {
Expand All @@ -671,6 +669,7 @@ export const layer = Layer.effect(
providerState,
text: summary,
recent: history.recent,
...usage,
})
return { status: "completed" as const }
})
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/session/message-updater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,8 @@ export function update(adapter: Adapter, event: SessionEvent.DurableEvent) {
summary: event.data.text,
providerContext: event.data.providerContext,
recent: event.data.recent,
cost: event.data.cost,
tokens: event.data.tokens,
})
return
}
Expand All @@ -430,6 +432,8 @@ export function update(adapter: Adapter, event: SessionEvent.DurableEvent) {
summary: event.data.text,
providerContext: event.data.providerContext,
recent: event.data.recent,
cost: event.data.cost,
tokens: event.data.tokens,
time: { created },
}),
)
Expand All @@ -444,6 +448,8 @@ export function update(adapter: Adapter, event: SessionEvent.DurableEvent) {
metadata: current?.metadata ?? event.metadata,
reason: event.data.reason,
error: event.data.error,
cost: event.data.cost,
tokens: event.data.tokens,
time: current?.time ?? { created },
})
if (current?.status === "running") return yield* adapter.updateCompaction(failed)
Expand Down
10 changes: 9 additions & 1 deletion packages/core/test/session-compaction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -401,8 +401,16 @@ it.effect("manual compaction summarizes short context instead of no-op", () =>
expect(JSON.stringify(requests[0]?.messages)).toContain("Use Effect services and generators.")
expect(JSON.stringify(requests[0]?.messages)).toContain("User shell pwd completed: /project")
expect(JSON.stringify(requests[0]?.messages)).not.toContain("display-only-output")
// The compaction message carries its own request usage so clients can show what compacting cost.
expect(yield* store.context(sessionID)).toMatchObject([
{ type: "compaction", reason: "manual", summary: "## Objective\n- manual summary", recent: "" },
{
type: "compaction",
reason: "manual",
summary: "## Objective\n- manual summary",
recent: "",
cost: 0.0000233,
tokens: { input: 10, output: 4, reasoning: 2, cache: { read: 3, write: 2 } },
},
])
expect(yield* store.get(sessionID)).toMatchObject({
cost: 0.0000233,
Expand Down
2 changes: 2 additions & 0 deletions packages/core/test/session-native-compaction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,8 @@ const setup = Effect.fnUntraced(function* (endpoint = false) {
return yield* Effect.die("Missing native checkpoint")
expect(last.summary).toBe("")
expect(last.recent).toBe("")
// Provider compaction has no summary, so the request usage is the only visible cost of the operation.
expect(last.tokens).toMatchObject({ input: 20, output: 4 })
return last.providerContext
})
return {
Expand Down
6 changes: 6 additions & 0 deletions packages/schema/src/session-event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,10 @@ export namespace Compaction {
providerContext: SessionMessage.CompactionCompleted.fields.providerContext,
text: Schema.String,
recent: Schema.String,
// Repeats the internal `session.usage.recorded` figures: that event never reaches clients, and it
// stays the accounting source for session totals and stats.
cost: SessionMessage.CompactionCompleted.fields.cost,
tokens: SessionMessage.CompactionCompleted.fields.tokens,
},
})
export type Ended = typeof Ended.Type
Expand All @@ -602,6 +606,8 @@ export namespace Compaction {
reason: Started.data.fields.reason,
error: SessionError.Error,
inputID: SessionMessage.ID.pipe(optional),
cost: SessionMessage.CompactionFailed.fields.cost,
tokens: SessionMessage.CompactionFailed.fields.tokens,
},
})
export type Failed = typeof Failed.Type
Expand Down
8 changes: 8 additions & 0 deletions packages/schema/src/session-message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,12 @@ export const Assistant = Schema.Struct({

const CompactionBase = { type: Schema.tag("compaction"), ...Base }

/** Usage of the compaction request itself, not the size of the resulting context. */
const CompactionUsage = {
cost: Money.USD.pipe(optional),
tokens: TokenUsage.Info.pipe(optional),
}

export interface CompactionRunning extends Schema.Schema.Type<typeof CompactionRunning> {}
export const CompactionRunning = Schema.Struct({
...CompactionBase,
Expand All @@ -256,6 +262,7 @@ export const CompactionCompleted = Schema.Struct({
summary: Schema.String,
recent: Schema.String,
providerContext: SessionProviderContext.Info.pipe(optional),
...CompactionUsage,
}).annotate({ identifier: "Session.Message.Compaction.Completed" })

export interface CompactionFailed extends Schema.Schema.Type<typeof CompactionFailed> {}
Expand All @@ -264,6 +271,7 @@ export const CompactionFailed = Schema.Struct({
status: Schema.tag("failed"),
reason: Schema.Literals(["auto", "manual"]),
error: SessionError.Error,
...CompactionUsage,
}).annotate({ identifier: "Session.Message.Compaction.Failed" })

export const Compaction = Schema.Union([CompactionRunning, CompactionCompleted, CompactionFailed]).pipe(
Expand Down
Loading
Loading