feat: add hand-owned model quirks overlay
This commit is contained in:
4 files changed
+1211
-3
No files matched your search
+880
@@ -0,0 +1,880 @@
|
||||
# Hand-owned model behavior overlay.
|
||||
#
|
||||
# Everything in this file is curated by hand and layered on top of API-sourced
|
||||
# model data: oauth blocks, request patches, wire-name aliases, behavioral
|
||||
# flags, and models that no public catalog provides.
|
||||
#
|
||||
# `models:` entries are self-complete — `rules` apply ONLY to API-sourced
|
||||
# models, never to hand-owned `models:` entries. Legacy verbatim-preserved
|
||||
# entries are grouped under a `# legacy — pruning candidates` comment.
|
||||
|
||||
- provider: openai
|
||||
rules:
|
||||
- match: "gpt-5.6-*"
|
||||
default_reasoning_effort: medium
|
||||
- match: "gpt-5.5"
|
||||
default_reasoning_effort: medium
|
||||
- match: "gpt-5.5-pro"
|
||||
default_reasoning_effort: high
|
||||
- match: "gpt-5.4"
|
||||
default_reasoning_effort: none
|
||||
- match: "gpt-5.4-pro"
|
||||
default_reasoning_effort: medium
|
||||
- match: "gpt-5.4-mini"
|
||||
default_reasoning_effort: none
|
||||
- match: "gpt-5.4-nano"
|
||||
default_reasoning_effort: none
|
||||
- match: "gpt-5.3-codex"
|
||||
default_reasoning_effort: medium
|
||||
- match: "gpt-5.2"
|
||||
default_reasoning_effort: none
|
||||
- match: "gpt-5.2-pro"
|
||||
default_reasoning_effort: medium
|
||||
- match: "gpt-5.1"
|
||||
default_reasoning_effort: none
|
||||
- match: "gpt-5.1-chat-latest"
|
||||
default_reasoning_effort: none
|
||||
- match: "gpt-5"
|
||||
default_reasoning_effort: medium
|
||||
- match: "gpt-5-chat-latest"
|
||||
default_reasoning_effort: medium
|
||||
- match: "o4-mini"
|
||||
system_prompt_prefix: Formatting re-enabled
|
||||
default_reasoning_effort: medium
|
||||
patch:
|
||||
body:
|
||||
max_tokens: null
|
||||
temperature: null
|
||||
top_p: null
|
||||
- match: "o3"
|
||||
system_prompt_prefix: Formatting re-enabled
|
||||
patch:
|
||||
body:
|
||||
max_tokens: null
|
||||
temperature: null
|
||||
top_p: null
|
||||
- match: "o3-mini"
|
||||
system_prompt_prefix: Formatting re-enabled
|
||||
patch:
|
||||
body:
|
||||
max_tokens: null
|
||||
temperature: null
|
||||
top_p: null
|
||||
models:
|
||||
- name: text-embedding-3-large
|
||||
type: embedding
|
||||
input_price: 0.13
|
||||
max_tokens_per_chunk: 8191
|
||||
default_chunk_size: 2000
|
||||
max_batch_size: 100
|
||||
- name: text-embedding-3-small
|
||||
type: embedding
|
||||
input_price: 0.02
|
||||
max_tokens_per_chunk: 8191
|
||||
default_chunk_size: 2000
|
||||
max_batch_size: 100
|
||||
# legacy — pruning candidates
|
||||
- name: o4-mini-high
|
||||
max_output_tokens: 100000
|
||||
real_name: o4-mini
|
||||
max_input_tokens: 200000
|
||||
input_price: 1.1
|
||||
output_price: 4.4
|
||||
supports_vision: true
|
||||
supports_function_calling: true
|
||||
system_prompt_prefix: Formatting re-enabled
|
||||
patch:
|
||||
body:
|
||||
reasoning_effort: high
|
||||
max_tokens: null
|
||||
temperature: null
|
||||
top_p: null
|
||||
- name: o3-high
|
||||
real_name: o3
|
||||
max_input_tokens: 200000
|
||||
input_price: 2
|
||||
output_price: 8
|
||||
supports_vision: true
|
||||
supports_function_calling: true
|
||||
system_prompt_prefix: Formatting re-enabled
|
||||
patch:
|
||||
body:
|
||||
reasoning_effort: high
|
||||
max_tokens: null
|
||||
temperature: null
|
||||
top_p: null
|
||||
- name: o3-mini-high
|
||||
max_output_tokens: 100000
|
||||
real_name: o3-mini
|
||||
max_input_tokens: 200000
|
||||
input_price: 1.1
|
||||
output_price: 4.4
|
||||
supports_vision: true
|
||||
supports_function_calling: true
|
||||
system_prompt_prefix: Formatting re-enabled
|
||||
patch:
|
||||
body:
|
||||
reasoning_effort: high
|
||||
max_tokens: null
|
||||
temperature: null
|
||||
top_p: null
|
||||
|
||||
- provider: gemini
|
||||
rules:
|
||||
- match: "gemini-3.6-flash"
|
||||
default_reasoning_effort: medium
|
||||
- match: "gemini-3.5-flash"
|
||||
default_reasoning_effort: medium
|
||||
- match: "gemini-3-flash-preview"
|
||||
default_reasoning_effort: high
|
||||
- match: "gemini-3.1-flash-lite"
|
||||
default_reasoning_effort: minimal
|
||||
- match: "gemini-3.1-pro-preview"
|
||||
default_reasoning_effort: high
|
||||
- match: "gemini-3-pro-preview"
|
||||
default_reasoning_effort: high
|
||||
- match: "gemini-2.5-pro"
|
||||
default_reasoning_effort: high
|
||||
models:
|
||||
- name: text-embedding-004
|
||||
type: embedding
|
||||
input_price: 0
|
||||
max_tokens_per_chunk: 2048
|
||||
default_chunk_size: 1500
|
||||
max_batch_size: 100
|
||||
|
||||
- provider: claude
|
||||
rules:
|
||||
- match: "claude-*"
|
||||
require_max_tokens: true
|
||||
- match: "claude-opus-5"
|
||||
default_reasoning_effort: high
|
||||
- match: "claude-fable-*"
|
||||
default_reasoning_effort: high
|
||||
- match: "claude-opus-4-8"
|
||||
default_reasoning_effort: high
|
||||
- match: "claude-opus-4-7"
|
||||
default_reasoning_effort: high
|
||||
- match: "claude-opus-4-6"
|
||||
default_reasoning_effort: high
|
||||
- match: "claude-sonnet-5"
|
||||
default_reasoning_effort: high
|
||||
- match: "claude-sonnet-4-6"
|
||||
default_reasoning_effort: high
|
||||
variants:
|
||||
- match: "claude-opus-4-6"
|
||||
suffix: ":thinking"
|
||||
real_name: claude-opus-4-6
|
||||
max_output_tokens: 24000
|
||||
reasoning_levels: []
|
||||
patch:
|
||||
body:
|
||||
temperature: null
|
||||
top_p: null
|
||||
thinking:
|
||||
type: enabled
|
||||
budget_tokens: 16000
|
||||
- match: "claude-sonnet-4-6"
|
||||
suffix: ":thinking"
|
||||
real_name: claude-sonnet-4-6
|
||||
max_output_tokens: 24000
|
||||
reasoning_levels: []
|
||||
patch:
|
||||
body:
|
||||
temperature: null
|
||||
top_p: null
|
||||
thinking:
|
||||
type: enabled
|
||||
budget_tokens: 16000
|
||||
- match: "claude-sonnet-4-5-20250929"
|
||||
suffix: ":thinking"
|
||||
real_name: claude-sonnet-4-5-20250929
|
||||
max_output_tokens: 24000
|
||||
reasoning_levels: []
|
||||
patch:
|
||||
body:
|
||||
temperature: null
|
||||
top_p: null
|
||||
thinking:
|
||||
type: enabled
|
||||
budget_tokens: 16000
|
||||
- match: "claude-haiku-4-5-20251001"
|
||||
suffix: ":thinking"
|
||||
real_name: claude-haiku-4-5-20251001
|
||||
max_output_tokens: 24000
|
||||
reasoning_levels: []
|
||||
patch:
|
||||
body:
|
||||
temperature: null
|
||||
top_p: null
|
||||
thinking:
|
||||
type: enabled
|
||||
budget_tokens: 16000
|
||||
- match: "claude-opus-4-5-20251101"
|
||||
suffix: ":thinking"
|
||||
real_name: claude-opus-4-5-20251101
|
||||
max_output_tokens: 24000
|
||||
reasoning_levels: []
|
||||
patch:
|
||||
body:
|
||||
temperature: null
|
||||
top_p: null
|
||||
thinking:
|
||||
type: enabled
|
||||
budget_tokens: 16000
|
||||
- match: "claude-opus-4-1-20250805"
|
||||
suffix: ":thinking"
|
||||
real_name: claude-opus-4-1-20250805
|
||||
max_output_tokens: 24000
|
||||
reasoning_levels: []
|
||||
patch:
|
||||
body:
|
||||
temperature: null
|
||||
top_p: null
|
||||
thinking:
|
||||
type: enabled
|
||||
budget_tokens: 16000
|
||||
- match: "claude-opus-4-20250514"
|
||||
suffix: ":thinking"
|
||||
real_name: claude-opus-4-20250514
|
||||
max_output_tokens: 24000
|
||||
reasoning_levels: []
|
||||
patch:
|
||||
body:
|
||||
temperature: null
|
||||
top_p: null
|
||||
thinking:
|
||||
type: enabled
|
||||
budget_tokens: 16000
|
||||
|
||||
- provider: mistral
|
||||
models:
|
||||
- name: mistral-embed
|
||||
type: embedding
|
||||
max_input_tokens: 8092
|
||||
input_price: 0.1
|
||||
max_tokens_per_chunk: 8092
|
||||
default_chunk_size: 2000
|
||||
|
||||
- provider: ai21
|
||||
models:
|
||||
- name: jamba-large
|
||||
max_input_tokens: 256000
|
||||
input_price: 2
|
||||
output_price: 8
|
||||
supports_function_calling: true
|
||||
- name: jamba-mini
|
||||
max_input_tokens: 256000
|
||||
input_price: 0.2
|
||||
output_price: 0.4
|
||||
supports_function_calling: true
|
||||
|
||||
- provider: cohere
|
||||
models:
|
||||
- name: embed-v4.0
|
||||
type: embedding
|
||||
input_price: 0.12
|
||||
max_tokens_per_chunk: 2048
|
||||
default_chunk_size: 2000
|
||||
max_batch_size: 96
|
||||
- name: embed-english-v3.0
|
||||
type: embedding
|
||||
input_price: 0.1
|
||||
max_tokens_per_chunk: 512
|
||||
default_chunk_size: 1000
|
||||
max_batch_size: 96
|
||||
- name: embed-multilingual-v3.0
|
||||
type: embedding
|
||||
input_price: 0.1
|
||||
max_tokens_per_chunk: 512
|
||||
default_chunk_size: 1000
|
||||
max_batch_size: 96
|
||||
- name: rerank-v3.5
|
||||
type: reranker
|
||||
max_input_tokens: 4096
|
||||
- name: rerank-english-v3.0
|
||||
type: reranker
|
||||
max_input_tokens: 4096
|
||||
- name: rerank-multilingual-v3.0
|
||||
type: reranker
|
||||
max_input_tokens: 4096
|
||||
|
||||
- provider: xai
|
||||
oauth:
|
||||
client_id: b1a00492-073a-47ea-816f-4c329264a828
|
||||
authorize_url: https://auth.x.ai/oauth2/authorize
|
||||
token_url: https://auth.x.ai/oauth2/token
|
||||
scopes:
|
||||
- openid
|
||||
- profile
|
||||
- email
|
||||
- offline_access
|
||||
- grok-cli:access
|
||||
- api:access
|
||||
redirect_port: 56121
|
||||
flow: pkce
|
||||
token_request_format: form_url_encoded
|
||||
extra_authorize_params:
|
||||
plan: generic
|
||||
referrer: coyote
|
||||
echo_pkce_in_token_exchange: true
|
||||
models:
|
||||
- name: grok-4.20
|
||||
real_name: grok-4.20-multi-agent-0309
|
||||
input_price: 1.25
|
||||
output_price: 2.5
|
||||
max_input_tokens: 1000000
|
||||
supports_function_calling: true
|
||||
- name: grok-4.20-reasoning
|
||||
real_name: grok-4.20-0309-reasoning
|
||||
input_price: 1.25
|
||||
output_price: 2.5
|
||||
max_input_tokens: 1000000
|
||||
supports_function_calling: true
|
||||
- name: grok-4.20-non-reasoning
|
||||
real_name: grok-4.20-0309-non-reasoning
|
||||
input_price: 1.25
|
||||
output_price: 2.5
|
||||
max_input_tokens: 1000000
|
||||
supports_function_calling: true
|
||||
|
||||
- provider: vertexai
|
||||
rules:
|
||||
- match: "claude-*"
|
||||
require_max_tokens: true
|
||||
- match: "gemini-3.5-flash"
|
||||
default_reasoning_effort: medium
|
||||
- match: "gemini-3-flash-preview"
|
||||
default_reasoning_effort: high
|
||||
- match: "gemini-3.1-flash-lite"
|
||||
default_reasoning_effort: minimal
|
||||
- match: "gemini-3.1-pro-preview"
|
||||
default_reasoning_effort: high
|
||||
- match: "gemini-3-pro-preview"
|
||||
default_reasoning_effort: high
|
||||
- match: "gemini-2.5-flash"
|
||||
default_reasoning_effort: medium
|
||||
- match: "gemini-2.5-pro"
|
||||
default_reasoning_effort: high
|
||||
- match: "claude-opus-5"
|
||||
default_reasoning_effort: high
|
||||
- match: "claude-fable-*"
|
||||
default_reasoning_effort: high
|
||||
- match: "claude-opus-4-8"
|
||||
default_reasoning_effort: high
|
||||
- match: "claude-opus-4-7"
|
||||
default_reasoning_effort: high
|
||||
- match: "claude-sonnet-5"
|
||||
default_reasoning_effort: high
|
||||
- match: "claude-sonnet-4-6"
|
||||
default_reasoning_effort: high
|
||||
variants:
|
||||
- match: "claude-opus-4-6"
|
||||
suffix: ":thinking"
|
||||
real_name: claude-opus-4-6
|
||||
max_output_tokens: 24000
|
||||
supports_function_calling: false
|
||||
reasoning_levels: []
|
||||
patch:
|
||||
body:
|
||||
temperature: null
|
||||
top_p: null
|
||||
thinking:
|
||||
type: enabled
|
||||
budget_tokens: 16000
|
||||
- match: "claude-sonnet-4-6"
|
||||
suffix: ":thinking"
|
||||
real_name: claude-sonnet-4-6
|
||||
max_output_tokens: 24000
|
||||
supports_function_calling: false
|
||||
reasoning_levels: []
|
||||
patch:
|
||||
body:
|
||||
temperature: null
|
||||
top_p: null
|
||||
thinking:
|
||||
type: enabled
|
||||
budget_tokens: 16000
|
||||
- match: "claude-opus-4-5@20251101"
|
||||
suffix: ":thinking"
|
||||
real_name: claude-opus-4-5@20251101
|
||||
max_output_tokens: 24000
|
||||
supports_function_calling: false
|
||||
reasoning_levels: []
|
||||
patch:
|
||||
body:
|
||||
temperature: null
|
||||
top_p: null
|
||||
thinking:
|
||||
type: enabled
|
||||
budget_tokens: 16000
|
||||
- match: "claude-sonnet-4-5@20250929"
|
||||
suffix: ":thinking"
|
||||
real_name: claude-sonnet-4-5@20250929
|
||||
max_output_tokens: 24000
|
||||
supports_function_calling: false
|
||||
reasoning_levels: []
|
||||
patch:
|
||||
body:
|
||||
temperature: null
|
||||
top_p: null
|
||||
thinking:
|
||||
type: enabled
|
||||
budget_tokens: 16000
|
||||
- match: "claude-haiku-4-5@20251001"
|
||||
suffix: ":thinking"
|
||||
real_name: claude-haiku-4-5@20251001
|
||||
max_output_tokens: 24000
|
||||
supports_function_calling: false
|
||||
reasoning_levels: []
|
||||
patch:
|
||||
body:
|
||||
temperature: null
|
||||
top_p: null
|
||||
thinking:
|
||||
type: enabled
|
||||
budget_tokens: 16000
|
||||
models:
|
||||
- name: text-embedding-005
|
||||
type: embedding
|
||||
max_input_tokens: 20000
|
||||
input_price: 0.025
|
||||
max_tokens_per_chunk: 2048
|
||||
default_chunk_size: 1500
|
||||
max_batch_size: 5
|
||||
- name: text-multilingual-embedding-002
|
||||
type: embedding
|
||||
max_input_tokens: 20000
|
||||
input_price: 0.2
|
||||
max_tokens_per_chunk: 2048
|
||||
default_chunk_size: 1500
|
||||
max_batch_size: 5
|
||||
|
||||
- provider: bedrock
|
||||
rules:
|
||||
- match: "us.anthropic.claude-*"
|
||||
require_max_tokens: true
|
||||
- match: "us.meta.llama*"
|
||||
require_max_tokens: true
|
||||
- match: "us.anthropic.claude-opus-5"
|
||||
default_reasoning_effort: high
|
||||
- match: "us.anthropic.claude-fable-*"
|
||||
default_reasoning_effort: high
|
||||
- match: "us.anthropic.claude-opus-4-8"
|
||||
default_reasoning_effort: high
|
||||
- match: "us.anthropic.claude-opus-4-7"
|
||||
default_reasoning_effort: high
|
||||
- match: "us.anthropic.claude-opus-4-6-v1"
|
||||
default_reasoning_effort: high
|
||||
- match: "us.anthropic.claude-sonnet-5"
|
||||
default_reasoning_effort: high
|
||||
- match: "us.anthropic.claude-sonnet-4-6"
|
||||
default_reasoning_effort: high
|
||||
variants:
|
||||
- match: "us.anthropic.claude-opus-4-6-v1"
|
||||
suffix: ":thinking"
|
||||
real_name: us.anthropic.claude-opus-4-6-v1
|
||||
max_output_tokens: 24000
|
||||
supports_function_calling: false
|
||||
reasoning_levels: []
|
||||
patch:
|
||||
body:
|
||||
inferenceConfig:
|
||||
temperature: null
|
||||
topP: null
|
||||
additionalModelRequestFields:
|
||||
thinking:
|
||||
type: enabled
|
||||
budget_tokens: 16000
|
||||
- match: "us.anthropic.claude-sonnet-4-6"
|
||||
suffix: ":thinking"
|
||||
real_name: us.anthropic.claude-sonnet-4-6
|
||||
max_output_tokens: 24000
|
||||
supports_function_calling: false
|
||||
reasoning_levels: []
|
||||
patch:
|
||||
body:
|
||||
inferenceConfig:
|
||||
temperature: null
|
||||
topP: null
|
||||
additionalModelRequestFields:
|
||||
thinking:
|
||||
type: enabled
|
||||
budget_tokens: 16000
|
||||
- match: "us.anthropic.claude-opus-4-5-20251101-v1:0"
|
||||
suffix: ":thinking"
|
||||
real_name: us.anthropic.claude-opus-4-5-20251101-v1:0
|
||||
max_output_tokens: 24000
|
||||
supports_function_calling: false
|
||||
reasoning_levels: []
|
||||
patch:
|
||||
body:
|
||||
inferenceConfig:
|
||||
temperature: null
|
||||
topP: null
|
||||
additionalModelRequestFields:
|
||||
thinking:
|
||||
type: enabled
|
||||
budget_tokens: 16000
|
||||
- match: "us.anthropic.claude-sonnet-4-5-20250929-v1:0"
|
||||
suffix: ":thinking"
|
||||
real_name: us.anthropic.claude-sonnet-4-5-20250929-v1:0
|
||||
max_output_tokens: 24000
|
||||
supports_function_calling: false
|
||||
reasoning_levels: []
|
||||
patch:
|
||||
body:
|
||||
inferenceConfig:
|
||||
temperature: null
|
||||
topP: null
|
||||
additionalModelRequestFields:
|
||||
thinking:
|
||||
type: enabled
|
||||
budget_tokens: 16000
|
||||
- match: "us.anthropic.claude-haiku-4-5-20251001-v1:0"
|
||||
suffix: ":thinking"
|
||||
real_name: us.anthropic.claude-haiku-4-5-20251001-v1:0
|
||||
max_output_tokens: 24000
|
||||
supports_function_calling: false
|
||||
reasoning_levels: []
|
||||
patch:
|
||||
body:
|
||||
inferenceConfig:
|
||||
temperature: null
|
||||
topP: null
|
||||
additionalModelRequestFields:
|
||||
thinking:
|
||||
type: enabled
|
||||
budget_tokens: 16000
|
||||
models:
|
||||
- name: cohere.embed-english-v3
|
||||
type: embedding
|
||||
input_price: 0.1
|
||||
max_tokens_per_chunk: 512
|
||||
default_chunk_size: 1000
|
||||
max_batch_size: 96
|
||||
- name: cohere.embed-multilingual-v3
|
||||
type: embedding
|
||||
input_price: 0.1
|
||||
max_tokens_per_chunk: 512
|
||||
default_chunk_size: 1000
|
||||
max_batch_size: 96
|
||||
|
||||
- provider: cloudflare
|
||||
rules:
|
||||
- match: "@cf/*"
|
||||
require_max_tokens: true
|
||||
models:
|
||||
- name: '@cf/baai/bge-large-en-v1.5'
|
||||
type: embedding
|
||||
input_price: 0
|
||||
max_tokens_per_chunk: 512
|
||||
default_chunk_size: 1000
|
||||
max_batch_size: 100
|
||||
|
||||
- provider: ernie
|
||||
models:
|
||||
- name: ernie-4.5-turbo-128k
|
||||
max_input_tokens: 131072
|
||||
input_price: 0.112
|
||||
output_price: 0.448
|
||||
- name: ernie-4.5-turbo-vl-32k
|
||||
max_input_tokens: 32768
|
||||
input_price: 0.42
|
||||
output_price: 1.26
|
||||
supports_vision: true
|
||||
- name: ernie-5.0-thinking-preview
|
||||
max_input_tokens: 131072
|
||||
input_price: 1.4
|
||||
output_price: 5.6
|
||||
- name: ernie-x1.1-preview
|
||||
max_input_tokens: 65536
|
||||
input_price: 0.14
|
||||
output_price: 0.56
|
||||
- name: bge-large-zh
|
||||
type: embedding
|
||||
input_price: 0.07
|
||||
max_tokens_per_chunk: 512
|
||||
default_chunk_size: 1000
|
||||
max_batch_size: 16
|
||||
- name: bge-large-en
|
||||
type: embedding
|
||||
input_price: 0.07
|
||||
max_tokens_per_chunk: 512
|
||||
default_chunk_size: 1000
|
||||
max_batch_size: 16
|
||||
- name: bce-reranker-base
|
||||
type: reranker
|
||||
max_input_tokens: 1024
|
||||
input_price: 0.07
|
||||
|
||||
- provider: qianwen
|
||||
rules:
|
||||
- match: "qwen3.5-plus"
|
||||
patch:
|
||||
body:
|
||||
enable_thinking: false
|
||||
- match: "qwen3.5-397b-a17b"
|
||||
patch:
|
||||
body:
|
||||
enable_thinking: false
|
||||
variants:
|
||||
- match: "qwen3-max"
|
||||
suffix: ":thinking"
|
||||
real_name: qwen3-max
|
||||
patch:
|
||||
body:
|
||||
enable_thinking: true
|
||||
models:
|
||||
- name: qwen3.5-plus:thinking
|
||||
real_name: qwen3.5-plus
|
||||
max_input_tokens: 262144
|
||||
supports_function_calling: true
|
||||
- name: qwen3.5-397b-a17b:thinking
|
||||
real_name: qwen3.5-397b-a17b
|
||||
max_input_tokens: 262144
|
||||
supports_function_calling: true
|
||||
- name: text-embedding-v4
|
||||
type: embedding
|
||||
input_price: 0.1
|
||||
max_tokens_per_chunk: 8192
|
||||
default_chunk_size: 2000
|
||||
max_batch_size: 10
|
||||
- name: text-embedding-v3
|
||||
type: embedding
|
||||
input_price: 0.1
|
||||
max_tokens_per_chunk: 8192
|
||||
default_chunk_size: 2000
|
||||
max_batch_size: 10
|
||||
|
||||
- provider: hunyuan
|
||||
models:
|
||||
- name: hunyuan-2.0-instruct-20251111
|
||||
max_input_tokens: 131072
|
||||
input_price: 0.112
|
||||
output_price: 0.28
|
||||
supports_function_calling: true
|
||||
- name: hunyuan-2.0-thinking-20251109
|
||||
max_input_tokens: 131072
|
||||
input_price: 0.14
|
||||
output_price: 0.56
|
||||
supports_function_calling: true
|
||||
- name: hunyuan-vision-1.5-instruct
|
||||
max_input_tokens: 24576
|
||||
input_price: 0.42
|
||||
output_price: 1.26
|
||||
supports_vision: true
|
||||
- name: hunyuan-embedding
|
||||
type: embedding
|
||||
input_price: 0.01
|
||||
max_tokens_per_chunk: 1024
|
||||
default_chunk_size: 1000
|
||||
max_batch_size: 100
|
||||
|
||||
- provider: zhipuai
|
||||
variants:
|
||||
- match: "glm-5"
|
||||
suffix: ":instruct"
|
||||
real_name: glm-5
|
||||
patch:
|
||||
body:
|
||||
thinking:
|
||||
type: disabled
|
||||
- match: "glm-4.7"
|
||||
suffix: ":instruct"
|
||||
real_name: glm-4.7
|
||||
patch:
|
||||
body:
|
||||
thinking:
|
||||
type: disabled
|
||||
models:
|
||||
- name: embedding-3
|
||||
type: embedding
|
||||
max_input_tokens: 8192
|
||||
input_price: 0.07
|
||||
max_tokens_per_chunk: 8192
|
||||
default_chunk_size: 2000
|
||||
- name: rerank
|
||||
type: reranker
|
||||
max_input_tokens: 4096
|
||||
input_price: 0.112
|
||||
|
||||
- provider: openrouter
|
||||
rules:
|
||||
- match: "anthropic/claude-*"
|
||||
require_max_tokens: true
|
||||
- match: "openai/gpt-5.6-*"
|
||||
default_reasoning_effort: medium
|
||||
- match: "openai/gpt-5.5"
|
||||
default_reasoning_effort: medium
|
||||
- match: "openai/gpt-5.5-pro"
|
||||
default_reasoning_effort: high
|
||||
- match: "openai/gpt-5.4"
|
||||
default_reasoning_effort: none
|
||||
- match: "openai/gpt-5.4-pro"
|
||||
default_reasoning_effort: medium
|
||||
- match: "openai/gpt-5.4-mini"
|
||||
default_reasoning_effort: none
|
||||
- match: "openai/gpt-5.4-nano"
|
||||
default_reasoning_effort: none
|
||||
- match: "openai/gpt-5.3-codex"
|
||||
default_reasoning_effort: medium
|
||||
- match: "openai/gpt-5.2"
|
||||
default_reasoning_effort: none
|
||||
- match: "openai/gpt-5.2-pro"
|
||||
default_reasoning_effort: medium
|
||||
- match: "openai/gpt-5"
|
||||
default_reasoning_effort: medium
|
||||
- match: "google/gemini-3.5-flash"
|
||||
default_reasoning_effort: medium
|
||||
- match: "google/gemini-3-flash-preview"
|
||||
default_reasoning_effort: high
|
||||
- match: "google/gemini-3.1-flash-lite"
|
||||
default_reasoning_effort: minimal
|
||||
- match: "google/gemini-3.1-pro-preview"
|
||||
default_reasoning_effort: high
|
||||
- match: "google/gemini-3-pro-preview"
|
||||
default_reasoning_effort: high
|
||||
- match: "google/gemini-2.5-flash"
|
||||
default_reasoning_effort: low
|
||||
- match: "google/gemini-2.5-pro"
|
||||
default_reasoning_effort: high
|
||||
- match: "anthropic/claude-opus-5"
|
||||
default_reasoning_effort: high
|
||||
- match: "anthropic/claude-fable-*"
|
||||
default_reasoning_effort: high
|
||||
- match: "anthropic/claude-opus-4-8"
|
||||
default_reasoning_effort: high
|
||||
- match: "anthropic/claude-opus-4-7"
|
||||
default_reasoning_effort: high
|
||||
- match: "anthropic/claude-opus-4.6"
|
||||
default_reasoning_effort: high
|
||||
- match: "anthropic/claude-sonnet-5"
|
||||
default_reasoning_effort: high
|
||||
- match: "anthropic/claude-sonnet-4.6"
|
||||
default_reasoning_effort: high
|
||||
- match: "perplexity/sonar-reasoning-pro"
|
||||
patch:
|
||||
body:
|
||||
include_reasoning: true
|
||||
- match: "perplexity/sonar-deep-research"
|
||||
patch:
|
||||
body:
|
||||
include_reasoning: true
|
||||
|
||||
- provider: github
|
||||
models:
|
||||
- name: text-embedding-3-large
|
||||
type: embedding
|
||||
max_tokens_per_chunk: 8191
|
||||
default_chunk_size: 2000
|
||||
max_batch_size: 100
|
||||
- name: text-embedding-3-small
|
||||
type: embedding
|
||||
max_tokens_per_chunk: 8191
|
||||
default_chunk_size: 2000
|
||||
max_batch_size: 100
|
||||
- name: cohere-embed-v3-english
|
||||
type: embedding
|
||||
max_tokens_per_chunk: 512
|
||||
default_chunk_size: 1000
|
||||
max_batch_size: 96
|
||||
- name: cohere-embed-v3-multilingual
|
||||
type: embedding
|
||||
max_tokens_per_chunk: 512
|
||||
default_chunk_size: 1000
|
||||
max_batch_size: 96
|
||||
|
||||
- provider: deepinfra
|
||||
models:
|
||||
- name: BAAI/bge-large-en-v1.5
|
||||
type: embedding
|
||||
input_price: 0.01
|
||||
max_tokens_per_chunk: 512
|
||||
default_chunk_size: 1000
|
||||
max_batch_size: 100
|
||||
- name: BAAI/bge-m3
|
||||
type: embedding
|
||||
input_price: 0.01
|
||||
max_tokens_per_chunk: 8192
|
||||
default_chunk_size: 2000
|
||||
max_batch_size: 100
|
||||
- name: intfloat/e5-large-v2
|
||||
type: embedding
|
||||
input_price: 0.01
|
||||
max_tokens_per_chunk: 512
|
||||
default_chunk_size: 1000
|
||||
max_batch_size: 100
|
||||
- name: intfloat/multilingual-e5-large
|
||||
type: embedding
|
||||
input_price: 0.01
|
||||
max_tokens_per_chunk: 512
|
||||
default_chunk_size: 1000
|
||||
max_batch_size: 100
|
||||
- name: thenlper/gte-large
|
||||
type: embedding
|
||||
input_price: 0.01
|
||||
max_tokens_per_chunk: 512
|
||||
default_chunk_size: 1000
|
||||
max_batch_size: 100
|
||||
|
||||
- provider: jina
|
||||
models:
|
||||
- name: jina-embeddings-v3
|
||||
type: embedding
|
||||
input_price: 0
|
||||
max_tokens_per_chunk: 8192
|
||||
default_chunk_size: 2000
|
||||
max_batch_size: 100
|
||||
- name: jina-clip-v2
|
||||
type: embedding
|
||||
input_price: 0
|
||||
max_tokens_per_chunk: 8192
|
||||
default_chunk_size: 1500
|
||||
max_batch_size: 100
|
||||
- name: jina-colbert-v2
|
||||
type: embedding
|
||||
input_price: 0
|
||||
max_tokens_per_chunk: 8192
|
||||
default_chunk_size: 1500
|
||||
max_batch_size: 100
|
||||
- name: jina-reranker-v2-base-multilingual
|
||||
type: reranker
|
||||
max_input_tokens: 8192
|
||||
input_price: 0
|
||||
- name: jina-colbert-v2
|
||||
type: reranker
|
||||
max_input_tokens: 8192
|
||||
input_price: 0
|
||||
|
||||
- provider: voyageai
|
||||
models:
|
||||
- name: voyage-3-large
|
||||
type: embedding
|
||||
max_input_tokens: 120000
|
||||
input_price: 0.18
|
||||
max_tokens_per_chunk: 32000
|
||||
default_chunk_size: 2000
|
||||
max_batch_size: 128
|
||||
- name: voyage-3
|
||||
type: embedding
|
||||
max_input_tokens: 320000
|
||||
input_price: 0.06
|
||||
max_tokens_per_chunk: 32000
|
||||
default_chunk_size: 2000
|
||||
max_batch_size: 128
|
||||
- name: voyage-3-lite
|
||||
type: embedding
|
||||
max_input_tokens: 1000000
|
||||
input_price: 0.02
|
||||
max_tokens_per_chunk: 32000
|
||||
default_chunk_size: 1000
|
||||
max_batch_size: 128
|
||||
- name: rerank-2
|
||||
type: reranker
|
||||
max_input_tokens: 16000
|
||||
input_price: 0.05
|
||||
- name: rerank-2-lite
|
||||
type: reranker
|
||||
max_input_tokens: 8000
|
||||
input_price: 0.02
|
||||
@@ -0,0 +1,327 @@
|
||||
use super::model::ModelData;
|
||||
use super::oauth::OAuthConfig;
|
||||
|
||||
use fancy_regex::Regex;
|
||||
use log::warn;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
pub const QUIRKS_YAML: &str = include_str!("../../quirks.yaml");
|
||||
|
||||
pub static ALL_QUIRKS: LazyLock<Vec<ProviderQuirks>> =
|
||||
LazyLock::new(|| serde_yaml::from_str(QUIRKS_YAML).expect("quirks.yaml must be valid"));
|
||||
|
||||
/// Hand-owned catalog overrides for a single provider: oauth configuration,
|
||||
/// curation excludes, field overlays for API-sourced models, suffixed model
|
||||
/// variants, and fully hand-owned model entries.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ProviderQuirks {
|
||||
pub provider: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub oauth: Option<OAuthConfig>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub exclude: Vec<String>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub rules: Vec<QuirkRule>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub variants: Vec<QuirkVariant>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub models: Vec<ModelData>,
|
||||
}
|
||||
|
||||
/// A field overlay applied to every API-sourced model whose name matches the
|
||||
/// glob in `match` (`*` = any run of characters, `?` = exactly one). Only
|
||||
/// explicitly present fields are written; absent fields leave the model
|
||||
/// untouched.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct QuirkRule {
|
||||
pub r#match: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub real_name: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub max_input_tokens: Option<usize>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub max_output_tokens: Option<isize>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub input_price: Option<f64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub output_price: Option<f64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub patch: Option<Value>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub require_max_tokens: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub supports_vision: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub supports_function_calling: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub reasoning_levels: Option<Vec<String>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub default_reasoning_effort: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub no_stream: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub no_system_message: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub system_prompt_prefix: Option<String>,
|
||||
}
|
||||
|
||||
/// Clones every model whose name matches the glob in `match`, appends `suffix`
|
||||
/// to the clone's name, and overlays any explicitly present fields onto the
|
||||
/// clone.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct QuirkVariant {
|
||||
pub r#match: String,
|
||||
pub suffix: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub real_name: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub max_input_tokens: Option<usize>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub max_output_tokens: Option<isize>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub input_price: Option<f64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub output_price: Option<f64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub patch: Option<Value>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub require_max_tokens: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub supports_vision: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub supports_function_calling: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub reasoning_levels: Option<Vec<String>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub default_reasoning_effort: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub no_stream: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub no_system_message: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub system_prompt_prefix: Option<String>,
|
||||
}
|
||||
|
||||
/// Applies every rule whose glob matches `data.name`, in file order; later
|
||||
/// rules overwrite earlier ones on the same field.
|
||||
#[allow(dead_code)]
|
||||
pub fn apply_rules(provider_quirks: &ProviderQuirks, data: &mut ModelData) {
|
||||
for rule in &provider_quirks.rules {
|
||||
if !glob_matches(&rule.r#match, &data.name) {
|
||||
continue;
|
||||
}
|
||||
if let Some(real_name) = &rule.real_name {
|
||||
data.real_name = Some(real_name.clone());
|
||||
}
|
||||
if let Some(max_input_tokens) = rule.max_input_tokens {
|
||||
data.max_input_tokens = Some(max_input_tokens);
|
||||
}
|
||||
if let Some(max_output_tokens) = rule.max_output_tokens {
|
||||
data.max_output_tokens = Some(max_output_tokens);
|
||||
}
|
||||
if let Some(input_price) = rule.input_price {
|
||||
data.input_price = Some(input_price);
|
||||
}
|
||||
if let Some(output_price) = rule.output_price {
|
||||
data.output_price = Some(output_price);
|
||||
}
|
||||
if let Some(patch) = &rule.patch {
|
||||
data.patch = Some(patch.clone());
|
||||
}
|
||||
if let Some(require_max_tokens) = rule.require_max_tokens {
|
||||
data.require_max_tokens = require_max_tokens;
|
||||
}
|
||||
if let Some(supports_vision) = rule.supports_vision {
|
||||
data.supports_vision = supports_vision;
|
||||
}
|
||||
if let Some(supports_function_calling) = rule.supports_function_calling {
|
||||
data.supports_function_calling = supports_function_calling;
|
||||
}
|
||||
if let Some(reasoning_levels) = &rule.reasoning_levels {
|
||||
data.reasoning_levels = reasoning_levels.clone();
|
||||
}
|
||||
if let Some(default_reasoning_effort) = &rule.default_reasoning_effort {
|
||||
data.default_reasoning_effort = Some(default_reasoning_effort.clone());
|
||||
}
|
||||
if let Some(no_stream) = rule.no_stream {
|
||||
data.no_stream = no_stream;
|
||||
}
|
||||
if let Some(no_system_message) = rule.no_system_message {
|
||||
data.no_system_message = no_system_message;
|
||||
}
|
||||
if let Some(system_prompt_prefix) = &rule.system_prompt_prefix {
|
||||
data.system_prompt_prefix = Some(system_prompt_prefix.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Finds the quirks for a client: an exact provider match wins, otherwise a
|
||||
/// client whose name starts with the provider name (openai-compatible clients
|
||||
/// are conventionally named after the provider they wrap).
|
||||
#[allow(dead_code)]
|
||||
pub fn quirks_for(client_name: &str) -> Option<&'static ProviderQuirks> {
|
||||
ALL_QUIRKS
|
||||
.iter()
|
||||
.find(|quirks| quirks.provider == client_name)
|
||||
.or_else(|| {
|
||||
ALL_QUIRKS
|
||||
.iter()
|
||||
.find(|quirks| client_name.starts_with(&quirks.provider))
|
||||
})
|
||||
}
|
||||
|
||||
fn glob_matches(pattern: &str, name: &str) -> bool {
|
||||
compile_glob(pattern).is_match(name).unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Translates a glob pattern (`*` = any run of characters, `?` = exactly one)
|
||||
/// into an anchored regex. Patterns that fail to compile match nothing.
|
||||
fn compile_glob(pattern: &str) -> Regex {
|
||||
let translated = format!(
|
||||
"^{}$",
|
||||
fancy_regex::escape(pattern)
|
||||
.replace("\\*", ".*")
|
||||
.replace("\\?", ".")
|
||||
);
|
||||
Regex::new(&translated).unwrap_or_else(|error| {
|
||||
warn!("Invalid model quirk pattern '{pattern}': {error}. It will match nothing.");
|
||||
Regex::new("(?!)").expect("'(?!)' is a valid never-matching regex")
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const KNOWN_PROVIDERS: [&str; 24] = [
|
||||
"openai",
|
||||
"gemini",
|
||||
"claude",
|
||||
"mistral",
|
||||
"ai21",
|
||||
"cohere",
|
||||
"xai",
|
||||
"perplexity",
|
||||
"groq",
|
||||
"vertexai",
|
||||
"bedrock",
|
||||
"cloudflare",
|
||||
"ernie",
|
||||
"qianwen",
|
||||
"hunyuan",
|
||||
"moonshot",
|
||||
"deepseek",
|
||||
"zhipuai",
|
||||
"minimax",
|
||||
"openrouter",
|
||||
"github",
|
||||
"deepinfra",
|
||||
"jina",
|
||||
"voyageai",
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn quirks_yaml_parses() {
|
||||
assert!(!ALL_QUIRKS.is_empty());
|
||||
for quirks in ALL_QUIRKS.iter() {
|
||||
assert!(
|
||||
KNOWN_PROVIDERS.contains(&quirks.provider.as_str()),
|
||||
"unknown provider '{}' in quirks.yaml",
|
||||
quirks.provider
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quirks_models_no_dropped_keys() {
|
||||
let skipped_when_default = [
|
||||
"type",
|
||||
"require_max_tokens",
|
||||
"supports_vision",
|
||||
"supports_function_calling",
|
||||
"reasoning_levels",
|
||||
"no_stream",
|
||||
"no_system_message",
|
||||
];
|
||||
let raw: serde_yaml::Value = serde_yaml::from_str(QUIRKS_YAML).unwrap();
|
||||
for provider in raw.as_sequence().unwrap() {
|
||||
let Some(models) = provider
|
||||
.get("models")
|
||||
.and_then(|models| models.as_sequence())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
for entry in models {
|
||||
let parsed: ModelData = serde_yaml::from_value(entry.clone()).unwrap();
|
||||
let reserialized = serde_yaml::to_value(&parsed).unwrap();
|
||||
let reserialized_keys: Vec<&str> = reserialized
|
||||
.as_mapping()
|
||||
.unwrap()
|
||||
.keys()
|
||||
.map(|key| key.as_str().unwrap())
|
||||
.collect();
|
||||
for key in entry.as_mapping().unwrap().keys() {
|
||||
let key = key.as_str().unwrap();
|
||||
assert!(
|
||||
reserialized_keys.contains(&key) || skipped_when_default.contains(&key),
|
||||
"key '{key}' on model '{}' was dropped by ModelData",
|
||||
parsed.name
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rules_apply_in_order() {
|
||||
let quirks: ProviderQuirks = serde_yaml::from_str(
|
||||
r#"
|
||||
provider: openai
|
||||
rules:
|
||||
- match: "model-*"
|
||||
default_reasoning_effort: low
|
||||
require_max_tokens: true
|
||||
- match: "model-a"
|
||||
default_reasoning_effort: high
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut matched_by_both = ModelData::new("model-a");
|
||||
apply_rules(&quirks, &mut matched_by_both);
|
||||
assert_eq!(
|
||||
matched_by_both.default_reasoning_effort.as_deref(),
|
||||
Some("high")
|
||||
);
|
||||
assert!(matched_by_both.require_max_tokens);
|
||||
|
||||
let mut matched_by_glob = ModelData::new("model-b");
|
||||
apply_rules(&quirks, &mut matched_by_glob);
|
||||
assert_eq!(
|
||||
matched_by_glob.default_reasoning_effort.as_deref(),
|
||||
Some("low")
|
||||
);
|
||||
|
||||
let mut unmatched = ModelData::new("other");
|
||||
apply_rules(&quirks, &mut unmatched);
|
||||
assert_eq!(unmatched.default_reasoning_effort, None);
|
||||
assert!(!unmatched.require_max_tokens);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn glob_semantics() {
|
||||
assert!(glob_matches("claude-*", "claude-opus-4-8"));
|
||||
assert!(!glob_matches("claude-*", "gpt-4"));
|
||||
assert!(glob_matches("claude-*", "claude-"));
|
||||
assert!(glob_matches("gpt-?", "gpt-4"));
|
||||
assert!(!glob_matches("gpt-?", "gpt-45"));
|
||||
assert!(!glob_matches("gpt-?", "gpt-"));
|
||||
assert!(glob_matches("o3", "o3"));
|
||||
assert!(!glob_matches("o3", "o3-mini"));
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
mod access_token;
|
||||
pub mod catalog;
|
||||
mod claude_oauth;
|
||||
mod common;
|
||||
mod gemini_oauth;
|
||||
|
||||
+3
-3
@@ -330,11 +330,11 @@ pub struct ModelData {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub default_reasoning_effort: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
no_stream: bool,
|
||||
pub(crate) no_stream: bool,
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
no_system_message: bool,
|
||||
pub(crate) no_system_message: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
system_prompt_prefix: Option<String>,
|
||||
pub(crate) system_prompt_prefix: Option<String>,
|
||||
|
||||
// embedding-only properties
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
|
||||
Reference in new issue
Block a user