diff --git a/packages/scrawn/src/core/scrawn.ts b/packages/scrawn/src/core/scrawn.ts index 7e041e9..93c8f36 100644 --- a/packages/scrawn/src/core/scrawn.ts +++ b/packages/scrawn/src/core/scrawn.ts @@ -1024,6 +1024,14 @@ export class Scrawn< expr: payload.inputCacheDebit.expr?._expr, } : undefined, + outputCacheTokens: payload.outputCacheTokens, + outputCacheDebit: payload.outputCacheDebit + ? { + amount: payload.outputCacheDebit.amount, + tag: payload.outputCacheDebit.tag, + expr: payload.outputCacheDebit.expr?._expr, + } + : undefined, }; // Validate each payload @@ -1147,6 +1155,14 @@ export class Scrawn< resolveTokens(validated.inputCacheDebit.expr, tokenContext) ) : undefined, + outputCacheTokens: validated.outputCacheTokens ?? 0, + outputCacheAmount: validated.outputCacheDebit?.amount ?? undefined, + outputCacheTag: validated.outputCacheDebit?.tag ?? undefined, + outputCacheExpr: validated.outputCacheDebit?.expr + ? serializeExpr( + resolveTokens(validated.outputCacheDebit.expr, tokenContext) + ) + : undefined, } as AITokenUsage; const eventId = randomUUID(); diff --git a/packages/scrawn/src/core/types/event.ts b/packages/scrawn/src/core/types/event.ts index 191fb33..5c6629a 100644 --- a/packages/scrawn/src/core/types/event.ts +++ b/packages/scrawn/src/core/types/event.ts @@ -361,6 +361,8 @@ const DebitFieldSchema = z * - provider: optional non-empty string * - inputCacheTokens: optional non-negative integer * - inputCacheDebit: optional one of amount, tag, or expr + * - outputCacheTokens: optional non-negative integer + * - outputCacheDebit: optional one of amount, tag, or expr */ export const AITokenUsagePayloadSchema = z.object({ userId: z.string().min(1, "userId must be a non-empty string"), @@ -383,6 +385,12 @@ export const AITokenUsagePayloadSchema = z.object({ .nonnegative("inputCacheTokens must be non-negative") .optional(), inputCacheDebit: DebitFieldSchema.optional(), + outputCacheTokens: z + .number() + .int("outputCacheTokens must be an integer") + .nonnegative("outputCacheTokens must be non-negative") + .optional(), + outputCacheDebit: DebitFieldSchema.optional(), }); /** @@ -403,6 +411,8 @@ export const AITokenUsagePayloadSchema = z.object({ * @property provider - (Optional) LLM provider identifier (e.g. 'openai', 'anthropic') * @property inputCacheTokens - (Optional) Cached input tokens count (typically cheaper) * @property inputCacheDebit - (Optional) Debit pricing for cached input tokens + * @property outputCacheTokens - (Optional) Cached output tokens count (typically cheaper) + * @property outputCacheDebit - (Optional) Debit pricing for cached output tokens * * @example * ```typescript @@ -454,4 +464,8 @@ export type AITokenUsagePayload = { inputCacheTokens?: number; /** Debit pricing for cached input tokens (oneof amount, tag, or expr). */ inputCacheDebit?: DebitField; + /** Number of cached output tokens used (typically cheaper). */ + outputCacheTokens?: number; + /** Debit pricing for cached output tokens (oneof amount, tag, or expr). */ + outputCacheDebit?: DebitField; }; diff --git a/packages/scrawn/src/gen/event/v1/event.ts b/packages/scrawn/src/gen/event/v1/event.ts index 5f59bdd..3e1384d 100644 --- a/packages/scrawn/src/gen/event/v1/event.ts +++ b/packages/scrawn/src/gen/event/v1/event.ts @@ -151,6 +151,11 @@ export interface AITokenUsage { inputCacheTag?: string | undefined; /** Pricing expression for input cache tokens */ inputCacheExpr?: string | undefined; + outputCacheTokens: number; + outputCacheAmount?: number | undefined; + outputCacheTag?: string | undefined; + /** Pricing expression for output cache tokens */ + outputCacheExpr?: string | undefined; metadata?: string | undefined; } @@ -747,6 +752,10 @@ function createBaseAITokenUsage(): AITokenUsage { inputCacheAmount: undefined, inputCacheTag: undefined, inputCacheExpr: undefined, + outputCacheTokens: 0, + outputCacheAmount: undefined, + outputCacheTag: undefined, + outputCacheExpr: undefined, metadata: undefined, }; } @@ -798,6 +807,18 @@ export const AITokenUsage: MessageFns = { if (message.inputCacheExpr !== undefined) { writer.uint32(114).string(message.inputCacheExpr); } + if (message.outputCacheTokens !== 0) { + writer.uint32(128).int32(message.outputCacheTokens); + } + if (message.outputCacheAmount !== undefined) { + writer.uint32(136).int32(message.outputCacheAmount); + } + if (message.outputCacheTag !== undefined) { + writer.uint32(146).string(message.outputCacheTag); + } + if (message.outputCacheExpr !== undefined) { + writer.uint32(154).string(message.outputCacheExpr); + } if (message.metadata !== undefined) { writer.uint32(122).string(message.metadata); } @@ -924,6 +945,38 @@ export const AITokenUsage: MessageFns = { message.inputCacheExpr = reader.string(); continue; } + case 16: { + if (tag !== 128) { + break; + } + + message.outputCacheTokens = reader.int32(); + continue; + } + case 17: { + if (tag !== 136) { + break; + } + + message.outputCacheAmount = reader.int32(); + continue; + } + case 18: { + if (tag !== 146) { + break; + } + + message.outputCacheTag = reader.string(); + continue; + } + case 19: { + if (tag !== 154) { + break; + } + + message.outputCacheExpr = reader.string(); + continue; + } case 15: { if (tag !== 122) { break; @@ -983,6 +1036,18 @@ export const AITokenUsage: MessageFns = { inputCacheExpr: isSet(object.inputCacheExpr) ? globalThis.String(object.inputCacheExpr) : undefined, + outputCacheTokens: isSet(object.outputCacheTokens) + ? globalThis.Number(object.outputCacheTokens) + : 0, + outputCacheAmount: isSet(object.outputCacheAmount) + ? globalThis.Number(object.outputCacheAmount) + : undefined, + outputCacheTag: isSet(object.outputCacheTag) + ? globalThis.String(object.outputCacheTag) + : undefined, + outputCacheExpr: isSet(object.outputCacheExpr) + ? globalThis.String(object.outputCacheExpr) + : undefined, metadata: isSet(object.metadata) ? globalThis.String(object.metadata) : undefined, @@ -1033,6 +1098,18 @@ export const AITokenUsage: MessageFns = { if (message.inputCacheExpr !== undefined) { obj.inputCacheExpr = message.inputCacheExpr; } + if (message.outputCacheTokens !== 0) { + obj.outputCacheTokens = Math.round(message.outputCacheTokens); + } + if (message.outputCacheAmount !== undefined) { + obj.outputCacheAmount = Math.round(message.outputCacheAmount); + } + if (message.outputCacheTag !== undefined) { + obj.outputCacheTag = message.outputCacheTag; + } + if (message.outputCacheExpr !== undefined) { + obj.outputCacheExpr = message.outputCacheExpr; + } if (message.metadata !== undefined) { obj.metadata = message.metadata; } @@ -1062,6 +1139,10 @@ export const AITokenUsage: MessageFns = { message.inputCacheAmount = object.inputCacheAmount ?? undefined; message.inputCacheTag = object.inputCacheTag ?? undefined; message.inputCacheExpr = object.inputCacheExpr ?? undefined; + message.outputCacheTokens = object.outputCacheTokens ?? 0; + message.outputCacheAmount = object.outputCacheAmount ?? undefined; + message.outputCacheTag = object.outputCacheTag ?? undefined; + message.outputCacheExpr = object.outputCacheExpr ?? undefined; message.metadata = object.metadata ?? undefined; return message; },