feat: bill OpenAI cache_write_tokens at cache-creation price with zero clamp
Parse OpenAI's native cache_write_tokens (chat prompt_tokens_details / responses input_tokens_details), bill it at the cache-creation ratio, and clamp the uncached prompt remainder at zero since cached + cache-write can exceed prompt_tokens. Propagate the field through chat/responses/claude format conversions and tiered expression billing (cc variable).
This commit is contained in:
@@ -88,6 +88,7 @@ func HasOpenAIUsageTokens(usage *Usage) bool {
|
||||
}
|
||||
if usage.PromptTokensDetails.CachedTokens != 0 ||
|
||||
usage.PromptTokensDetails.CachedCreationTokens != 0 ||
|
||||
usage.PromptTokensDetails.CacheWriteTokens != 0 ||
|
||||
usage.PromptTokensDetails.TextTokens != 0 ||
|
||||
usage.PromptTokensDetails.ImageTokens != 0 ||
|
||||
usage.PromptTokensDetails.AudioTokens != 0 {
|
||||
|
||||
@@ -849,6 +849,7 @@ type OpenAIResponsesRequest struct {
|
||||
MaxOutputTokens *uint `json:"max_output_tokens,omitempty"`
|
||||
TopLogProbs *int `json:"top_logprobs,omitempty"`
|
||||
Metadata json.RawMessage `json:"metadata,omitempty"`
|
||||
Moderation json.RawMessage `json:"moderation,omitempty"`
|
||||
ParallelToolCalls json.RawMessage `json:"parallel_tool_calls,omitempty"`
|
||||
PreviousResponseID string `json:"previous_response_id,omitempty"`
|
||||
Reasoning *Reasoning `json:"reasoning,omitempty"`
|
||||
@@ -859,6 +860,7 @@ type OpenAIResponsesRequest struct {
|
||||
// This field is allowed by default and can be disabled via channel setting disable_store.
|
||||
Store json.RawMessage `json:"store,omitempty"`
|
||||
PromptCacheKey json.RawMessage `json:"prompt_cache_key,omitempty"`
|
||||
PromptCacheOptions json.RawMessage `json:"prompt_cache_options,omitempty"`
|
||||
PromptCacheRetention json.RawMessage `json:"prompt_cache_retention,omitempty"`
|
||||
// SafetyIdentifier carries client identity for policy abuse detection.
|
||||
// This field is filtered by default and can be enabled via channel setting allow_safety_identifier.
|
||||
|
||||
+20
-3
@@ -256,9 +256,26 @@ type OpenAIVideoResponse struct {
|
||||
type InputTokenDetails struct {
|
||||
CachedTokens int `json:"cached_tokens"`
|
||||
CachedCreationTokens int `json:"cached_creation_tokens,omitempty"`
|
||||
TextTokens int `json:"text_tokens"`
|
||||
AudioTokens int `json:"audio_tokens"`
|
||||
ImageTokens int `json:"image_tokens"`
|
||||
// CacheWriteTokens is OpenAI's native cache-write count, reported as
|
||||
// prompt_tokens_details.cache_write_tokens (Chat Completions) or
|
||||
// input_tokens_details.cache_write_tokens (Responses). It is billed at the
|
||||
// cache-creation price.
|
||||
CacheWriteTokens int `json:"cache_write_tokens,omitempty"`
|
||||
TextTokens int `json:"text_tokens"`
|
||||
AudioTokens int `json:"audio_tokens"`
|
||||
ImageTokens int `json:"image_tokens"`
|
||||
}
|
||||
|
||||
// CacheCreationTokensTotal returns the cache-write token count regardless of
|
||||
// which field the upstream reported it in: Claude-derived conversions populate
|
||||
// CachedCreationTokens while OpenAI reports cache_write_tokens natively. Both
|
||||
// are billed at the cache-creation price; when both are present the larger
|
||||
// value wins so the same tokens are never double-counted.
|
||||
func (d InputTokenDetails) CacheCreationTokensTotal() int {
|
||||
if d.CacheWriteTokens > d.CachedCreationTokens {
|
||||
return d.CacheWriteTokens
|
||||
}
|
||||
return d.CachedCreationTokens
|
||||
}
|
||||
|
||||
type OutputTokenDetails struct {
|
||||
|
||||
@@ -17,12 +17,14 @@ type OpenAIResponsesCompactionRequest struct {
|
||||
// Codex compact request parity:
|
||||
// https://github.com/openai/codex/commit/53d59722268dde82fb93c1f37964ce196c2a86d7
|
||||
// https://github.com/openai/codex/commit/5d6f23a27bf9c90709af527a7108c1c2eadf5123
|
||||
Tools json.RawMessage `json:"tools,omitempty"`
|
||||
ParallelToolCalls json.RawMessage `json:"parallel_tool_calls,omitempty"`
|
||||
Reasoning *Reasoning `json:"reasoning,omitempty"`
|
||||
ServiceTier string `json:"service_tier,omitempty"`
|
||||
PromptCacheKey json.RawMessage `json:"prompt_cache_key,omitempty"`
|
||||
Text json.RawMessage `json:"text,omitempty"`
|
||||
Tools json.RawMessage `json:"tools,omitempty"`
|
||||
ParallelToolCalls json.RawMessage `json:"parallel_tool_calls,omitempty"`
|
||||
Reasoning *Reasoning `json:"reasoning,omitempty"`
|
||||
ServiceTier string `json:"service_tier,omitempty"`
|
||||
PromptCacheKey json.RawMessage `json:"prompt_cache_key,omitempty"`
|
||||
PromptCacheOptions json.RawMessage `json:"prompt_cache_options,omitempty"`
|
||||
PromptCacheRetention json.RawMessage `json:"prompt_cache_retention,omitempty"`
|
||||
Text json.RawMessage `json:"text,omitempty"`
|
||||
}
|
||||
|
||||
func (r *OpenAIResponsesCompactionRequest) GetTokenCountMeta() *types.TokenCountMeta {
|
||||
|
||||
Reference in New Issue
Block a user