From a6aca489e77c0544c02155ef1b6caadca07960aa Mon Sep 17 00:00:00 2001 From: Irash Perera Date: Thu, 20 Aug 2026 09:24:32 +0530 Subject: [PATCH 1/7] Add usage extraction fields to LLM provider templates --- .../llm-provider-template-management.md | 21 + docs/rest-apis/gateway/schemas.md | 643 +++++++- .../api/management-openapi.yaml | 73 + .../anthropic-template.yaml | 50 + .../awsbedrock-template.yaml | 38 +- .../gemini-template.yaml | 42 +- .../mistral-template.yaml | 6 + .../openai-template.yaml | 29 + .../pkg/api/management/generated.go | 573 +++---- .../pkg/config/llm_parser_test.go | 135 ++ .../pkg/config/llm_validator.go | 110 ++ .../config/llm_validator_additional_test.go | 223 +++ .../pkg/config/llm_validator_test.go | 65 + .../internal/kernel/execution_context.go | 15 + .../internal/kernel/execution_context_test.go | 71 + .../api/v1/llmprovidertemplate_types.go | 52 + .../api/v1/zz_generated.deepcopy.go | 106 +- .../api/v1alpha1/llmprovidertemplate_types.go | 52 + .../api/v1alpha1/zz_generated.deepcopy.go | 106 +- ...latform.wso2.com_llmprovidertemplates.yaml | 1334 +++++++++++++++++ ...latform.wso2.com_llmprovidertemplates.yaml | 1334 +++++++++++++++++ 21 files changed, 4761 insertions(+), 317 deletions(-) diff --git a/docs/rest-apis/gateway/llm-provider-template-management.md b/docs/rest-apis/gateway/llm-provider-template-management.md index 11c6b7c0cf..50d6d453a8 100644 --- a/docs/rest-apis/gateway/llm-provider-template-management.md +++ b/docs/rest-apis/gateway/llm-provider-template-management.md @@ -254,11 +254,24 @@ Status Code **200** |»»»» promptTokens|[ExtractionIdentifier](schemas.md#schemaextractionidentifier)|false|none|none| |»»»»» location|string|true|none|Where to find the token information| |»»»»» identifier|string|true|none|JSONPath expression or header name to identify the token value| +|»»»»» fallbackIdentifiers|[string]|false|none|Additional locations to try when the primary identifier yields no
value, in order. Used where one provider returns several response
shapes on the same path.| +|»»»»» valueMap|object|false|none|Maps values this provider reports onto the vocabulary the gateway
bills on. A reported value that is not a key is used unchanged.
Used where providers name the same billing tier differently.| +|»»»»»» **additionalProperties**|string|false|none|none| |»»»» completionTokens|[ExtractionIdentifier](schemas.md#schemaextractionidentifier)|false|none|none| |»»»» totalTokens|[ExtractionIdentifier](schemas.md#schemaextractionidentifier)|false|none|none| |»»»» remainingTokens|[ExtractionIdentifier](schemas.md#schemaextractionidentifier)|false|none|none| |»»»» requestModel|[ExtractionIdentifier](schemas.md#schemaextractionidentifier)|false|none|none| |»»»» responseModel|[ExtractionIdentifier](schemas.md#schemaextractionidentifier)|false|none|none| +|»»»» cachedTokens|[ExtractionIdentifier](schemas.md#schemaextractionidentifier)|false|none|none| +|»»»» cacheWriteTokens|[ExtractionIdentifier](schemas.md#schemaextractionidentifier)|false|none|none| +|»»»» cacheWrite1hTokens|[ExtractionIdentifier](schemas.md#schemaextractionidentifier)|false|none|none| +|»»»» reasoningTokens|[ExtractionIdentifier](schemas.md#schemaextractionidentifier)|false|none|none| +|»»»» audioInputTokens|[ExtractionIdentifier](schemas.md#schemaextractionidentifier)|false|none|none| +|»»»» audioOutputTokens|[ExtractionIdentifier](schemas.md#schemaextractionidentifier)|false|none|none| +|»»»» serviceTier|[ExtractionIdentifier](schemas.md#schemaextractionidentifier)|false|none|none| +|»»»» providerFields|object|false|none|Named locations a provider-specific calculator needs but the closed
vocabulary does not cover. The value at each location is handed to
the calculator as-is; only its position is declared here.| +|»»»»» **additionalProperties**|[ExtractionIdentifier](schemas.md#schemaextractionidentifier)|false|none|none| +|»»»» cacheAccounting|string|false|none|Whether the cached token count reported by the provider is already
part of the input token total, or additional to it. One of
'inclusive' or 'additive'. Defaults to inclusive when omitted.| |»»»» resourceMappings|[LLMProviderTemplateResourceMappings](schemas.md#schemallmprovidertemplateresourcemappings)|false|none|none| |»»»»» resources|[[LLMProviderTemplateResourceMapping](schemas.md#schemallmprovidertemplateresourcemapping)]|false|none|none| |»»»»»» resource|string|true|none|Resource path pattern for this mapping| @@ -268,6 +281,14 @@ Status Code **200** |»»»»»» remainingTokens|[ExtractionIdentifier](schemas.md#schemaextractionidentifier)|false|none|none| |»»»»»» requestModel|[ExtractionIdentifier](schemas.md#schemaextractionidentifier)|false|none|none| |»»»»»» responseModel|[ExtractionIdentifier](schemas.md#schemaextractionidentifier)|false|none|none| +|»»»»»» cachedTokens|[ExtractionIdentifier](schemas.md#schemaextractionidentifier)|false|none|none| +|»»»»»» cacheWriteTokens|[ExtractionIdentifier](schemas.md#schemaextractionidentifier)|false|none|none| +|»»»»»» cacheWrite1hTokens|[ExtractionIdentifier](schemas.md#schemaextractionidentifier)|false|none|none| +|»»»»»» reasoningTokens|[ExtractionIdentifier](schemas.md#schemaextractionidentifier)|false|none|none| +|»»»»»» audioInputTokens|[ExtractionIdentifier](schemas.md#schemaextractionidentifier)|false|none|none| +|»»»»»» audioOutputTokens|[ExtractionIdentifier](schemas.md#schemaextractionidentifier)|false|none|none| +|»»»»»» serviceTier|[ExtractionIdentifier](schemas.md#schemaextractionidentifier)|false|none|none| +|»»»»»» cacheAccounting|string|false|none|Whether the cached token count reported by the provider is already
part of the input token total, or additional to it. One of
'inclusive' or 'additive'. Overrides the template-level setting for
this resource; when omitted, the template-level value is inherited.| *and* diff --git a/docs/rest-apis/gateway/schemas.md b/docs/rest-apis/gateway/schemas.md index 9c4cec8704..20c602ea54 100644 --- a/docs/rest-apis/gateway/schemas.md +++ b/docs/rest-apis/gateway/schemas.md @@ -1785,56 +1785,348 @@ and "version": "v1.0", "promptTokens": { "location": "payload", - "identifier": "$.usage.inputTokens" + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } }, "completionTokens": { "location": "payload", - "identifier": "$.usage.inputTokens" + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } }, "totalTokens": { "location": "payload", - "identifier": "$.usage.inputTokens" + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } }, "remainingTokens": { "location": "payload", - "identifier": "$.usage.inputTokens" + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } }, "requestModel": { "location": "payload", - "identifier": "$.usage.inputTokens" + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } }, "responseModel": { "location": "payload", - "identifier": "$.usage.inputTokens" + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } + }, + "cachedTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } + }, + "cacheWriteTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } + }, + "cacheWrite1hTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } + }, + "reasoningTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } + }, + "audioInputTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } }, + "audioOutputTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } + }, + "serviceTier": { + "location": "payload", + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } + }, + "providerFields": { + "property1": { + "location": "payload", + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } + }, + "property2": { + "location": "payload", + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } + } + }, + "cacheAccounting": "inclusive", "resourceMappings": { "resources": [ { "resource": "/responses", "promptTokens": { "location": "payload", - "identifier": "$.usage.inputTokens" + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } }, "completionTokens": { "location": "payload", - "identifier": "$.usage.inputTokens" + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } }, "totalTokens": { "location": "payload", - "identifier": "$.usage.inputTokens" + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } }, "remainingTokens": { "location": "payload", - "identifier": "$.usage.inputTokens" + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } }, "requestModel": { "location": "payload", - "identifier": "$.usage.inputTokens" + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } }, "responseModel": { "location": "payload", - "identifier": "$.usage.inputTokens" - } + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } + }, + "cachedTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } + }, + "cacheWriteTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } + }, + "cacheWrite1hTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } + }, + "reasoningTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } + }, + "audioInputTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } + }, + "audioOutputTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } + }, + "serviceTier": { + "location": "payload", + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } + }, + "cacheAccounting": "inclusive" } ] } @@ -1856,6 +2148,16 @@ and |remainingTokens|[ExtractionIdentifier](#schemaextractionidentifier)|false|none|none| |requestModel|[ExtractionIdentifier](#schemaextractionidentifier)|false|none|none| |responseModel|[ExtractionIdentifier](#schemaextractionidentifier)|false|none|none| +|cachedTokens|[ExtractionIdentifier](#schemaextractionidentifier)|false|none|none| +|cacheWriteTokens|[ExtractionIdentifier](#schemaextractionidentifier)|false|none|none| +|cacheWrite1hTokens|[ExtractionIdentifier](#schemaextractionidentifier)|false|none|none| +|reasoningTokens|[ExtractionIdentifier](#schemaextractionidentifier)|false|none|none| +|audioInputTokens|[ExtractionIdentifier](#schemaextractionidentifier)|false|none|none| +|audioOutputTokens|[ExtractionIdentifier](#schemaextractionidentifier)|false|none|none| +|serviceTier|[ExtractionIdentifier](#schemaextractionidentifier)|false|none|none| +|providerFields|object|false|none|Named locations a provider-specific calculator needs but the closed
vocabulary does not cover. The value at each location is handed to
the calculator as-is; only its position is declared here.| +|» **additionalProperties**|[ExtractionIdentifier](#schemaextractionidentifier)|false|none|none| +|cacheAccounting|string|false|none|Whether the cached token count reported by the provider is already
part of the input token total, or additional to it. One of
'inclusive' or 'additive'. Defaults to inclusive when omitted.| |resourceMappings|[LLMProviderTemplateResourceMappings](#schemallmprovidertemplateresourcemappings)|false|none|none| ## LLMProviderTemplateResourceMappings @@ -1872,28 +2174,161 @@ and "resource": "/responses", "promptTokens": { "location": "payload", - "identifier": "$.usage.inputTokens" + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } }, "completionTokens": { "location": "payload", - "identifier": "$.usage.inputTokens" + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } }, "totalTokens": { "location": "payload", - "identifier": "$.usage.inputTokens" + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } }, "remainingTokens": { "location": "payload", - "identifier": "$.usage.inputTokens" + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } }, "requestModel": { "location": "payload", - "identifier": "$.usage.inputTokens" + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } }, "responseModel": { "location": "payload", - "identifier": "$.usage.inputTokens" - } + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } + }, + "cachedTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } + }, + "cacheWriteTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } + }, + "cacheWrite1hTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } + }, + "reasoningTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } + }, + "audioInputTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } + }, + "audioOutputTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } + }, + "serviceTier": { + "location": "payload", + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } + }, + "cacheAccounting": "inclusive" } ] } @@ -1918,28 +2353,161 @@ and "resource": "/responses", "promptTokens": { "location": "payload", - "identifier": "$.usage.inputTokens" + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } }, "completionTokens": { "location": "payload", - "identifier": "$.usage.inputTokens" + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } }, "totalTokens": { "location": "payload", - "identifier": "$.usage.inputTokens" + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } }, "remainingTokens": { "location": "payload", - "identifier": "$.usage.inputTokens" + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } }, "requestModel": { "location": "payload", - "identifier": "$.usage.inputTokens" + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } }, "responseModel": { "location": "payload", - "identifier": "$.usage.inputTokens" - } + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } + }, + "cachedTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } + }, + "cacheWriteTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } + }, + "cacheWrite1hTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } + }, + "reasoningTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } + }, + "audioInputTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } + }, + "audioOutputTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } + }, + "serviceTier": { + "location": "payload", + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } + }, + "cacheAccounting": "inclusive" } ``` @@ -1955,6 +2523,14 @@ and |remainingTokens|[ExtractionIdentifier](#schemaextractionidentifier)|false|none|none| |requestModel|[ExtractionIdentifier](#schemaextractionidentifier)|false|none|none| |responseModel|[ExtractionIdentifier](#schemaextractionidentifier)|false|none|none| +|cachedTokens|[ExtractionIdentifier](#schemaextractionidentifier)|false|none|none| +|cacheWriteTokens|[ExtractionIdentifier](#schemaextractionidentifier)|false|none|none| +|cacheWrite1hTokens|[ExtractionIdentifier](#schemaextractionidentifier)|false|none|none| +|reasoningTokens|[ExtractionIdentifier](#schemaextractionidentifier)|false|none|none| +|audioInputTokens|[ExtractionIdentifier](#schemaextractionidentifier)|false|none|none| +|audioOutputTokens|[ExtractionIdentifier](#schemaextractionidentifier)|false|none|none| +|serviceTier|[ExtractionIdentifier](#schemaextractionidentifier)|false|none|none| +|cacheAccounting|string|false|none|Whether the cached token count reported by the provider is already
part of the input token total, or additional to it. One of
'inclusive' or 'additive'. Overrides the template-level setting for
this resource; when omitted, the template-level value is inherited.| ## ExtractionIdentifier @@ -1966,7 +2542,15 @@ and ```json { "location": "payload", - "identifier": "$.usage.inputTokens" + "identifier": "$.usage.inputTokens", + "fallbackIdentifiers": [ + "$.usage.input_tokens", + "$.usage.prompt_tokens" + ], + "valueMap": { + "ON_DEMAND_PRIORITY": "priority", + "ON_DEMAND_FLEX": "flex" + } } ``` @@ -1977,6 +2561,9 @@ and |---|---|---|---|---| |location|string|true|none|Where to find the token information| |identifier|string|true|none|JSONPath expression or header name to identify the token value| +|fallbackIdentifiers|[string]|false|none|Additional locations to try when the primary identifier yields no
value, in order. Used where one provider returns several response
shapes on the same path.| +|valueMap|object|false|none|Maps values this provider reports onto the vocabulary the gateway
bills on. A reported value that is not a key is used unchanged.
Used where providers name the same billing tier differently.| +|» **additionalProperties**|string|false|none|none| ##### Enumerated Values diff --git a/gateway/gateway-controller/api/management-openapi.yaml b/gateway/gateway-controller/api/management-openapi.yaml index 454eba734d..6ea472d7e5 100644 --- a/gateway/gateway-controller/api/management-openapi.yaml +++ b/gateway/gateway-controller/api/management-openapi.yaml @@ -4113,6 +4113,35 @@ components: $ref: '#/components/schemas/ExtractionIdentifier' responseModel: $ref: '#/components/schemas/ExtractionIdentifier' + cachedTokens: + $ref: '#/components/schemas/ExtractionIdentifier' + cacheWriteTokens: + $ref: '#/components/schemas/ExtractionIdentifier' + cacheWrite1hTokens: + $ref: '#/components/schemas/ExtractionIdentifier' + reasoningTokens: + $ref: '#/components/schemas/ExtractionIdentifier' + audioInputTokens: + $ref: '#/components/schemas/ExtractionIdentifier' + audioOutputTokens: + $ref: '#/components/schemas/ExtractionIdentifier' + serviceTier: + $ref: '#/components/schemas/ExtractionIdentifier' + providerFields: + type: object + additionalProperties: + $ref: '#/components/schemas/ExtractionIdentifier' + description: | + Named locations a provider-specific calculator needs but the closed + vocabulary does not cover. The value at each location is handed to + the calculator as-is; only its position is declared here. + cacheAccounting: + type: string + description: | + Whether the cached token count reported by the provider is already + part of the input token total, or additional to it. One of + 'inclusive' or 'additive'. Defaults to inclusive when omitted. + example: inclusive resourceMappings: $ref: '#/components/schemas/LLMProviderTemplateResourceMappings' @@ -4146,6 +4175,28 @@ components: $ref: '#/components/schemas/ExtractionIdentifier' responseModel: $ref: '#/components/schemas/ExtractionIdentifier' + cachedTokens: + $ref: '#/components/schemas/ExtractionIdentifier' + cacheWriteTokens: + $ref: '#/components/schemas/ExtractionIdentifier' + cacheWrite1hTokens: + $ref: '#/components/schemas/ExtractionIdentifier' + reasoningTokens: + $ref: '#/components/schemas/ExtractionIdentifier' + audioInputTokens: + $ref: '#/components/schemas/ExtractionIdentifier' + audioOutputTokens: + $ref: '#/components/schemas/ExtractionIdentifier' + serviceTier: + $ref: '#/components/schemas/ExtractionIdentifier' + cacheAccounting: + type: string + description: | + Whether the cached token count reported by the provider is already + part of the input token total, or additional to it. One of + 'inclusive' or 'additive'. Overrides the template-level setting for + this resource; when omitted, the template-level value is inherited. + example: inclusive ExtractionIdentifier: type: object @@ -4166,6 +4217,28 @@ components: type: string description: JSONPath expression or header name to identify the token value example: $.usage.inputTokens + fallbackIdentifiers: + type: array + items: + type: string + description: | + Additional locations to try when the primary identifier yields no + value, in order. Used where one provider returns several response + shapes on the same path. + example: + - $.usage.input_tokens + - $.usage.prompt_tokens + valueMap: + type: object + additionalProperties: + type: string + description: | + Maps values this provider reports onto the vocabulary the gateway + bills on. A reported value that is not a key is used unchanged. + Used where providers name the same billing tier differently. + example: + ON_DEMAND_PRIORITY: priority + ON_DEMAND_FLEX: flex LLMProviderConfigurationRequest: type: object diff --git a/gateway/gateway-controller/default-llm-provider-templates/anthropic-template.yaml b/gateway/gateway-controller/default-llm-provider-templates/anthropic-template.yaml index 0b4f38524f..28b16f5caa 100644 --- a/gateway/gateway-controller/default-llm-provider-templates/anthropic-template.yaml +++ b/gateway/gateway-controller/default-llm-provider-templates/anthropic-template.yaml @@ -28,9 +28,13 @@ spec: promptTokens: location: payload identifier: $.usage.input_tokens + fallbackIdentifiers: + - $.message.usage.input_tokens completionTokens: location: payload identifier: $.usage.output_tokens + fallbackIdentifiers: + - $.message.usage.output_tokens remainingTokens: location: header identifier: anthropic-ratelimit-tokens-remaining @@ -40,3 +44,49 @@ spec: responseModel: location: payload identifier: $.model + fallbackIdentifiers: + - $.message.model + cachedTokens: + location: payload + identifier: $.usage.cache_read_input_tokens + fallbackIdentifiers: + - $.message.usage.cache_read_input_tokens + cacheWriteTokens: + location: payload + identifier: $.usage.cache_creation.ephemeral_5m_input_tokens + fallbackIdentifiers: + - $.message.usage.cache_creation.ephemeral_5m_input_tokens + cacheWrite1hTokens: + location: payload + identifier: $.usage.cache_creation.ephemeral_1h_input_tokens + fallbackIdentifiers: + - $.message.usage.cache_creation.ephemeral_1h_input_tokens + serviceTier: + location: payload + identifier: $.usage.service_tier + fallbackIdentifiers: + - $.message.usage.service_tier + valueMap: + priority: priority + batch: batch + providerFields: + # The streaming envelope nests usage under "message", so both locations are + # declared and the calculator never sees the difference. + inferenceGeo: + location: payload + identifier: $.usage.inference_geo + fallbackIdentifiers: + - $.message.usage.inference_geo + webSearchRequests: + location: payload + identifier: $.usage.server_tool_use.web_search_requests + fallbackIdentifiers: + - $.message.usage.server_tool_use.web_search_requests + # Request-side parameters Anthropic does not echo in the response. + speed: + location: payload + identifier: $.speed + searchContextSize: + location: payload + identifier: $.web_search_options.search_context_size + cacheAccounting: additive diff --git a/gateway/gateway-controller/default-llm-provider-templates/awsbedrock-template.yaml b/gateway/gateway-controller/default-llm-provider-templates/awsbedrock-template.yaml index 196359ab59..438ad572da 100644 --- a/gateway/gateway-controller/default-llm-provider-templates/awsbedrock-template.yaml +++ b/gateway/gateway-controller/default-llm-provider-templates/awsbedrock-template.yaml @@ -28,15 +28,49 @@ spec: promptTokens: location: payload identifier: $.usage.inputTokens + fallbackIdentifiers: + - $.usage.input_tokens + - $.usage.prompt_tokens + - $.inputTextTokenCount completionTokens: location: payload identifier: $.usage.outputTokens + fallbackIdentifiers: + - $.usage.output_tokens + - $.usage.completion_tokens + - $.results[0].tokenCount totalTokens: location: payload identifier: $.usage.totalTokens requestModel: location: pathParam - identifier: model/([A-Za-z0-9.:-]+)/ + identifier: model/([A-Za-z0-9.:%-]+)/ responseModel: location: pathParam - identifier: model/([A-Za-z0-9.:-]+)/ + identifier: model/([A-Za-z0-9.:%-]+)/ + cachedTokens: + location: payload + identifier: $.usage.cacheReadInputTokens + fallbackIdentifiers: + - $.usage.cache_read_input_tokens + - $.usage.prompt_tokens_details.cached_tokens + cacheWriteTokens: + location: payload + identifier: $.usage.cacheWriteInputTokens + fallbackIdentifiers: + - $.usage.cache_creation_input_tokens + - $.usage.prompt_tokens_details.cache_write_tokens + serviceTier: + location: payload + identifier: $.serviceTier.type + valueMap: + priority: priority + flex: flex + providerFields: + # The per-entry cache array. Bedrock reports one entry per TTL, so the + # 5-minute and 1-hour split is read from here rather than from a single + # field. Only its position is declared; the calculator walks the entries. + cacheDetails: + location: payload + identifier: $.usage.cacheDetails + cacheAccounting: additive diff --git a/gateway/gateway-controller/default-llm-provider-templates/gemini-template.yaml b/gateway/gateway-controller/default-llm-provider-templates/gemini-template.yaml index 79a5a8be94..047c68ccf1 100644 --- a/gateway/gateway-controller/default-llm-provider-templates/gemini-template.yaml +++ b/gateway/gateway-controller/default-llm-provider-templates/gemini-template.yaml @@ -39,7 +39,47 @@ spec: identifier: x-ratelimit-remaining-tokens requestModel: location: pathParam - identifier: (?<=models/)[a-zA-Z0-9.\-]+ + identifier: models/([a-zA-Z0-9.\-]+) responseModel: location: payload identifier: $.modelVersion + serviceTier: + location: payload + identifier: $.usageMetadata.serviceTier # Developer API + fallbackIdentifiers: + - $.usageMetadata.trafficType # Vertex API + valueMap: + priority: priority + flex: flex + ON_DEMAND_PRIORITY: priority + ON_DEMAND_FLEX: flex + cachedTokens: + location: payload + identifier: $.usageMetadata.cachedContentTokenCount + reasoningTokens: + location: payload + identifier: $.usageMetadata.thoughtsTokenCount + providerFields: + # Per-modality token lists. Only their position is declared; the calculator + # picks the modality it needs out of the entries. + promptTokensDetails: + location: payload + identifier: $.usageMetadata.promptTokensDetails + cacheTokensDetails: + location: payload + identifier: $.usageMetadata.cacheTokensDetails + candidatesTokensDetails: + location: payload + identifier: $.usageMetadata.candidatesTokensDetails + responseTokensDetails: + location: payload + identifier: $.usageMetadata.responseTokensDetails + toolUsePromptTokenCount: + location: payload + identifier: $.usageMetadata.toolUsePromptTokenCount + # Grounding searches are billed per query and no field reports the number, + # so the calculator counts the entries. + candidates: + location: payload + identifier: $.candidates + cacheAccounting: inclusive diff --git a/gateway/gateway-controller/default-llm-provider-templates/mistral-template.yaml b/gateway/gateway-controller/default-llm-provider-templates/mistral-template.yaml index d2bee935e4..512cad99ba 100644 --- a/gateway/gateway-controller/default-llm-provider-templates/mistral-template.yaml +++ b/gateway/gateway-controller/default-llm-provider-templates/mistral-template.yaml @@ -43,3 +43,9 @@ spec: responseModel: location: payload identifier: $.model + providerFields: + # Voxtral bills audio input by duration; prompt_tokens covers only text. + promptAudioSeconds: + location: payload + identifier: $.usage.prompt_audio_seconds + cacheAccounting: inclusive diff --git a/gateway/gateway-controller/default-llm-provider-templates/openai-template.yaml b/gateway/gateway-controller/default-llm-provider-templates/openai-template.yaml index b22d6ae62f..c039abcb1a 100644 --- a/gateway/gateway-controller/default-llm-provider-templates/openai-template.yaml +++ b/gateway/gateway-controller/default-llm-provider-templates/openai-template.yaml @@ -43,6 +43,35 @@ spec: responseModel: location: payload identifier: $.model + cachedTokens: + location: payload + identifier: $.usage.prompt_tokens_details.cached_tokens + reasoningTokens: + location: payload + identifier: $.usage.completion_tokens_details.reasoning_tokens + audioInputTokens: + location: payload + identifier: $.usage.prompt_tokens_details.audio_tokens + audioOutputTokens: + location: payload + identifier: $.usage.completion_tokens_details.audio_tokens + serviceTier: + location: payload + identifier: $.service_tier + valueMap: + priority: priority + flex: flex + fast: priority + providerFields: + # A url_citation annotation inside a choice marks a web search call. + choices: + location: payload + identifier: $.choices + # The search depth is request-side only. + searchContextSize: + location: payload + identifier: $.web_search_options.search_context_size + cacheAccounting: inclusive resourceMappings: resources: - resource: /responses diff --git a/gateway/gateway-controller/pkg/api/management/generated.go b/gateway/gateway-controller/pkg/api/management/generated.go index 5ab19469d7..c2c071374e 100644 --- a/gateway/gateway-controller/pkg/api/management/generated.go +++ b/gateway/gateway-controller/pkg/api/management/generated.go @@ -679,11 +679,21 @@ type ErrorResponse struct { // ExtractionIdentifier defines model for ExtractionIdentifier. type ExtractionIdentifier struct { + // FallbackIdentifiers Additional locations to try when the primary identifier yields no + // value, in order. Used where one provider returns several response + // shapes on the same path. + FallbackIdentifiers *[]string `json:"fallbackIdentifiers,omitempty" yaml:"fallbackIdentifiers,omitempty"` + // Identifier JSONPath expression or header name to identify the token value Identifier string `json:"identifier" yaml:"identifier"` // Location Where to find the token information Location ExtractionIdentifierLocation `json:"location" yaml:"location"` + + // ValueMap Maps values this provider reports onto the vocabulary the gateway + // bills on. A reported value that is not a key is used unchanged. + // Used where providers name the same billing tier differently. + ValueMap *map[string]string `json:"valueMap,omitempty" yaml:"valueMap,omitempty"` } // ExtractionIdentifierLocation Where to find the token information @@ -855,7 +865,17 @@ type LLMProviderTemplateKind string // LLMProviderTemplateData defines model for LLMProviderTemplateData. type LLMProviderTemplateData struct { - CompletionTokens *ExtractionIdentifier `json:"completionTokens,omitempty" yaml:"completionTokens,omitempty"` + AudioInputTokens *ExtractionIdentifier `json:"audioInputTokens,omitempty" yaml:"audioInputTokens,omitempty"` + AudioOutputTokens *ExtractionIdentifier `json:"audioOutputTokens,omitempty" yaml:"audioOutputTokens,omitempty"` + + // CacheAccounting Whether the cached token count reported by the provider is already + // part of the input token total, or additional to it. One of + // 'inclusive' or 'additive'. Defaults to inclusive when omitted. + CacheAccounting *string `json:"cacheAccounting,omitempty" yaml:"cacheAccounting,omitempty"` + CacheWrite1hTokens *ExtractionIdentifier `json:"cacheWrite1hTokens,omitempty" yaml:"cacheWrite1hTokens,omitempty"` + CacheWriteTokens *ExtractionIdentifier `json:"cacheWriteTokens,omitempty" yaml:"cacheWriteTokens,omitempty"` + CachedTokens *ExtractionIdentifier `json:"cachedTokens,omitempty" yaml:"cachedTokens,omitempty"` + CompletionTokens *ExtractionIdentifier `json:"completionTokens,omitempty" yaml:"completionTokens,omitempty"` // DisplayName Human-readable LLM Template name DisplayName string `json:"displayName" yaml:"displayName"` @@ -868,12 +888,19 @@ type LLMProviderTemplateData struct { // ManagedBy Origin of the template. Built-in templates use 'wso2'; custom // templates default to 'customer' and may be set to any value. - ManagedBy *string `json:"managedBy,omitempty" yaml:"managedBy,omitempty"` - PromptTokens *ExtractionIdentifier `json:"promptTokens,omitempty" yaml:"promptTokens,omitempty"` + ManagedBy *string `json:"managedBy,omitempty" yaml:"managedBy,omitempty"` + PromptTokens *ExtractionIdentifier `json:"promptTokens,omitempty" yaml:"promptTokens,omitempty"` + + // ProviderFields Named locations a provider-specific calculator needs but the closed + // vocabulary does not cover. The value at each location is handed to + // the calculator as-is; only its position is declared here. + ProviderFields *map[string]ExtractionIdentifier `json:"providerFields,omitempty" yaml:"providerFields,omitempty"` + ReasoningTokens *ExtractionIdentifier `json:"reasoningTokens,omitempty" yaml:"reasoningTokens,omitempty"` RemainingTokens *ExtractionIdentifier `json:"remainingTokens,omitempty" yaml:"remainingTokens,omitempty"` RequestModel *ExtractionIdentifier `json:"requestModel,omitempty" yaml:"requestModel,omitempty"` ResourceMappings *LLMProviderTemplateResourceMappings `json:"resourceMappings,omitempty" yaml:"resourceMappings,omitempty"` ResponseModel *ExtractionIdentifier `json:"responseModel,omitempty" yaml:"responseModel,omitempty"` + ServiceTier *ExtractionIdentifier `json:"serviceTier,omitempty" yaml:"serviceTier,omitempty"` TotalTokens *ExtractionIdentifier `json:"totalTokens,omitempty" yaml:"totalTokens,omitempty"` // Version Template content version (e.g. v1.0). Multiple versions of the same @@ -900,14 +927,27 @@ type LLMProviderTemplateRequestKind string // LLMProviderTemplateResourceMapping defines model for LLMProviderTemplateResourceMapping. type LLMProviderTemplateResourceMapping struct { - CompletionTokens *ExtractionIdentifier `json:"completionTokens,omitempty" yaml:"completionTokens,omitempty"` - PromptTokens *ExtractionIdentifier `json:"promptTokens,omitempty" yaml:"promptTokens,omitempty"` - RemainingTokens *ExtractionIdentifier `json:"remainingTokens,omitempty" yaml:"remainingTokens,omitempty"` - RequestModel *ExtractionIdentifier `json:"requestModel,omitempty" yaml:"requestModel,omitempty"` + AudioInputTokens *ExtractionIdentifier `json:"audioInputTokens,omitempty" yaml:"audioInputTokens,omitempty"` + AudioOutputTokens *ExtractionIdentifier `json:"audioOutputTokens,omitempty" yaml:"audioOutputTokens,omitempty"` + + // CacheAccounting Whether the cached token count reported by the provider is already + // part of the input token total, or additional to it. One of + // 'inclusive' or 'additive'. Overrides the template-level setting for + // this resource; when omitted, the template-level value is inherited. + CacheAccounting *string `json:"cacheAccounting,omitempty" yaml:"cacheAccounting,omitempty"` + CacheWrite1hTokens *ExtractionIdentifier `json:"cacheWrite1hTokens,omitempty" yaml:"cacheWrite1hTokens,omitempty"` + CacheWriteTokens *ExtractionIdentifier `json:"cacheWriteTokens,omitempty" yaml:"cacheWriteTokens,omitempty"` + CachedTokens *ExtractionIdentifier `json:"cachedTokens,omitempty" yaml:"cachedTokens,omitempty"` + CompletionTokens *ExtractionIdentifier `json:"completionTokens,omitempty" yaml:"completionTokens,omitempty"` + PromptTokens *ExtractionIdentifier `json:"promptTokens,omitempty" yaml:"promptTokens,omitempty"` + ReasoningTokens *ExtractionIdentifier `json:"reasoningTokens,omitempty" yaml:"reasoningTokens,omitempty"` + RemainingTokens *ExtractionIdentifier `json:"remainingTokens,omitempty" yaml:"remainingTokens,omitempty"` + RequestModel *ExtractionIdentifier `json:"requestModel,omitempty" yaml:"requestModel,omitempty"` // Resource Resource path pattern for this mapping Resource string `json:"resource" yaml:"resource"` ResponseModel *ExtractionIdentifier `json:"responseModel,omitempty" yaml:"responseModel,omitempty"` + ServiceTier *ExtractionIdentifier `json:"serviceTier,omitempty" yaml:"serviceTier,omitempty"` TotalTokens *ExtractionIdentifier `json:"totalTokens,omitempty" yaml:"totalTokens,omitempty"` } @@ -4561,261 +4601,270 @@ func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.H // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "/wcAAP//nIc3NSrXAQA=", + "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", + "/22YsUw64QEA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/gateway/gateway-controller/pkg/config/llm_parser_test.go b/gateway/gateway-controller/pkg/config/llm_parser_test.go index 4de0978f3e..e6eb5b6531 100644 --- a/gateway/gateway-controller/pkg/config/llm_parser_test.go +++ b/gateway/gateway-controller/pkg/config/llm_parser_test.go @@ -1031,3 +1031,138 @@ spec: assert.NotNil(t, template.Spec.PromptTokens) assert.NotNil(t, template.Spec.CompletionTokens) } + +// A template carrying the usage detail fields must survive YAML parsing with +// every field intact, including inside a resource mapping. +func TestParseLLMProviderTemplate_UsageDetailFieldsSurviveYAML(t *testing.T) { + yamlSpec := []byte(` +apiVersion: gateway.api-platform.wso2.com/v1 +kind: LlmProviderTemplate +metadata: + name: roundtrip-check +spec: + displayName: Roundtrip Check + cacheAccounting: additive + promptTokens: + location: payload + identifier: $.usage.input_tokens + cachedTokens: + location: payload + identifier: $.usage.cache_read_input_tokens + cacheWriteTokens: + location: payload + identifier: $.usage.cache_creation.ephemeral_5m_input_tokens + cacheWrite1hTokens: + location: payload + identifier: $.usage.cache_creation.ephemeral_1h_input_tokens + reasoningTokens: + location: payload + identifier: $.usage.completion_tokens_details.reasoning_tokens + audioInputTokens: + location: payload + identifier: $.usage.prompt_tokens_details.audio_tokens + audioOutputTokens: + location: payload + identifier: $.usage.completion_tokens_details.audio_tokens + serviceTier: + location: payload + identifier: $.usage.service_tier + valueMap: + ON_DEMAND_PRIORITY: priority + ON_DEMAND_FLEX: flex + providerFields: + cacheDetails: + location: payload + identifier: $.usage.cacheDetails + cacheTokensDetails: + location: payload + identifier: $.usageMetadata.cacheTokensDetails + resourceMappings: + resources: + - resource: /responses + cacheAccounting: inclusive + cachedTokens: + location: payload + identifier: $.usage.input_tokens_details.cached_tokens +`) + + parser := NewParser() + + var tmpl api.LLMProviderTemplate + if err := parser.Parse(yamlSpec, "application/yaml", &tmpl); err != nil { + t.Fatalf("parse failed: %v", err) + } + + spec := tmpl.Spec + + // The parsed template must also pass validation, so a field that parses but + // fails deploy-time checks is caught here too. + if errs := NewLLMValidator().validateTemplateSpec(&spec); len(errs) != 0 { + t.Fatalf("expected valid template, got %v", errs) + } + + if spec.CacheAccounting == nil || *spec.CacheAccounting != "additive" { + t.Errorf("cacheAccounting not preserved: %v", spec.CacheAccounting) + } + + checks := map[string]*api.ExtractionIdentifier{ + "$.usage.cache_read_input_tokens": spec.CachedTokens, + "$.usage.cache_creation.ephemeral_5m_input_tokens": spec.CacheWriteTokens, + "$.usage.cache_creation.ephemeral_1h_input_tokens": spec.CacheWrite1hTokens, + "$.usage.completion_tokens_details.reasoning_tokens": spec.ReasoningTokens, + "$.usage.prompt_tokens_details.audio_tokens": spec.AudioInputTokens, + "$.usage.completion_tokens_details.audio_tokens": spec.AudioOutputTokens, + "$.usage.service_tier": spec.ServiceTier, + } + for want, got := range checks { + if got == nil { + t.Errorf("field for %q was dropped", want) + continue + } + if got.Identifier != want { + t.Errorf("identifier mismatch: got %q want %q", got.Identifier, want) + } + } + + // valueMap and providerFields are map-typed, so a missing or mistagged field + // on the generated model drops them silently rather than failing the parse. + if spec.ServiceTier == nil || spec.ServiceTier.ValueMap == nil { + t.Fatal("serviceTier.valueMap was dropped by the typed model") + } + valueMap := *spec.ServiceTier.ValueMap + if got := valueMap["ON_DEMAND_PRIORITY"]; got != "priority" { + t.Errorf("ON_DEMAND_PRIORITY = %q, want priority", got) + } + if got := valueMap["ON_DEMAND_FLEX"]; got != "flex" { + t.Errorf("ON_DEMAND_FLEX = %q, want flex", got) + } + + if spec.ProviderFields == nil { + t.Fatal("providerFields was dropped by the typed model") + } + providerFields := *spec.ProviderFields + if len(providerFields) != 2 { + t.Fatalf("got %d providerFields entries, want 2: %#v", len(providerFields), providerFields) + } + if got := providerFields["cacheDetails"]; got.Identifier != "$.usage.cacheDetails" { + t.Errorf("cacheDetails identifier = %q", got.Identifier) + } + if got := providerFields["cacheTokensDetails"]; string(got.Location) != "payload" { + t.Errorf("cacheTokensDetails location = %q", got.Location) + } + + if spec.ResourceMappings == nil || spec.ResourceMappings.Resources == nil { + t.Fatal("resourceMappings was dropped") + } + resources := *spec.ResourceMappings.Resources + if len(resources) != 1 { + t.Fatalf("expected 1 resource mapping, got %d", len(resources)) + } + m := resources[0] + if m.CachedTokens == nil || m.CachedTokens.Identifier != "$.usage.input_tokens_details.cached_tokens" { + t.Errorf("mapping cachedTokens not preserved: %v", m.CachedTokens) + } + if m.CacheAccounting == nil || *m.CacheAccounting != "inclusive" { + t.Errorf("mapping cacheAccounting not preserved: %v", m.CacheAccounting) + } +} diff --git a/gateway/gateway-controller/pkg/config/llm_validator.go b/gateway/gateway-controller/pkg/config/llm_validator.go index 293828b2be..2f72a5ea4d 100644 --- a/gateway/gateway-controller/pkg/config/llm_validator.go +++ b/gateway/gateway-controller/pkg/config/llm_validator.go @@ -173,6 +173,36 @@ func (v *LLMValidator) validateTemplateSpec(spec *api.LLMProviderTemplateData) [ spec.RemainingTokens)...) } + if spec.CachedTokens != nil { + errors = append(errors, v.validateExtractionIdentifier("spec.cachedTokens", spec.CachedTokens)...) + } + + if spec.CacheWriteTokens != nil { + errors = append(errors, v.validateExtractionIdentifier("spec.cacheWriteTokens", spec.CacheWriteTokens)...) + } + + if spec.CacheWrite1hTokens != nil { + errors = append(errors, v.validateExtractionIdentifier("spec.cacheWrite1hTokens", spec.CacheWrite1hTokens)...) + } + + if spec.ReasoningTokens != nil { + errors = append(errors, v.validateExtractionIdentifier("spec.reasoningTokens", spec.ReasoningTokens)...) + } + + if spec.AudioInputTokens != nil { + errors = append(errors, v.validateExtractionIdentifier("spec.audioInputTokens", spec.AudioInputTokens)...) + } + + if spec.AudioOutputTokens != nil { + errors = append(errors, v.validateExtractionIdentifier("spec.audioOutputTokens", spec.AudioOutputTokens)...) + } + + if spec.ServiceTier != nil { + errors = append(errors, v.validateExtractionIdentifier("spec.serviceTier", spec.ServiceTier)...) + } + + errors = append(errors, v.validateCacheAccounting("spec.cacheAccounting", spec.CacheAccounting)...) + if spec.ResourceMappings != nil { errors = append(errors, v.validateTemplateResourceMappings("spec.resourceMappings", spec.ResourceMappings)...) } @@ -244,6 +274,44 @@ func (v *LLMValidator) validateTemplateResourceMapping(fieldPrefix string, errors = append(errors, v.validateExtractionIdentifier(fieldPrefix+".responseModel", mapping.ResponseModel)...) } + if mapping.CachedTokens != nil { + errors = append(errors, v.validateExtractionIdentifier( + fmt.Sprintf("%s.cachedTokens", fieldPrefix), mapping.CachedTokens)...) + } + + if mapping.CacheWriteTokens != nil { + errors = append(errors, v.validateExtractionIdentifier( + fmt.Sprintf("%s.cacheWriteTokens", fieldPrefix), mapping.CacheWriteTokens)...) + } + + if mapping.CacheWrite1hTokens != nil { + errors = append(errors, v.validateExtractionIdentifier( + fmt.Sprintf("%s.cacheWrite1hTokens", fieldPrefix), mapping.CacheWrite1hTokens)...) + } + + if mapping.ReasoningTokens != nil { + errors = append(errors, v.validateExtractionIdentifier( + fmt.Sprintf("%s.reasoningTokens", fieldPrefix), mapping.ReasoningTokens)...) + } + + if mapping.AudioInputTokens != nil { + errors = append(errors, v.validateExtractionIdentifier( + fmt.Sprintf("%s.audioInputTokens", fieldPrefix), mapping.AudioInputTokens)...) + } + + if mapping.AudioOutputTokens != nil { + errors = append(errors, v.validateExtractionIdentifier( + fmt.Sprintf("%s.audioOutputTokens", fieldPrefix), mapping.AudioOutputTokens)...) + } + + if mapping.ServiceTier != nil { + errors = append(errors, v.validateExtractionIdentifier( + fmt.Sprintf("%s.serviceTier", fieldPrefix), mapping.ServiceTier)...) + } + + errors = append(errors, v.validateCacheAccounting( + fmt.Sprintf("%s.cacheAccounting", fieldPrefix), mapping.CacheAccounting)...) + return errors } @@ -269,9 +337,51 @@ func (v *LLMValidator) validateExtractionIdentifier( }) } + if identifier.FallbackIdentifiers != nil { + for i, fallback := range *identifier.FallbackIdentifiers { + if fallback == "" { + errors = append(errors, ValidationError{ + Field: fmt.Sprintf("%s.fallbackIdentifiers[%d]", fieldPrefix, i), + Message: "fallback identifier cannot be empty", + }) + } + } + } + + // A key names a value the provider reports, so an empty key matches nothing. + // An empty target is allowed: it states that the reported value carries no + // rates of its own and should be billed as standard. + if identifier.ValueMap != nil { + for key := range *identifier.ValueMap { + if key == "" { + errors = append(errors, ValidationError{ + Field: fmt.Sprintf("%s.valueMap", fieldPrefix), + Message: "valueMap key cannot be empty", + }) + } + } + } + return errors } +// validateCacheAccounting checks the cache accounting mode. An absent value is +// valid and means inclusive. +func (v *LLMValidator) validateCacheAccounting(fieldPrefix string, value *string) []ValidationError { + if value == nil { + return nil + } + + if *value != "inclusive" && *value != "additive" { + return []ValidationError{{ + Field: fieldPrefix, + Message: "cacheAccounting must be 'inclusive' or 'additive'", + }} + } + + return nil +} + // validateLLMProvider validates an LLM provider configuration func (v *LLMValidator) validateLLMProvider(provider *api.LLMProviderConfiguration) []ValidationError { var errors []ValidationError diff --git a/gateway/gateway-controller/pkg/config/llm_validator_additional_test.go b/gateway/gateway-controller/pkg/config/llm_validator_additional_test.go index bd343404b5..32cf88c28a 100644 --- a/gateway/gateway-controller/pkg/config/llm_validator_additional_test.go +++ b/gateway/gateway-controller/pkg/config/llm_validator_additional_test.go @@ -1,6 +1,7 @@ package config import ( + "fmt" "testing" api "github.com/wso2/api-platform/gateway/gateway-controller/pkg/api/management" @@ -595,3 +596,225 @@ func TestLLMValidator_ValidateTemplateResourceMapping_WithSpaceInResource(t *tes t.Errorf("Expected error for resource with spaces, got: %v", errors) } } + +func TestLLMValidator_ValidateTemplateSpec_NewUsageFields_Valid(t *testing.T) { + v := NewLLMValidator() + accounting := "additive" + spec := &api.LLMProviderTemplateData{ + DisplayName: "anthropic", + CachedTokens: &api.ExtractionIdentifier{ + Location: api.Payload, + Identifier: "$.usage.cache_read_input_tokens", + }, + CacheWriteTokens: &api.ExtractionIdentifier{ + Location: api.Payload, + Identifier: "$.usage.cache_creation.ephemeral_5m_input_tokens", + }, + ServiceTier: &api.ExtractionIdentifier{ + Location: api.Payload, + Identifier: "$.usage.service_tier", + }, + CacheAccounting: &accounting, + } + + errs := v.validateTemplateSpec(spec) + if len(errs) != 0 { + t.Fatalf("expected no validation errors, got %v", errs) + } +} + +func TestLLMValidator_ValidateTemplateSpec_NewUsageFields_MissingIdentifier(t *testing.T) { + v := NewLLMValidator() + spec := &api.LLMProviderTemplateData{ + DisplayName: "openai", + CachedTokens: &api.ExtractionIdentifier{ + Location: api.Payload, + Identifier: "", + }, + } + + errs := v.validateTemplateSpec(spec) + if len(errs) != 1 { + t.Fatalf("expected 1 validation error, got %d: %v", len(errs), errs) + } + if errs[0].Field != "spec.cachedTokens.identifier" { + t.Errorf("expected field spec.cachedTokens.identifier, got %s", errs[0].Field) + } +} + +func TestLLMValidator_ValidateTemplateSpec_CacheAccounting_Invalid(t *testing.T) { + v := NewLLMValidator() + accounting := "sometimes" + spec := &api.LLMProviderTemplateData{ + DisplayName: "openai", + CacheAccounting: &accounting, + } + + errs := v.validateTemplateSpec(spec) + if len(errs) != 1 { + t.Fatalf("expected 1 validation error, got %d: %v", len(errs), errs) + } + if errs[0].Field != "spec.cacheAccounting" { + t.Errorf("expected field spec.cacheAccounting, got %s", errs[0].Field) + } +} + +func TestLLMValidator_ValidateTemplateSpec_CacheAccounting_OmittedIsValid(t *testing.T) { + v := NewLLMValidator() + spec := &api.LLMProviderTemplateData{DisplayName: "openai"} + + errs := v.validateTemplateSpec(spec) + if len(errs) != 0 { + t.Fatalf("expected no validation errors, got %v", errs) + } +} + +func TestLLMValidator_ValidateTemplateResourceMapping_NewUsageFields(t *testing.T) { + v := NewLLMValidator() + accounting := "inclusive" + mapping := &api.LLMProviderTemplateResourceMapping{ + Resource: "/responses", + CachedTokens: &api.ExtractionIdentifier{ + Location: api.Payload, + Identifier: "$.usage.input_tokens_details.cached_tokens", + }, + CacheAccounting: &accounting, + } + + errs := v.validateTemplateResourceMapping("spec.resourceMappings.resources[0]", mapping) + if len(errs) != 0 { + t.Fatalf("expected no validation errors, got %v", errs) + } +} + +// TestLLMValidator_ValidateTemplateResourceMapping_FieldPaths pins the exact +// error field path built for each mapping-level identifier field, so a wrong +// field-name prefix in validateTemplateResourceMapping cannot pass silently. +func TestLLMValidator_ValidateTemplateResourceMapping_FieldPaths(t *testing.T) { + const prefix = "spec.resourceMappings.resources[0]" + + badIdentifier := &api.ExtractionIdentifier{ + Location: api.Payload, + Identifier: "", + } + + tests := []struct { + name string + fieldName string + build func() *api.LLMProviderTemplateResourceMapping + }{ + { + name: "cachedTokens", + fieldName: "cachedTokens", + build: func() *api.LLMProviderTemplateResourceMapping { + return &api.LLMProviderTemplateResourceMapping{Resource: "/responses", CachedTokens: badIdentifier} + }, + }, + { + name: "cacheWriteTokens", + fieldName: "cacheWriteTokens", + build: func() *api.LLMProviderTemplateResourceMapping { + return &api.LLMProviderTemplateResourceMapping{Resource: "/responses", CacheWriteTokens: badIdentifier} + }, + }, + { + name: "cacheWrite1hTokens", + fieldName: "cacheWrite1hTokens", + build: func() *api.LLMProviderTemplateResourceMapping { + return &api.LLMProviderTemplateResourceMapping{Resource: "/responses", CacheWrite1hTokens: badIdentifier} + }, + }, + { + name: "reasoningTokens", + fieldName: "reasoningTokens", + build: func() *api.LLMProviderTemplateResourceMapping { + return &api.LLMProviderTemplateResourceMapping{Resource: "/responses", ReasoningTokens: badIdentifier} + }, + }, + { + name: "audioInputTokens", + fieldName: "audioInputTokens", + build: func() *api.LLMProviderTemplateResourceMapping { + return &api.LLMProviderTemplateResourceMapping{Resource: "/responses", AudioInputTokens: badIdentifier} + }, + }, + { + name: "audioOutputTokens", + fieldName: "audioOutputTokens", + build: func() *api.LLMProviderTemplateResourceMapping { + return &api.LLMProviderTemplateResourceMapping{Resource: "/responses", AudioOutputTokens: badIdentifier} + }, + }, + { + name: "serviceTier", + fieldName: "serviceTier", + build: func() *api.LLMProviderTemplateResourceMapping { + return &api.LLMProviderTemplateResourceMapping{Resource: "/responses", ServiceTier: badIdentifier} + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v := NewLLMValidator() + mapping := tt.build() + + errs := v.validateTemplateResourceMapping(prefix, mapping) + if len(errs) != 1 { + t.Fatalf("expected exactly 1 validation error, got %d: %v", len(errs), errs) + } + + wantField := fmt.Sprintf("%s.%s.identifier", prefix, tt.fieldName) + if errs[0].Field != wantField { + t.Errorf("expected field %q, got %q", wantField, errs[0].Field) + } + }) + } +} + +// TestLLMValidator_ValidateTemplateResourceMapping_CacheAccounting_Invalid pins +// the exact error field path for an invalid mapping-level cacheAccounting value. +func TestLLMValidator_ValidateTemplateResourceMapping_CacheAccounting_Invalid(t *testing.T) { + const prefix = "spec.resourceMappings.resources[0]" + v := NewLLMValidator() + accounting := "sometimes" + mapping := &api.LLMProviderTemplateResourceMapping{ + Resource: "/responses", + CacheAccounting: &accounting, + } + + errs := v.validateTemplateResourceMapping(prefix, mapping) + if len(errs) != 1 { + t.Fatalf("expected exactly 1 validation error, got %d: %v", len(errs), errs) + } + + wantField := prefix + ".cacheAccounting" + if errs[0].Field != wantField { + t.Errorf("expected field %q, got %q", wantField, errs[0].Field) + } +} + +// TestLLMValidator_ValidateExtractionIdentifier_EmptyFallback pins the exact +// error field path for an empty entry inside fallbackIdentifiers. +func TestLLMValidator_ValidateExtractionIdentifier_EmptyFallback(t *testing.T) { + v := NewLLMValidator() + fallbacks := []string{"$.usage.prompt_tokens", ""} + spec := &api.LLMProviderTemplateData{ + DisplayName: "bedrock", + PromptTokens: &api.ExtractionIdentifier{ + Location: api.Payload, + Identifier: "$.usage.inputTokens", + FallbackIdentifiers: &fallbacks, + }, + } + + errs := v.validateTemplateSpec(spec) + if len(errs) != 1 { + t.Fatalf("expected exactly 1 validation error, got %d: %v", len(errs), errs) + } + + wantField := "spec.promptTokens.fallbackIdentifiers[1]" + if errs[0].Field != wantField { + t.Errorf("expected field %q, got %q", wantField, errs[0].Field) + } +} diff --git a/gateway/gateway-controller/pkg/config/llm_validator_test.go b/gateway/gateway-controller/pkg/config/llm_validator_test.go index d0464a52b6..070adc914e 100644 --- a/gateway/gateway-controller/pkg/config/llm_validator_test.go +++ b/gateway/gateway-controller/pkg/config/llm_validator_test.go @@ -2121,3 +2121,68 @@ func TestValidateLLMProvider_UpstreamRef(t *testing.T) { assert.Empty(t, errs) }) } + +// TestValidateLLMProviderTemplate_ValueMap covers the value-map translation +// table. Keys name values the provider reports, so an empty key can never +// match anything; an empty target is meaningful and states that the reported +// value carries no distinct rates. +func TestValidateLLMProviderTemplate_ValueMap(t *testing.T) { + tests := []struct { + name string + valueMap map[string]string + expectError bool + }{ + { + name: "empty key is rejected", + valueMap: map[string]string{"": "priority"}, + expectError: true, + }, + { + name: "empty target is accepted", + valueMap: map[string]string{"scale": ""}, + expectError: false, + }, + { + name: "populated map is accepted", + valueMap: map[string]string{"ON_DEMAND_PRIORITY": "priority", "ON_DEMAND_FLEX": "flex"}, + expectError: false, + }, + } + + validator := NewLLMValidator() + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + valueMap := tt.valueMap + template := api.LLMProviderTemplate{ + ApiVersion: "gateway.api-platform.wso2.com/v1", + Kind: api.LLMProviderTemplateKindLlmProviderTemplate, + Metadata: api.Metadata{Name: "openai"}, + Spec: api.LLMProviderTemplateData{ + DisplayName: "test", + PromptTokens: &api.ExtractionIdentifier{ + Location: api.Payload, + Identifier: "$.usage.prompt_tokens", + ValueMap: &valueMap, + }, + }, + } + + errors := validator.Validate(&template) + + if tt.expectError { + require.NotEmpty(t, errors, "Should have validation errors") + found := false + for _, err := range errors { + if strings.Contains(err.Field, "valueMap") { + found = true + break + } + } + assert.True(t, found, "Should find a valueMap error, got %v", errors) + } else { + assert.Empty(t, errors, "Should not have validation errors") + } + }) + } +} diff --git a/gateway/gateway-runtime/policy-engine/internal/kernel/execution_context.go b/gateway/gateway-runtime/policy-engine/internal/kernel/execution_context.go index fc7e1a18b6..82820b514e 100644 --- a/gateway/gateway-runtime/policy-engine/internal/kernel/execution_context.go +++ b/gateway/gateway-runtime/policy-engine/internal/kernel/execution_context.go @@ -125,6 +125,10 @@ type PolicyExecutionContext struct { isStreamingRequest bool requestStreamAccumulator []byte requestStreamContext *policy.RequestStreamContext + // requestChunkIndex numbers the chunks handed to the policy chain. Policies + // that stitch chunks together use it to tell a new chunk from a redelivery, + // so it must advance on every delivery. + requestChunkIndex uint64 // requestStreamDecomp performs per-chunk decompression for compressed streaming // request bodies. Nil when the request is not Content-Encoded. requestStreamDecomp *streamDecompressor @@ -134,6 +138,9 @@ type PolicyExecutionContext struct { isStreamingResponse bool streamAccumulator []byte responseStreamContext *policy.ResponseStreamContext + // responseChunkIndex numbers the chunks handed to the policy chain. See + // requestChunkIndex. + responseChunkIndex uint64 // responseStreamDecomp performs per-chunk decompression for compressed streaming // response bodies. Nil when the response is not Content-Encoded. responseStreamDecomp *streamDecompressor @@ -712,9 +719,11 @@ func (ec *PolicyExecutionContext) processStreamingRequestBody( ctx context.Context, body *extprocv3.HttpBody, ) (*extprocv3.ProcessingResponse, error) { + ec.requestChunkIndex++ chunk := &policy.StreamBody{ Chunk: body.Body, EndOfStream: body.EndOfStream, + Index: ec.requestChunkIndex, } // Compressed request: decompress this chunk, pass directly to policies, @@ -832,9 +841,11 @@ func (ec *PolicyExecutionContext) processStreamingRequestBody( }, nil } + ec.requestChunkIndex++ flushChunk := &policy.StreamBody{ Chunk: ec.requestStreamAccumulator, EndOfStream: chunk.EndOfStream, + Index: ec.requestChunkIndex, } slog.Debug("[streaming] flushing accumulated request data to policies", "route", ec.routeKey, @@ -1037,9 +1048,11 @@ func (ec *PolicyExecutionContext) processStreamingResponseBody( }, nil } + ec.responseChunkIndex++ chunk := &policy.StreamBody{ Chunk: body.Body, EndOfStream: body.EndOfStream, + Index: ec.responseChunkIndex, } // Compressed response: decompress this chunk, pass directly to policies, @@ -1155,9 +1168,11 @@ func (ec *PolicyExecutionContext) processStreamingResponseBody( }, nil } + ec.responseChunkIndex++ flushChunk := &policy.StreamBody{ Chunk: ec.streamAccumulator, EndOfStream: chunk.EndOfStream, + Index: ec.responseChunkIndex, } slog.Debug("[streaming] flushing accumulated response data to policies", "route", ec.routeKey, diff --git a/gateway/gateway-runtime/policy-engine/internal/kernel/execution_context_test.go b/gateway/gateway-runtime/policy-engine/internal/kernel/execution_context_test.go index e4241c3f22..8d9f8add19 100644 --- a/gateway/gateway-runtime/policy-engine/internal/kernel/execution_context_test.go +++ b/gateway/gateway-runtime/policy-engine/internal/kernel/execution_context_test.go @@ -1176,3 +1176,74 @@ func TestProcessStreamingRequestBody_MalformedEncodingFailsClosed(t *testing.T) assert.NotErrorIs(t, err, ErrDecompressedTooLarge) assert.Nil(t, execCtx.requestStreamDecomp) } + +// indexRecordingPolicy records the Index of every chunk the kernel delivers. +type indexRecordingPolicy struct { + seen []uint64 +} + +func (p *indexRecordingPolicy) Mode() policy.ProcessingMode { + return policy.ProcessingMode{ResponseBodyMode: policy.BodyModeStream} +} + +func (p *indexRecordingPolicy) OnResponseBody(_ context.Context, _ *policy.ResponseContext, _ map[string]interface{}) policy.ResponseAction { + return policy.DownstreamResponseModifications{} +} + +// NeedsMoreResponseData returns false so the kernel flushes every chunk, which +// is what a policy doing its own accumulation asks for. +func (p *indexRecordingPolicy) NeedsMoreResponseData(_ []byte) bool { return false } + +func (p *indexRecordingPolicy) OnResponseBodyChunk(_ context.Context, _ *policy.ResponseStreamContext, chunk *policy.StreamBody, _ map[string]interface{}) policy.StreamingResponseAction { + p.seen = append(p.seen, chunk.Index) + return policy.ForwardResponseChunk{} +} + +// A policy that stitches chunks together tells a new chunk from a redelivery by +// its Index, so the kernel must advance it on every delivery. When it does not, +// such a policy keeps only the first chunk and silently works off a partial +// body — which is how streaming LLM responses ended up priced at zero. +func TestStreamingResponse_ChunkIndexAdvancesPerDelivery(t *testing.T) { + rec := &indexRecordingPolicy{} + + kernel := NewKernel() + server := NewExternalProcessorServer(kernel, newTestExecutor(), config.TracingConfig{}, "", testMaxDecompressedBytes, testMaxDecompressedBytes) + chain := ®istry.PolicyChain{ + RequiresResponseBody: true, + SupportsResponseStreaming: true, + Policies: []policy.Policy{rec}, + PolicySpecs: []policy.PolicySpec{{Enabled: true}}, + } + execCtx := newPolicyExecutionContext(server, "test-route", chain) + + execCtx.buildRequestContexts(&extprocv3.HttpHeaders{Headers: &corev3.HeaderMap{}}, RouteMetadata{}) + execCtx.buildResponseContexts(&extprocv3.HttpHeaders{ + Headers: &corev3.HeaderMap{ + Headers: []*corev3.HeaderValue{ + {Key: ":status", RawValue: []byte("200")}, + {Key: "content-type", RawValue: []byte("text/event-stream")}, + }, + }, + }) + + chunks := []string{ + "data: {\"model\":\"m\"}\n\n", + "data: {\"choices\":[]}\n\n", + "data: {\"usage\":{\"prompt_tokens\":6}}\n\n", + "data: [DONE]\n\n", + } + for i, c := range chunks { + _, err := execCtx.processStreamingResponseBody(context.Background(), &extprocv3.HttpBody{ + Body: []byte(c), + EndOfStream: i == len(chunks)-1, + }) + require.NoError(t, err) + } + + require.Len(t, rec.seen, len(chunks), "every chunk should reach the policy") + for i := 1; i < len(rec.seen); i++ { + assert.Greater(t, rec.seen[i], rec.seen[i-1], + "chunk %d index %d must exceed chunk %d index %d; equal indices make accumulating policies discard the chunk", + i, rec.seen[i], i-1, rec.seen[i-1]) + } +} diff --git a/kubernetes/gateway-operator/api/v1/llmprovidertemplate_types.go b/kubernetes/gateway-operator/api/v1/llmprovidertemplate_types.go index e819233ce0..dc1201e48d 100644 --- a/kubernetes/gateway-operator/api/v1/llmprovidertemplate_types.go +++ b/kubernetes/gateway-operator/api/v1/llmprovidertemplate_types.go @@ -31,6 +31,16 @@ type ExtractionIdentifier struct { // +kubebuilder:validation:Required // +kubebuilder:validation:Enum=header;pathParam;payload;queryParam Location string `json:"location"` + + // FallbackIdentifiers are additional locations to try, in order, when the + // primary identifier yields no value. + // +optional + FallbackIdentifiers []string `json:"fallbackIdentifiers,omitempty"` + + // ValueMap translates values the provider reports into the vocabulary the + // gateway works in. A reported value that is not a key is used unchanged. + // +optional + ValueMap map[string]string `json:"valueMap,omitempty"` } // LLMProviderTemplateResourceMapping maps a resource path pattern to per-field @@ -52,6 +62,27 @@ type LLMProviderTemplateResourceMapping struct { ResponseModel *ExtractionIdentifier `json:"responseModel,omitempty"` // +optional TotalTokens *ExtractionIdentifier `json:"totalTokens,omitempty"` + + // +optional + AudioInputTokens *ExtractionIdentifier `json:"audioInputTokens,omitempty"` + // +optional + AudioOutputTokens *ExtractionIdentifier `json:"audioOutputTokens,omitempty"` + // +optional + CachedTokens *ExtractionIdentifier `json:"cachedTokens,omitempty"` + // +optional + CacheWrite1hTokens *ExtractionIdentifier `json:"cacheWrite1hTokens,omitempty"` + // +optional + CacheWriteTokens *ExtractionIdentifier `json:"cacheWriteTokens,omitempty"` + // +optional + ReasoningTokens *ExtractionIdentifier `json:"reasoningTokens,omitempty"` + // +optional + ServiceTier *ExtractionIdentifier `json:"serviceTier,omitempty"` + + // CacheAccounting overrides the template-level cache accounting mode for + // this resource. When omitted, the template-level value is inherited. + // +optional + // +kubebuilder:validation:Enum=inclusive;additive + CacheAccounting string `json:"cacheAccounting,omitempty"` } // LLMProviderTemplateResourceMappings lists per-resource extraction mappings. @@ -82,6 +113,27 @@ type LLMProviderTemplateData struct { ResponseModel *ExtractionIdentifier `json:"responseModel,omitempty"` // +optional TotalTokens *ExtractionIdentifier `json:"totalTokens,omitempty"` + + // +optional + AudioInputTokens *ExtractionIdentifier `json:"audioInputTokens,omitempty"` + // +optional + AudioOutputTokens *ExtractionIdentifier `json:"audioOutputTokens,omitempty"` + // +optional + CachedTokens *ExtractionIdentifier `json:"cachedTokens,omitempty"` + // +optional + CacheWrite1hTokens *ExtractionIdentifier `json:"cacheWrite1hTokens,omitempty"` + // +optional + CacheWriteTokens *ExtractionIdentifier `json:"cacheWriteTokens,omitempty"` + // +optional + ReasoningTokens *ExtractionIdentifier `json:"reasoningTokens,omitempty"` + // +optional + ServiceTier *ExtractionIdentifier `json:"serviceTier,omitempty"` + + // CacheAccounting states whether the provider's cached token count is part + // of the input total or additional to it. Defaults to inclusive. + // +optional + // +kubebuilder:validation:Enum=inclusive;additive + CacheAccounting string `json:"cacheAccounting,omitempty"` } //+kubebuilder:object:root=true diff --git a/kubernetes/gateway-operator/api/v1/zz_generated.deepcopy.go b/kubernetes/gateway-operator/api/v1/zz_generated.deepcopy.go index f4ca5f8f1a..b645c87e70 100644 --- a/kubernetes/gateway-operator/api/v1/zz_generated.deepcopy.go +++ b/kubernetes/gateway-operator/api/v1/zz_generated.deepcopy.go @@ -511,6 +511,18 @@ func (in *CertificateSpec) DeepCopy() *CertificateSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ExtractionIdentifier) DeepCopyInto(out *ExtractionIdentifier) { *out = *in + if in.FallbackIdentifiers != nil { + in, out := &in.FallbackIdentifiers, &out.FallbackIdentifiers + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ValueMap != nil { + in, out := &in.ValueMap, &out.ValueMap + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtractionIdentifier. @@ -839,22 +851,22 @@ func (in *LLMProviderTemplateData) DeepCopyInto(out *LLMProviderTemplateData) { if in.CompletionTokens != nil { in, out := &in.CompletionTokens, &out.CompletionTokens *out = new(ExtractionIdentifier) - **out = **in + (*in).DeepCopyInto(*out) } if in.PromptTokens != nil { in, out := &in.PromptTokens, &out.PromptTokens *out = new(ExtractionIdentifier) - **out = **in + (*in).DeepCopyInto(*out) } if in.RemainingTokens != nil { in, out := &in.RemainingTokens, &out.RemainingTokens *out = new(ExtractionIdentifier) - **out = **in + (*in).DeepCopyInto(*out) } if in.RequestModel != nil { in, out := &in.RequestModel, &out.RequestModel *out = new(ExtractionIdentifier) - **out = **in + (*in).DeepCopyInto(*out) } if in.ResourceMappings != nil { in, out := &in.ResourceMappings, &out.ResourceMappings @@ -864,12 +876,47 @@ func (in *LLMProviderTemplateData) DeepCopyInto(out *LLMProviderTemplateData) { if in.ResponseModel != nil { in, out := &in.ResponseModel, &out.ResponseModel *out = new(ExtractionIdentifier) - **out = **in + (*in).DeepCopyInto(*out) } if in.TotalTokens != nil { in, out := &in.TotalTokens, &out.TotalTokens *out = new(ExtractionIdentifier) - **out = **in + (*in).DeepCopyInto(*out) + } + if in.AudioInputTokens != nil { + in, out := &in.AudioInputTokens, &out.AudioInputTokens + *out = new(ExtractionIdentifier) + (*in).DeepCopyInto(*out) + } + if in.AudioOutputTokens != nil { + in, out := &in.AudioOutputTokens, &out.AudioOutputTokens + *out = new(ExtractionIdentifier) + (*in).DeepCopyInto(*out) + } + if in.CachedTokens != nil { + in, out := &in.CachedTokens, &out.CachedTokens + *out = new(ExtractionIdentifier) + (*in).DeepCopyInto(*out) + } + if in.CacheWrite1hTokens != nil { + in, out := &in.CacheWrite1hTokens, &out.CacheWrite1hTokens + *out = new(ExtractionIdentifier) + (*in).DeepCopyInto(*out) + } + if in.CacheWriteTokens != nil { + in, out := &in.CacheWriteTokens, &out.CacheWriteTokens + *out = new(ExtractionIdentifier) + (*in).DeepCopyInto(*out) + } + if in.ReasoningTokens != nil { + in, out := &in.ReasoningTokens, &out.ReasoningTokens + *out = new(ExtractionIdentifier) + (*in).DeepCopyInto(*out) + } + if in.ServiceTier != nil { + in, out := &in.ServiceTier, &out.ServiceTier + *out = new(ExtractionIdentifier) + (*in).DeepCopyInto(*out) } } @@ -889,32 +936,67 @@ func (in *LLMProviderTemplateResourceMapping) DeepCopyInto(out *LLMProviderTempl if in.CompletionTokens != nil { in, out := &in.CompletionTokens, &out.CompletionTokens *out = new(ExtractionIdentifier) - **out = **in + (*in).DeepCopyInto(*out) } if in.PromptTokens != nil { in, out := &in.PromptTokens, &out.PromptTokens *out = new(ExtractionIdentifier) - **out = **in + (*in).DeepCopyInto(*out) } if in.RemainingTokens != nil { in, out := &in.RemainingTokens, &out.RemainingTokens *out = new(ExtractionIdentifier) - **out = **in + (*in).DeepCopyInto(*out) } if in.RequestModel != nil { in, out := &in.RequestModel, &out.RequestModel *out = new(ExtractionIdentifier) - **out = **in + (*in).DeepCopyInto(*out) } if in.ResponseModel != nil { in, out := &in.ResponseModel, &out.ResponseModel *out = new(ExtractionIdentifier) - **out = **in + (*in).DeepCopyInto(*out) } if in.TotalTokens != nil { in, out := &in.TotalTokens, &out.TotalTokens *out = new(ExtractionIdentifier) - **out = **in + (*in).DeepCopyInto(*out) + } + if in.AudioInputTokens != nil { + in, out := &in.AudioInputTokens, &out.AudioInputTokens + *out = new(ExtractionIdentifier) + (*in).DeepCopyInto(*out) + } + if in.AudioOutputTokens != nil { + in, out := &in.AudioOutputTokens, &out.AudioOutputTokens + *out = new(ExtractionIdentifier) + (*in).DeepCopyInto(*out) + } + if in.CachedTokens != nil { + in, out := &in.CachedTokens, &out.CachedTokens + *out = new(ExtractionIdentifier) + (*in).DeepCopyInto(*out) + } + if in.CacheWrite1hTokens != nil { + in, out := &in.CacheWrite1hTokens, &out.CacheWrite1hTokens + *out = new(ExtractionIdentifier) + (*in).DeepCopyInto(*out) + } + if in.CacheWriteTokens != nil { + in, out := &in.CacheWriteTokens, &out.CacheWriteTokens + *out = new(ExtractionIdentifier) + (*in).DeepCopyInto(*out) + } + if in.ReasoningTokens != nil { + in, out := &in.ReasoningTokens, &out.ReasoningTokens + *out = new(ExtractionIdentifier) + (*in).DeepCopyInto(*out) + } + if in.ServiceTier != nil { + in, out := &in.ServiceTier, &out.ServiceTier + *out = new(ExtractionIdentifier) + (*in).DeepCopyInto(*out) } } diff --git a/kubernetes/gateway-operator/api/v1alpha1/llmprovidertemplate_types.go b/kubernetes/gateway-operator/api/v1alpha1/llmprovidertemplate_types.go index b1ad1ee9bf..ec738b6795 100644 --- a/kubernetes/gateway-operator/api/v1alpha1/llmprovidertemplate_types.go +++ b/kubernetes/gateway-operator/api/v1alpha1/llmprovidertemplate_types.go @@ -31,6 +31,16 @@ type ExtractionIdentifier struct { // +kubebuilder:validation:Required // +kubebuilder:validation:Enum=header;pathParam;payload;queryParam Location string `json:"location"` + + // FallbackIdentifiers are additional locations to try, in order, when the + // primary identifier yields no value. + // +optional + FallbackIdentifiers []string `json:"fallbackIdentifiers,omitempty"` + + // ValueMap translates values the provider reports into the vocabulary the + // gateway works in. A reported value that is not a key is used unchanged. + // +optional + ValueMap map[string]string `json:"valueMap,omitempty"` } // LLMProviderTemplateResourceMapping maps a resource path pattern to per-field @@ -52,6 +62,27 @@ type LLMProviderTemplateResourceMapping struct { ResponseModel *ExtractionIdentifier `json:"responseModel,omitempty"` // +optional TotalTokens *ExtractionIdentifier `json:"totalTokens,omitempty"` + + // +optional + AudioInputTokens *ExtractionIdentifier `json:"audioInputTokens,omitempty"` + // +optional + AudioOutputTokens *ExtractionIdentifier `json:"audioOutputTokens,omitempty"` + // +optional + CachedTokens *ExtractionIdentifier `json:"cachedTokens,omitempty"` + // +optional + CacheWrite1hTokens *ExtractionIdentifier `json:"cacheWrite1hTokens,omitempty"` + // +optional + CacheWriteTokens *ExtractionIdentifier `json:"cacheWriteTokens,omitempty"` + // +optional + ReasoningTokens *ExtractionIdentifier `json:"reasoningTokens,omitempty"` + // +optional + ServiceTier *ExtractionIdentifier `json:"serviceTier,omitempty"` + + // CacheAccounting overrides the template-level cache accounting mode for + // this resource. When omitted, the template-level value is inherited. + // +optional + // +kubebuilder:validation:Enum=inclusive;additive + CacheAccounting string `json:"cacheAccounting,omitempty"` } // LLMProviderTemplateResourceMappings lists per-resource extraction mappings. @@ -82,6 +113,27 @@ type LLMProviderTemplateData struct { ResponseModel *ExtractionIdentifier `json:"responseModel,omitempty"` // +optional TotalTokens *ExtractionIdentifier `json:"totalTokens,omitempty"` + + // +optional + AudioInputTokens *ExtractionIdentifier `json:"audioInputTokens,omitempty"` + // +optional + AudioOutputTokens *ExtractionIdentifier `json:"audioOutputTokens,omitempty"` + // +optional + CachedTokens *ExtractionIdentifier `json:"cachedTokens,omitempty"` + // +optional + CacheWrite1hTokens *ExtractionIdentifier `json:"cacheWrite1hTokens,omitempty"` + // +optional + CacheWriteTokens *ExtractionIdentifier `json:"cacheWriteTokens,omitempty"` + // +optional + ReasoningTokens *ExtractionIdentifier `json:"reasoningTokens,omitempty"` + // +optional + ServiceTier *ExtractionIdentifier `json:"serviceTier,omitempty"` + + // CacheAccounting states whether the provider's cached token count is part + // of the input total or additional to it. Defaults to inclusive. + // +optional + // +kubebuilder:validation:Enum=inclusive;additive + CacheAccounting string `json:"cacheAccounting,omitempty"` } //+kubebuilder:object:root=true diff --git a/kubernetes/gateway-operator/api/v1alpha1/zz_generated.deepcopy.go b/kubernetes/gateway-operator/api/v1alpha1/zz_generated.deepcopy.go index 41b7e7c266..36c97398d4 100644 --- a/kubernetes/gateway-operator/api/v1alpha1/zz_generated.deepcopy.go +++ b/kubernetes/gateway-operator/api/v1alpha1/zz_generated.deepcopy.go @@ -511,6 +511,18 @@ func (in *CertificateSpec) DeepCopy() *CertificateSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ExtractionIdentifier) DeepCopyInto(out *ExtractionIdentifier) { *out = *in + if in.FallbackIdentifiers != nil { + in, out := &in.FallbackIdentifiers, &out.FallbackIdentifiers + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ValueMap != nil { + in, out := &in.ValueMap, &out.ValueMap + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtractionIdentifier. @@ -839,22 +851,22 @@ func (in *LLMProviderTemplateData) DeepCopyInto(out *LLMProviderTemplateData) { if in.CompletionTokens != nil { in, out := &in.CompletionTokens, &out.CompletionTokens *out = new(ExtractionIdentifier) - **out = **in + (*in).DeepCopyInto(*out) } if in.PromptTokens != nil { in, out := &in.PromptTokens, &out.PromptTokens *out = new(ExtractionIdentifier) - **out = **in + (*in).DeepCopyInto(*out) } if in.RemainingTokens != nil { in, out := &in.RemainingTokens, &out.RemainingTokens *out = new(ExtractionIdentifier) - **out = **in + (*in).DeepCopyInto(*out) } if in.RequestModel != nil { in, out := &in.RequestModel, &out.RequestModel *out = new(ExtractionIdentifier) - **out = **in + (*in).DeepCopyInto(*out) } if in.ResourceMappings != nil { in, out := &in.ResourceMappings, &out.ResourceMappings @@ -864,12 +876,47 @@ func (in *LLMProviderTemplateData) DeepCopyInto(out *LLMProviderTemplateData) { if in.ResponseModel != nil { in, out := &in.ResponseModel, &out.ResponseModel *out = new(ExtractionIdentifier) - **out = **in + (*in).DeepCopyInto(*out) } if in.TotalTokens != nil { in, out := &in.TotalTokens, &out.TotalTokens *out = new(ExtractionIdentifier) - **out = **in + (*in).DeepCopyInto(*out) + } + if in.AudioInputTokens != nil { + in, out := &in.AudioInputTokens, &out.AudioInputTokens + *out = new(ExtractionIdentifier) + (*in).DeepCopyInto(*out) + } + if in.AudioOutputTokens != nil { + in, out := &in.AudioOutputTokens, &out.AudioOutputTokens + *out = new(ExtractionIdentifier) + (*in).DeepCopyInto(*out) + } + if in.CachedTokens != nil { + in, out := &in.CachedTokens, &out.CachedTokens + *out = new(ExtractionIdentifier) + (*in).DeepCopyInto(*out) + } + if in.CacheWrite1hTokens != nil { + in, out := &in.CacheWrite1hTokens, &out.CacheWrite1hTokens + *out = new(ExtractionIdentifier) + (*in).DeepCopyInto(*out) + } + if in.CacheWriteTokens != nil { + in, out := &in.CacheWriteTokens, &out.CacheWriteTokens + *out = new(ExtractionIdentifier) + (*in).DeepCopyInto(*out) + } + if in.ReasoningTokens != nil { + in, out := &in.ReasoningTokens, &out.ReasoningTokens + *out = new(ExtractionIdentifier) + (*in).DeepCopyInto(*out) + } + if in.ServiceTier != nil { + in, out := &in.ServiceTier, &out.ServiceTier + *out = new(ExtractionIdentifier) + (*in).DeepCopyInto(*out) } } @@ -889,32 +936,67 @@ func (in *LLMProviderTemplateResourceMapping) DeepCopyInto(out *LLMProviderTempl if in.CompletionTokens != nil { in, out := &in.CompletionTokens, &out.CompletionTokens *out = new(ExtractionIdentifier) - **out = **in + (*in).DeepCopyInto(*out) } if in.PromptTokens != nil { in, out := &in.PromptTokens, &out.PromptTokens *out = new(ExtractionIdentifier) - **out = **in + (*in).DeepCopyInto(*out) } if in.RemainingTokens != nil { in, out := &in.RemainingTokens, &out.RemainingTokens *out = new(ExtractionIdentifier) - **out = **in + (*in).DeepCopyInto(*out) } if in.RequestModel != nil { in, out := &in.RequestModel, &out.RequestModel *out = new(ExtractionIdentifier) - **out = **in + (*in).DeepCopyInto(*out) } if in.ResponseModel != nil { in, out := &in.ResponseModel, &out.ResponseModel *out = new(ExtractionIdentifier) - **out = **in + (*in).DeepCopyInto(*out) } if in.TotalTokens != nil { in, out := &in.TotalTokens, &out.TotalTokens *out = new(ExtractionIdentifier) - **out = **in + (*in).DeepCopyInto(*out) + } + if in.AudioInputTokens != nil { + in, out := &in.AudioInputTokens, &out.AudioInputTokens + *out = new(ExtractionIdentifier) + (*in).DeepCopyInto(*out) + } + if in.AudioOutputTokens != nil { + in, out := &in.AudioOutputTokens, &out.AudioOutputTokens + *out = new(ExtractionIdentifier) + (*in).DeepCopyInto(*out) + } + if in.CachedTokens != nil { + in, out := &in.CachedTokens, &out.CachedTokens + *out = new(ExtractionIdentifier) + (*in).DeepCopyInto(*out) + } + if in.CacheWrite1hTokens != nil { + in, out := &in.CacheWrite1hTokens, &out.CacheWrite1hTokens + *out = new(ExtractionIdentifier) + (*in).DeepCopyInto(*out) + } + if in.CacheWriteTokens != nil { + in, out := &in.CacheWriteTokens, &out.CacheWriteTokens + *out = new(ExtractionIdentifier) + (*in).DeepCopyInto(*out) + } + if in.ReasoningTokens != nil { + in, out := &in.ReasoningTokens, &out.ReasoningTokens + *out = new(ExtractionIdentifier) + (*in).DeepCopyInto(*out) + } + if in.ServiceTier != nil { + in, out := &in.ServiceTier, &out.ServiceTier + *out = new(ExtractionIdentifier) + (*in).DeepCopyInto(*out) } } diff --git a/kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmprovidertemplates.yaml b/kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmprovidertemplates.yaml index 299fa01e0c..e9483379ae 100644 --- a/kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmprovidertemplates.yaml +++ b/kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmprovidertemplates.yaml @@ -45,11 +45,196 @@ spec: payload. The non-resource extractor fields apply when no resourceMappings entry matches the request path. properties: + audioInputTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + audioOutputTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + cacheAccounting: + description: |- + CacheAccounting states whether the provider's cached token count is part + of the input total or additional to it. Defaults to inclusive. + enum: + - inclusive + - additive + type: string + cacheWrite1hTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + cacheWriteTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + cachedTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object completionTokens: description: |- ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. type: string @@ -61,6 +246,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -73,6 +265,47 @@ spec: ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + reasoningTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. type: string @@ -84,6 +317,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -93,6 +333,13 @@ spec: ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. type: string @@ -104,6 +351,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -113,6 +367,13 @@ spec: ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. type: string @@ -124,6 +385,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -138,11 +406,201 @@ spec: LLMProviderTemplateResourceMapping maps a resource path pattern to per-field extraction identifiers for a given LLM API resource. properties: + audioInputTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or + header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + audioOutputTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or + header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + cacheAccounting: + description: |- + CacheAccounting overrides the template-level cache accounting mode for + this resource. When omitted, the template-level value is inherited. + enum: + - inclusive + - additive + type: string + cacheWrite1hTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or + header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + cacheWriteTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or + header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + cachedTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or + header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object completionTokens: description: |- ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. @@ -155,6 +613,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -164,6 +629,48 @@ spec: ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or + header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + reasoningTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. @@ -176,6 +683,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -185,6 +699,13 @@ spec: ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. @@ -197,6 +718,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -206,6 +734,13 @@ spec: ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. @@ -218,6 +753,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -231,6 +773,13 @@ spec: ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. @@ -243,6 +792,48 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + serviceTier: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or + header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -252,6 +843,13 @@ spec: ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. @@ -264,6 +862,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -278,6 +883,13 @@ spec: ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. type: string @@ -289,6 +901,47 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + serviceTier: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -298,6 +951,13 @@ spec: ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. type: string @@ -309,6 +969,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -431,11 +1098,196 @@ spec: payload. The non-resource extractor fields apply when no resourceMappings entry matches the request path. properties: + audioInputTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + audioOutputTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + cacheAccounting: + description: |- + CacheAccounting states whether the provider's cached token count is part + of the input total or additional to it. Defaults to inclusive. + enum: + - inclusive + - additive + type: string + cacheWrite1hTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + cacheWriteTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + cachedTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object completionTokens: description: |- ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. type: string @@ -447,6 +1299,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -459,6 +1318,47 @@ spec: ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + reasoningTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. type: string @@ -470,6 +1370,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -479,6 +1386,13 @@ spec: ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. type: string @@ -490,6 +1404,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -499,6 +1420,13 @@ spec: ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. type: string @@ -510,6 +1438,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -524,11 +1459,201 @@ spec: LLMProviderTemplateResourceMapping maps a resource path pattern to per-field extraction identifiers for a given LLM API resource. properties: + audioInputTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or + header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + audioOutputTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or + header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + cacheAccounting: + description: |- + CacheAccounting overrides the template-level cache accounting mode for + this resource. When omitted, the template-level value is inherited. + enum: + - inclusive + - additive + type: string + cacheWrite1hTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or + header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + cacheWriteTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or + header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + cachedTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or + header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object completionTokens: description: |- ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. @@ -541,6 +1666,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -550,6 +1682,48 @@ spec: ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or + header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + reasoningTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. @@ -562,6 +1736,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -571,6 +1752,13 @@ spec: ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. @@ -583,6 +1771,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -592,6 +1787,13 @@ spec: ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. @@ -604,6 +1806,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -617,6 +1826,48 @@ spec: ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or + header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + serviceTier: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. @@ -629,6 +1880,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -638,6 +1896,13 @@ spec: ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. @@ -650,6 +1915,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -664,6 +1936,47 @@ spec: ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + serviceTier: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. type: string @@ -675,6 +1988,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -684,6 +2004,13 @@ spec: ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. type: string @@ -695,6 +2022,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location diff --git a/kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmprovidertemplates.yaml b/kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmprovidertemplates.yaml index 299fa01e0c..e9483379ae 100644 --- a/kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmprovidertemplates.yaml +++ b/kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmprovidertemplates.yaml @@ -45,11 +45,196 @@ spec: payload. The non-resource extractor fields apply when no resourceMappings entry matches the request path. properties: + audioInputTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + audioOutputTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + cacheAccounting: + description: |- + CacheAccounting states whether the provider's cached token count is part + of the input total or additional to it. Defaults to inclusive. + enum: + - inclusive + - additive + type: string + cacheWrite1hTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + cacheWriteTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + cachedTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object completionTokens: description: |- ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. type: string @@ -61,6 +246,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -73,6 +265,47 @@ spec: ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + reasoningTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. type: string @@ -84,6 +317,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -93,6 +333,13 @@ spec: ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. type: string @@ -104,6 +351,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -113,6 +367,13 @@ spec: ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. type: string @@ -124,6 +385,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -138,11 +406,201 @@ spec: LLMProviderTemplateResourceMapping maps a resource path pattern to per-field extraction identifiers for a given LLM API resource. properties: + audioInputTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or + header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + audioOutputTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or + header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + cacheAccounting: + description: |- + CacheAccounting overrides the template-level cache accounting mode for + this resource. When omitted, the template-level value is inherited. + enum: + - inclusive + - additive + type: string + cacheWrite1hTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or + header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + cacheWriteTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or + header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + cachedTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or + header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object completionTokens: description: |- ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. @@ -155,6 +613,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -164,6 +629,48 @@ spec: ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or + header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + reasoningTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. @@ -176,6 +683,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -185,6 +699,13 @@ spec: ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. @@ -197,6 +718,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -206,6 +734,13 @@ spec: ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. @@ -218,6 +753,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -231,6 +773,13 @@ spec: ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. @@ -243,6 +792,48 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + serviceTier: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or + header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -252,6 +843,13 @@ spec: ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. @@ -264,6 +862,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -278,6 +883,13 @@ spec: ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. type: string @@ -289,6 +901,47 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + serviceTier: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -298,6 +951,13 @@ spec: ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. type: string @@ -309,6 +969,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -431,11 +1098,196 @@ spec: payload. The non-resource extractor fields apply when no resourceMappings entry matches the request path. properties: + audioInputTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + audioOutputTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + cacheAccounting: + description: |- + CacheAccounting states whether the provider's cached token count is part + of the input total or additional to it. Defaults to inclusive. + enum: + - inclusive + - additive + type: string + cacheWrite1hTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + cacheWriteTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + cachedTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object completionTokens: description: |- ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. type: string @@ -447,6 +1299,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -459,6 +1318,47 @@ spec: ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + reasoningTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. type: string @@ -470,6 +1370,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -479,6 +1386,13 @@ spec: ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. type: string @@ -490,6 +1404,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -499,6 +1420,13 @@ spec: ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. type: string @@ -510,6 +1438,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -524,11 +1459,201 @@ spec: LLMProviderTemplateResourceMapping maps a resource path pattern to per-field extraction identifiers for a given LLM API resource. properties: + audioInputTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or + header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + audioOutputTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or + header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + cacheAccounting: + description: |- + CacheAccounting overrides the template-level cache accounting mode for + this resource. When omitted, the template-level value is inherited. + enum: + - inclusive + - additive + type: string + cacheWrite1hTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or + header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + cacheWriteTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or + header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + cachedTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or + header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object completionTokens: description: |- ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. @@ -541,6 +1666,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -550,6 +1682,48 @@ spec: ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or + header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + reasoningTokens: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. @@ -562,6 +1736,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -571,6 +1752,13 @@ spec: ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. @@ -583,6 +1771,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -592,6 +1787,13 @@ spec: ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. @@ -604,6 +1806,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -617,6 +1826,48 @@ spec: ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or + header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + serviceTier: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. @@ -629,6 +1880,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -638,6 +1896,13 @@ spec: ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. @@ -650,6 +1915,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -664,6 +1936,47 @@ spec: ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + serviceTier: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. type: string @@ -675,6 +1988,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location @@ -684,6 +2004,13 @@ spec: ExtractionIdentifier locates a value within an HTTP request or response. Mirrors the gateway-controller management API ExtractionIdentifier model. properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array identifier: description: Identifier is a JSONPath expression or header name. type: string @@ -695,6 +2022,13 @@ spec: - payload - queryParam type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object required: - identifier - location From 58915c13ae87ad5231fb52b7199d72ab8505513e Mon Sep 17 00:00:00 2001 From: Irash Perera Date: Thu, 20 Aug 2026 11:41:50 +0530 Subject: [PATCH 2/7] Preserve the chunk index when a policy rewrites the body --- .../policy-engine/internal/executor/chain.go | 5 +- .../internal/executor/chain_test.go | 74 +++++++++++++++++++ 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/gateway/gateway-runtime/policy-engine/internal/executor/chain.go b/gateway/gateway-runtime/policy-engine/internal/executor/chain.go index 2380a295c1..da79d599e9 100644 --- a/gateway/gateway-runtime/policy-engine/internal/executor/chain.go +++ b/gateway/gateway-runtime/policy-engine/internal/executor/chain.go @@ -796,6 +796,7 @@ func (c *ChainExecutor) ExecuteStreamingRequestPolicies( currentChunk = &policy.StreamBody{ Chunk: fwd.Body, EndOfStream: currentChunk.EndOfStream, + Index: currentChunk.Index, } } @@ -939,11 +940,11 @@ func (c *ChainExecutor) ExecuteStreamingResponsePolicies( switch a := action.(type) { case policy.ForwardResponseChunk: if a.Body != nil { - currentChunk = &policy.StreamBody{Chunk: a.Body, EndOfStream: currentChunk.EndOfStream} + currentChunk = &policy.StreamBody{Chunk: a.Body, EndOfStream: currentChunk.EndOfStream, Index: currentChunk.Index} } case policy.TerminateResponseChunk: if a.Body != nil { - currentChunk = &policy.StreamBody{Chunk: a.Body, EndOfStream: true} + currentChunk = &policy.StreamBody{Chunk: a.Body, EndOfStream: true, Index: currentChunk.Index} } } diff --git a/gateway/gateway-runtime/policy-engine/internal/executor/chain_test.go b/gateway/gateway-runtime/policy-engine/internal/executor/chain_test.go index a6408a78c9..bbf15ecdd2 100644 --- a/gateway/gateway-runtime/policy-engine/internal/executor/chain_test.go +++ b/gateway/gateway-runtime/policy-engine/internal/executor/chain_test.go @@ -960,3 +960,77 @@ func TestExecuteStreamingResponsePolicies_ModeFirstGating(t *testing.T) { }) } } + +type transformingResponsePolicy struct{} + +func (p *transformingResponsePolicy) Mode() policy.ProcessingMode { + return policy.ProcessingMode{ResponseBodyMode: policy.BodyModeStream} +} + +func (p *transformingResponsePolicy) OnResponseBody(_ context.Context, _ *policy.ResponseContext, _ map[string]interface{}) policy.ResponseAction { + return policy.DownstreamResponseModifications{} +} + +func (p *transformingResponsePolicy) NeedsMoreResponseData(_ []byte) bool { return false } + +func (p *transformingResponsePolicy) OnResponseBodyChunk(_ context.Context, _ *policy.ResponseStreamContext, chunk *policy.StreamBody, _ map[string]interface{}) policy.StreamingResponseAction { + return policy.ForwardResponseChunk{Body: []byte("rewritten")} +} + + +type indexRecordingResponsePolicy struct { + seen []uint64 +} + +func (p *indexRecordingResponsePolicy) Mode() policy.ProcessingMode { + return policy.ProcessingMode{ResponseBodyMode: policy.BodyModeStream} +} + +func (p *indexRecordingResponsePolicy) OnResponseBody(_ context.Context, _ *policy.ResponseContext, _ map[string]interface{}) policy.ResponseAction { + return policy.DownstreamResponseModifications{} +} + +func (p *indexRecordingResponsePolicy) NeedsMoreResponseData(_ []byte) bool { return false } + +func (p *indexRecordingResponsePolicy) OnResponseBodyChunk(_ context.Context, _ *policy.ResponseStreamContext, chunk *policy.StreamBody, _ map[string]interface{}) policy.StreamingResponseAction { + p.seen = append(p.seen, chunk.Index) + return policy.ForwardResponseChunk{} +} + +func TestExecuteStreamingResponsePolicies_IndexSurvivesBodyTransformation(t *testing.T) { + tracer := noop.NewTracerProvider().Tracer("test") + executor := NewChainExecutor(nil, nil, tracer) + + recorder := &indexRecordingResponsePolicy{} + respCtx := &policy.ResponseStreamContext{ + SharedContext: testutils.NewTestSharedContext(), + RequestHeaders: policy.NewHeaders(map[string][]string{"content-type": {"application/json"}}), + RequestPath: "/test", + RequestMethod: "POST", + ResponseHeaders: policy.NewHeaders(map[string][]string{"content-type": {"application/json"}}), + ResponseStatus: 200, + } + + policies := []policy.Policy{recorder, &transformingResponsePolicy{}} + specs := []policy.PolicySpec{ + newPolicySpec("record", "v1.0.0", true, nil), + newPolicySpec("transform", "v1.0.0", true, nil), + } + + for _, index := range []uint64{1, 2, 3} { + _, err := executor.ExecuteStreamingResponsePolicies( + context.Background(), + policies, + respCtx, + &policy.StreamBody{Chunk: []byte("chunk"), Index: index, EndOfStream: index == 3}, + specs, + "api", + "route", + false, + ) + require.NoError(t, err) + } + + assert.Equal(t, []uint64{1, 2, 3}, recorder.seen, + "the downstream policy must see the kernel's indexes, not zeros") +} From 1fa9d714687b8dbae2507b8dc3c242076a094bfb Mon Sep 17 00:00:00 2001 From: Irash Perera Date: Thu, 20 Aug 2026 11:43:45 +0530 Subject: [PATCH 3/7] Clean up code formatting in chain executor and tests --- .../gateway-runtime/policy-engine/internal/executor/chain.go | 2 +- .../policy-engine/internal/executor/chain_test.go | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/gateway/gateway-runtime/policy-engine/internal/executor/chain.go b/gateway/gateway-runtime/policy-engine/internal/executor/chain.go index da79d599e9..dd5db116f3 100644 --- a/gateway/gateway-runtime/policy-engine/internal/executor/chain.go +++ b/gateway/gateway-runtime/policy-engine/internal/executor/chain.go @@ -796,7 +796,7 @@ func (c *ChainExecutor) ExecuteStreamingRequestPolicies( currentChunk = &policy.StreamBody{ Chunk: fwd.Body, EndOfStream: currentChunk.EndOfStream, - Index: currentChunk.Index, + Index: currentChunk.Index, } } diff --git a/gateway/gateway-runtime/policy-engine/internal/executor/chain_test.go b/gateway/gateway-runtime/policy-engine/internal/executor/chain_test.go index bbf15ecdd2..4dcd2aeff5 100644 --- a/gateway/gateway-runtime/policy-engine/internal/executor/chain_test.go +++ b/gateway/gateway-runtime/policy-engine/internal/executor/chain_test.go @@ -977,7 +977,6 @@ func (p *transformingResponsePolicy) OnResponseBodyChunk(_ context.Context, _ *p return policy.ForwardResponseChunk{Body: []byte("rewritten")} } - type indexRecordingResponsePolicy struct { seen []uint64 } From a41d4b4642bfcf7dc0e0edadc39773e1516b2c12 Mon Sep 17 00:00:00 2001 From: Irash Perera Date: Thu, 20 Aug 2026 11:59:07 +0530 Subject: [PATCH 4/7] Expose providerFields in the operator API and CRDs --- .../api/v1/llmprovidertemplate_types.go | 6 ++ .../api/v1/zz_generated.deepcopy.go | 7 ++ .../api/v1alpha1/llmprovidertemplate_types.go | 6 ++ .../api/v1alpha1/zz_generated.deepcopy.go | 7 ++ ...latform.wso2.com_llmprovidertemplates.yaml | 80 +++++++++++++++++++ ...latform.wso2.com_llmprovidertemplates.yaml | 80 +++++++++++++++++++ 6 files changed, 186 insertions(+) diff --git a/kubernetes/gateway-operator/api/v1/llmprovidertemplate_types.go b/kubernetes/gateway-operator/api/v1/llmprovidertemplate_types.go index dc1201e48d..3536200bcc 100644 --- a/kubernetes/gateway-operator/api/v1/llmprovidertemplate_types.go +++ b/kubernetes/gateway-operator/api/v1/llmprovidertemplate_types.go @@ -129,6 +129,12 @@ type LLMProviderTemplateData struct { // +optional ServiceTier *ExtractionIdentifier `json:"serviceTier,omitempty"` + // ProviderFields names locations a provider-specific calculator needs that + // the closed vocabulary above does not cover. Only the position is declared; + // the value found there is passed through unchanged. + // +optional + ProviderFields map[string]ExtractionIdentifier `json:"providerFields,omitempty"` + // CacheAccounting states whether the provider's cached token count is part // of the input total or additional to it. Defaults to inclusive. // +optional diff --git a/kubernetes/gateway-operator/api/v1/zz_generated.deepcopy.go b/kubernetes/gateway-operator/api/v1/zz_generated.deepcopy.go index b645c87e70..ecd1902a12 100644 --- a/kubernetes/gateway-operator/api/v1/zz_generated.deepcopy.go +++ b/kubernetes/gateway-operator/api/v1/zz_generated.deepcopy.go @@ -918,6 +918,13 @@ func (in *LLMProviderTemplateData) DeepCopyInto(out *LLMProviderTemplateData) { *out = new(ExtractionIdentifier) (*in).DeepCopyInto(*out) } + if in.ProviderFields != nil { + in, out := &in.ProviderFields, &out.ProviderFields + *out = make(map[string]ExtractionIdentifier, len(*in)) + for key, val := range *in { + (*out)[key] = *val.DeepCopy() + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LLMProviderTemplateData. diff --git a/kubernetes/gateway-operator/api/v1alpha1/llmprovidertemplate_types.go b/kubernetes/gateway-operator/api/v1alpha1/llmprovidertemplate_types.go index ec738b6795..932d7f817d 100644 --- a/kubernetes/gateway-operator/api/v1alpha1/llmprovidertemplate_types.go +++ b/kubernetes/gateway-operator/api/v1alpha1/llmprovidertemplate_types.go @@ -129,6 +129,12 @@ type LLMProviderTemplateData struct { // +optional ServiceTier *ExtractionIdentifier `json:"serviceTier,omitempty"` + // ProviderFields names locations a provider-specific calculator needs that + // the closed vocabulary above does not cover. Only the position is declared; + // the value found there is passed through unchanged. + // +optional + ProviderFields map[string]ExtractionIdentifier `json:"providerFields,omitempty"` + // CacheAccounting states whether the provider's cached token count is part // of the input total or additional to it. Defaults to inclusive. // +optional diff --git a/kubernetes/gateway-operator/api/v1alpha1/zz_generated.deepcopy.go b/kubernetes/gateway-operator/api/v1alpha1/zz_generated.deepcopy.go index 36c97398d4..aa0a944792 100644 --- a/kubernetes/gateway-operator/api/v1alpha1/zz_generated.deepcopy.go +++ b/kubernetes/gateway-operator/api/v1alpha1/zz_generated.deepcopy.go @@ -918,6 +918,13 @@ func (in *LLMProviderTemplateData) DeepCopyInto(out *LLMProviderTemplateData) { *out = new(ExtractionIdentifier) (*in).DeepCopyInto(*out) } + if in.ProviderFields != nil { + in, out := &in.ProviderFields, &out.ProviderFields + *out = make(map[string]ExtractionIdentifier, len(*in)) + for key, val := range *in { + (*out)[key] = *val.DeepCopy() + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LLMProviderTemplateData. diff --git a/kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmprovidertemplates.yaml b/kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmprovidertemplates.yaml index e9483379ae..0cd8887c62 100644 --- a/kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmprovidertemplates.yaml +++ b/kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmprovidertemplates.yaml @@ -294,6 +294,46 @@ spec: - identifier - location type: object + providerFields: + additionalProperties: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + description: |- + ProviderFields names locations a provider-specific calculator needs that + the closed vocabulary above does not cover. Only the position is declared; + the value found there is passed through unchanged. + type: object reasoningTokens: description: |- ExtractionIdentifier locates a value within an HTTP request or response. @@ -1347,6 +1387,46 @@ spec: - identifier - location type: object + providerFields: + additionalProperties: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + description: |- + ProviderFields names locations a provider-specific calculator needs that + the closed vocabulary above does not cover. Only the position is declared; + the value found there is passed through unchanged. + type: object reasoningTokens: description: |- ExtractionIdentifier locates a value within an HTTP request or response. diff --git a/kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmprovidertemplates.yaml b/kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmprovidertemplates.yaml index e9483379ae..0cd8887c62 100644 --- a/kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmprovidertemplates.yaml +++ b/kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmprovidertemplates.yaml @@ -294,6 +294,46 @@ spec: - identifier - location type: object + providerFields: + additionalProperties: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + description: |- + ProviderFields names locations a provider-specific calculator needs that + the closed vocabulary above does not cover. Only the position is declared; + the value found there is passed through unchanged. + type: object reasoningTokens: description: |- ExtractionIdentifier locates a value within an HTTP request or response. @@ -1347,6 +1387,46 @@ spec: - identifier - location type: object + providerFields: + additionalProperties: + description: |- + ExtractionIdentifier locates a value within an HTTP request or response. + Mirrors the gateway-controller management API ExtractionIdentifier model. + properties: + fallbackIdentifiers: + description: |- + FallbackIdentifiers are additional locations to try, in order, when the + primary identifier yields no value. + items: + type: string + type: array + identifier: + description: Identifier is a JSONPath expression or header name. + type: string + location: + description: Location is where to look for the value. + enum: + - header + - pathParam + - payload + - queryParam + type: string + valueMap: + additionalProperties: + type: string + description: |- + ValueMap translates values the provider reports into the vocabulary the + gateway works in. A reported value that is not a key is used unchanged. + type: object + required: + - identifier + - location + type: object + description: |- + ProviderFields names locations a provider-specific calculator needs that + the closed vocabulary above does not cover. Only the position is declared; + the value found there is passed through unchanged. + type: object reasoningTokens: description: |- ExtractionIdentifier locates a value within an HTTP request or response. From 4f6684be71de2eea44e0efbde5525c2c0e70eeea Mon Sep 17 00:00:00 2001 From: Irash Perera Date: Thu, 20 Aug 2026 12:22:21 +0530 Subject: [PATCH 5/7] Add cachedTokens and reasoningTokens to OpenAI provider template --- .../default-llm-provider-templates/openai-template.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/gateway/gateway-controller/default-llm-provider-templates/openai-template.yaml b/gateway/gateway-controller/default-llm-provider-templates/openai-template.yaml index c039abcb1a..5e7185175f 100644 --- a/gateway/gateway-controller/default-llm-provider-templates/openai-template.yaml +++ b/gateway/gateway-controller/default-llm-provider-templates/openai-template.yaml @@ -81,3 +81,9 @@ spec: completionTokens: location: payload identifier: $.usage.output_tokens + cachedTokens: + location: payload + identifier: $.usage.input_tokens_details.cached_tokens + reasoningTokens: + location: payload + identifier: $.usage.output_tokens_details.reasoning_tokens From deb6ce4faebc982bab6a4f86c699e4690825da3d Mon Sep 17 00:00:00 2001 From: Irash Perera Date: Thu, 20 Aug 2026 13:27:40 +0530 Subject: [PATCH 6/7] Add validation for providerFields in LLMProviderTemplate --- .../pkg/config/llm_validator.go | 19 +++ .../pkg/config/llm_validator_test.go | 131 ++++++++++++++++++ 2 files changed, 150 insertions(+) diff --git a/gateway/gateway-controller/pkg/config/llm_validator.go b/gateway/gateway-controller/pkg/config/llm_validator.go index 2f72a5ea4d..86ccf284e7 100644 --- a/gateway/gateway-controller/pkg/config/llm_validator.go +++ b/gateway/gateway-controller/pkg/config/llm_validator.go @@ -20,8 +20,10 @@ package config import ( "fmt" + "maps" "net/url" "regexp" + "slices" "strings" "unicode" @@ -201,6 +203,23 @@ func (v *LLMValidator) validateTemplateSpec(spec *api.LLMProviderTemplateData) [ errors = append(errors, v.validateExtractionIdentifier("spec.serviceTier", spec.ServiceTier)...) } + if spec.ProviderFields != nil { + // Sorted so two identical requests report errors in the same order; + // Go randomises map iteration. + for _, name := range slices.Sorted(maps.Keys(*spec.ProviderFields)) { + if name == "" { + errors = append(errors, ValidationError{ + Field: "spec.providerFields", + Message: "providerFields key cannot be empty", + }) + continue + } + field := (*spec.ProviderFields)[name] + errors = append(errors, v.validateExtractionIdentifier( + fmt.Sprintf("spec.providerFields.%s", name), &field)...) + } + } + errors = append(errors, v.validateCacheAccounting("spec.cacheAccounting", spec.CacheAccounting)...) if spec.ResourceMappings != nil { diff --git a/gateway/gateway-controller/pkg/config/llm_validator_test.go b/gateway/gateway-controller/pkg/config/llm_validator_test.go index 070adc914e..31fb49d954 100644 --- a/gateway/gateway-controller/pkg/config/llm_validator_test.go +++ b/gateway/gateway-controller/pkg/config/llm_validator_test.go @@ -2186,3 +2186,134 @@ func TestValidateLLMProviderTemplate_ValueMap(t *testing.T) { }) } } + +func TestValidateLLMProviderTemplate_ProviderFields(t *testing.T) { + emptyFallback := []string{""} + emptyValueMapKey := map[string]string{"": "priority"} + + tests := []struct { + name string + providerFields map[string]api.ExtractionIdentifier + expectedField string + }{ + { + name: "valid entry is accepted", + providerFields: map[string]api.ExtractionIdentifier{ + "choices": {Location: api.Payload, Identifier: "$.choices"}, + }, + }, + { + name: "unsupported location is rejected", + providerFields: map[string]api.ExtractionIdentifier{ + "choices": {Location: "payloadx", Identifier: "$.choices"}, + }, + expectedField: "spec.providerFields.choices.location", + }, + { + name: "missing identifier is rejected", + providerFields: map[string]api.ExtractionIdentifier{ + "choices": {Location: api.Payload, Identifier: ""}, + }, + expectedField: "spec.providerFields.choices.identifier", + }, + { + name: "empty fallback identifier is rejected", + providerFields: map[string]api.ExtractionIdentifier{ + "choices": { + Location: api.Payload, + Identifier: "$.choices", + FallbackIdentifiers: &emptyFallback, + }, + }, + expectedField: "spec.providerFields.choices.fallbackIdentifiers[0]", + }, + { + name: "empty valueMap key is rejected", + providerFields: map[string]api.ExtractionIdentifier{ + "choices": { + Location: api.Payload, + Identifier: "$.choices", + ValueMap: &emptyValueMapKey, + }, + }, + expectedField: "spec.providerFields.choices.valueMap", + }, + { + name: "empty map key is rejected", + providerFields: map[string]api.ExtractionIdentifier{ + "": {Location: api.Payload, Identifier: "$.choices"}, + }, + expectedField: "spec.providerFields", + }, + } + + validator := NewLLMValidator() + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + providerFields := tt.providerFields + template := api.LLMProviderTemplate{ + ApiVersion: "gateway.api-platform.wso2.com/v1", + Kind: api.LLMProviderTemplateKindLlmProviderTemplate, + Metadata: api.Metadata{Name: "openai"}, + Spec: api.LLMProviderTemplateData{ + DisplayName: "test", + PromptTokens: &api.ExtractionIdentifier{ + Location: api.Payload, + Identifier: "$.usage.prompt_tokens", + }, + ProviderFields: &providerFields, + }, + } + + errors := validator.Validate(&template) + + if tt.expectedField == "" { + assert.Empty(t, errors, "Should not have validation errors") + return + } + + require.NotEmpty(t, errors, "Should have validation errors") + found := false + for _, err := range errors { + if err.Field == tt.expectedField { + found = true + break + } + } + assert.True(t, found, "Should report %q, got %v", tt.expectedField, errors) + }) + } +} + +// A providerFields entry must be reported under its own key, so an operator +// with several entries can tell which one is wrong. +func TestValidateLLMProviderTemplate_ProviderFieldsErrorsAreKeyed(t *testing.T) { + providerFields := map[string]api.ExtractionIdentifier{ + "choices": {Location: "bogus", Identifier: "$.choices"}, + "searchContextSize": {Location: api.Payload, Identifier: ""}, + } + + template := api.LLMProviderTemplate{ + ApiVersion: "gateway.api-platform.wso2.com/v1", + Kind: api.LLMProviderTemplateKindLlmProviderTemplate, + Metadata: api.Metadata{Name: "openai"}, + Spec: api.LLMProviderTemplateData{ + DisplayName: "test", + ProviderFields: &providerFields, + }, + } + + fields := make([]string, 0, 2) + for _, err := range NewLLMValidator().Validate(&template) { + if strings.HasPrefix(err.Field, "spec.providerFields") { + fields = append(fields, err.Field) + } + } + + // Sorted key order, so the list is identical on every run. + assert.Equal(t, []string{ + "spec.providerFields.choices.location", + "spec.providerFields.searchContextSize.identifier", + }, fields) +} From a67504bbdf105566f05147423a78e8fe6ecb4515 Mon Sep 17 00:00:00 2001 From: Irash Perera Date: Sun, 23 Aug 2026 15:30:09 +0530 Subject: [PATCH 7/7] Read Anthropic thinking tokens --- .../default-llm-provider-templates/anthropic-template.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/gateway/gateway-controller/default-llm-provider-templates/anthropic-template.yaml b/gateway/gateway-controller/default-llm-provider-templates/anthropic-template.yaml index 28b16f5caa..ced7c4f814 100644 --- a/gateway/gateway-controller/default-llm-provider-templates/anthropic-template.yaml +++ b/gateway/gateway-controller/default-llm-provider-templates/anthropic-template.yaml @@ -61,6 +61,11 @@ spec: identifier: $.usage.cache_creation.ephemeral_1h_input_tokens fallbackIdentifiers: - $.message.usage.cache_creation.ephemeral_1h_input_tokens + reasoningTokens: + location: payload + identifier: $.usage.output_tokens_details.thinking_tokens + fallbackIdentifiers: + - $.message.usage.output_tokens_details.thinking_tokens serviceTier: location: payload identifier: $.usage.service_tier