
Models Config
- 16 installs
- 17 repo stars
- Updated July 25, 2026
- dwsy/agent
Manages AI model/provider configuration, tests API connections, and keeps pricing data current for many LLM providers.
About
A CLI skill that manages AI model configuration in a Pi agent's models.json: adding providers and models, testing API connectivity including reasoning and streaming, and keeping pricing data current across OpenAI, Anthropic, Gemini, Mistral, and Azure. A solo builder reaches for it to wire up and sanity-check multiple LLM backends for their agent.
- Add providers and models to Pi agent config
- Test connectivity, reasoning and streaming
- Auto-update model pricing data
Models Config by the numbers
- 16 all-time installs (skills.sh)
- Ranked #10,994 of 16,556 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Jul 29, 2026 (Skillselion catalog sync)
npx skills add https://github.com/dwsy/agent --skill models-configAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 16 |
|---|---|
| repo stars | ★ 17 |
| Last updated | July 25, 2026 |
| Repository | dwsy/agent ↗ |
What it does
Manages AI model/provider configuration, tests API connections, and keeps pricing data current for many LLM providers.
Who is it for?
Builders wiring multiple LLM providers into an agent
Skip if: Single-model, hardcoded setups
Files
Models Config Skill
功能
编辑和管理 ~/.pi/agent/models.json 配置文件,支持多种 API 协议的测试和验证,以及从 https://models.dev/api.json 自动获取模型价格信息。
配置结构
{
"providers": {
"provider-name": {
"baseUrl": "https://api.example.com",
"apiKey": "sk-xxx",
"api": "anthropic-messages|openai-completions|openai-responses",
"authHeader": true
}
}
}使用方法
基本操作
# 编辑配置文件
bat ~/.pi/agent/models.json
# 验证 JSON 格式
python3 -m json.tool ~/.pi/agent/models.json协议类型
| API 类型 | 用途 | 端点格式 |
|---|---|---|
anthropic-messages | Claude 消息 API | /v1/messages |
openai-completions | OpenAI Completions API | /v1/chat/completions |
openai-responses | OpenAI Responses API | /v1/responses |
测试方法
1. Anthropic Messages API
export ANTHROPIC_BASE_URL=https://api.xairouter.com
export ANTHROPIC_AUTH_TOKEN=sk-XvsJhNdiXcDYA3e5hzD1AJP5ploMAaFuMTUxp3bHRfCiZRNt
curl $ANTHROPIC_BASE_URL/v1/messages \
-H "x-api-key: $ANTHROPIC_AUTH_TOKEN" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{
"model": "claude-sonnet-4-5",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Hello"}]
}'2. OpenAI Completions API (Chat)
export OPENAI_BASE_URL=http://127.0.0.1:8317/v1
export OPENAI_API_KEY=proxypal-local
curl $OPENAI_BASE_URL/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "content-type: application/json" \
-d '{
"model": "glm-4.7",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Hello"}]
}'3. OpenAI Responses API
export OPENAI_BASE_URL=http://127.0.0.1:8317/v1
export OPENAI_API_KEY=proxypal-local
curl $OPENAI_BASE_URL/responses \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "content-type: application/json" \
-d '{
"model": "glm-4.7",
"input": "Hello"
}'4. 简化测试脚本
#!/usr/bin/env bash
# test-model.sh
PROVIDER=$1
BASE_URL=$2
API_KEY=$3
MODEL=$4
echo "Testing $PROVIDER with model $MODEL..."
case "$PROVIDER" in
anthropic)
curl -s "$BASE_URL/v1/messages" \
-H "x-api-key: $API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d "{\"model\":\"$MODEL\",\"max_tokens\":256,\"messages\":[{\"role\":\"user\",\"content\":\"Hi\"}]}" \
| jq .
;;
openai-chat)
curl -s "$BASE_URL/chat/completions" \
-H "Authorization: Bearer $API_KEY" \
-H "content-type: application/json" \
-d "{\"model\":\"$MODEL\",\"max_tokens\":256,\"messages\":[{\"role\":\"user\",\"content\":\"Hi\"}]}" \
| jq .
;;
openai-responses)
curl -s "$BASE_URL/responses" \
-H "Authorization: Bearer $API_KEY" \
-H "content-type: application/json" \
-d "{\"model\":\"$MODEL\",\"input\":\"Hi\"}" \
| jq .
;;
esac使用示例:
# 测试 Anthropic
bash test-model.sh anthropic \
https://api.xairouter.com \
sk-XvsJhNdiXcDYA3e5hzD1AJP5ploMAaFuMTUxp3bHRfCiZRNt \
claude-sonnet-4-5
# 测试 OpenAI Chat
bash test-model.sh openai-chat \
http://127.0.0.1:8317/v1 \
proxypal-local \
glm-4.7常见配置
本地服务 (ProxyPal)
{
"proxypal": {
"baseUrl": "http://127.0.0.1:8317/v1",
"apiKey": "proxypal-local",
"api": "openai-completions",
"authHeader": true
}
}Cloud 服务 (xAIRouter)
{
"xairouter": {
"baseUrl": "https://api.xairouter.com",
"apiKey": "sk-xxx",
"api": "anthropic-messages",
"authHeader": true
}
}Ngrok 隧道
{
"ngrok": {
"baseUrl": "https://xxx.ngrok-free.dev/v1",
"apiKey": "proxypal-local",
"api": "openai-responses",
"authHeader": true
}
}价格更新功能
从 https://models.dev/api.json 获取模型价格并更新到配置文件。
使用方法
# 更新所有模型价格
bun ~/.pi/agent/skills/models-config/update-prices.ts工作原理
1. 从 https://models.dev/api.json 获取最新价格数据 2. 读取 ~/.pi/agent/models.json 配置文件 3. 按 model ID 智能匹配价格信息 4. 更新 cost 字段(input/output/cacheRead/cacheWrite) 5. 显示更新摘要
匹配规则
优先级(从高到低):
1. 精确匹配:model ID 完全相同 2. 标准化匹配:去除前缀、版本号后相同
anthropic/claude-sonnet-4-5→claude-sonnet-4-5claude-sonnet-4-5-20250929→claude-sonnet-4-5
3. 模糊匹配:基于 Levenshtein 距离的相似度匹配(≥70%)
- 自动匹配最佳相似度的模型
- 显示匹配相似度百分比
支持的别名映射:
| 你的模型 ID | 匹配到 |
|---|---|
opus4.5 | claude-opus-4-5 |
claude-sonnet-4-5-20250929 | claude-sonnet-4-5 |
claude-haiku-4-5-20251001 | claude-haiku-4-5 |
z-ai/glm4.7 | glm-4.7 |
minimaxai/minimax-m2.1 | minimax-m2.1 |
模糊匹配示例
即使你的供应商不在 models.dev 中,只要模型名称相似,也会自动匹配:
bun ~/.pi/agent/skills/models-config/update-prices.ts
# 输出示例:
✓ Updated: claude-sonnet-4-5
Old: {"input":0,"output":0,"cacheRead":0,"cacheWrite":0}
New: {"input":2.6,"output":13,"cacheRead":0.26,"cacheWrite":3.2}
✓ Updated: glm-4.7
Fuzzy matched "glm-4.7" -> "zai-glm-4.7" (85.7% similarity)
Old: {"input":0,"output":0,"cacheRead":0,"cacheWrite":0}
New: {"input":0,"output":0,"cacheRead":0,"cacheWrite":0}
=== Summary ===
Updated: 15 models
Not found: 3 models
Models without price data:
- custom-model-x
- experimental-beta注意事项
1. baseUrl 格式:
anthropic-messages: 不需要/v1后缀openai-*: 通常需要/v1后缀
2. 认证方式:
- Anthropic:
x-api-keyheader - OpenAI:
Authorization: Bearerheader
3. 测试前检查:
- 确认服务端口已启动(如
curl http://127.0.0.1:8317) - 检查 API Key 有效性
- 验证网络连通性
4. 价格更新:
- 模糊匹配阈值:70% 相似度
- 匹配成功会显示相似度百分比
- 未匹配的模型会在摘要中列出
- 价格单位:美元/百万 tokens ($/1M tokens)
#!/usr/bin/env bun
import { readFile, writeFile } from 'node:fs/promises'
const API_URL = 'https://models.dev/api.json'
const MODELS_PATH = `${process.env.HOME}/.pi/agent/models.json`
interface Cost {
input: number
output: number
cache_read?: number
cache_write?: number
}
interface ModelConfig {
id: string
name: string
cost?: Cost
[key: string]: any
}
interface ProviderConfig {
baseUrl: string
apiKey: string
api: string
authHeader: boolean
models: ModelConfig[]
}
interface ModelsJson {
providers: Record<string, ProviderConfig>
}
interface ApiModel {
id: string
name: string
cost?: Cost
[key: string]: any
}
interface ApiProvider {
id: string
models: Record<string, ApiModel>
}
interface ApiResponse {
[providerId: string]: ApiProvider
}
async function fetchPrices(): Promise<ApiResponse> {
console.log(`Fetching prices from ${API_URL}...`)
const response = await fetch(API_URL)
if (!response.ok) {
throw new Error(`Failed to fetch API: ${response.statusText}`)
}
return await response.json()
}
function calculateSimilarity(str1: string, str2: string): number {
// Levenshtein distance-based similarity
const len1 = str1.length
const len2 = str2.length
const matrix: number[][] = []
for (let i = 0; i <= len1; i++) {
matrix[i] = [i]
}
for (let j = 0; j <= len2; j++) {
matrix[0][j] = j
}
for (let i = 1; i <= len1; i++) {
for (let j = 1; j <= len2; j++) {
const cost = str1[i - 1] === str2[j - 1] ? 0 : 1
matrix[i][j] = Math.min(
matrix[i - 1][j] + 1,
matrix[i][j - 1] + 1,
matrix[i - 1][j - 1] + cost
)
}
}
const distance = matrix[len1][len2]
return 1 - distance / Math.max(len1, len2)
}
function normalizeModelId(id: string): string {
// Normalize model ID for matching
return id
.toLowerCase()
.replace(/_/g, '-') // Replace underscores with hyphens
.replace(/anthropic\/claude-/, 'claude-')
.replace(/openai\//, '')
.replace(/google\//, '')
.replace(/^z-ai\//, '')
.replace(/^minimaxai\//, '')
.replace(/^claude-opus-4-5-20251101$/, 'claude-opus-4-5')
.replace(/^claude-sonnet-4-5-20250929$/, 'claude-sonnet-4-5')
.replace(/^claude-haiku-4-5-20251001$/, 'claude-haiku-4-5')
.replace(/^opus4\.5$/, 'claude-opus-4-5')
.replace(/v1$/, '') // Remove version suffix
.replace(/-preview$/, '') // Remove preview suffix
.replace(/-turbo$/, '') // Remove turbo suffix
}
function findModelPrice(apiData: ApiResponse, modelId: string): Cost | null {
const normalizedId = normalizeModelId(modelId)
const SIMILARITY_THRESHOLD = 0.7 // 70% similarity required
let bestMatch: { cost: Cost; similarity: number; apiId: string; hasPrice: boolean } | null = null
// Search through all providers and models
for (const providerId in apiData) {
const provider = apiData[providerId]
if (!provider?.models) continue
for (const apiModelId in provider.models) {
const apiModel = provider.models[apiModelId]
if (!apiModel?.cost) continue
const normalizedApiId = normalizeModelId(apiModelId)
// Extract base model names (remove provider prefixes, versions, dates)
const baseTarget = normalizedId.replace(/-\d{8}$/, '').replace(/-\d+\.\d+$/, '')
const baseApi = normalizedApiId.replace(/-\d{8}$/, '').replace(/-\d+\.\d+$/, '')
// Check if one contains the other (substring match) or exact match
const isExactMatch = normalizedApiId === normalizedId
const isSubstringMatch = normalizedId.includes(baseApi) || normalizedApiId.includes(baseTarget)
if (isExactMatch || isSubstringMatch) {
const similarity = isExactMatch
? 1.0
: Math.max(
calculateSimilarity(normalizedId, normalizedApiId),
calculateSimilarity(baseTarget, baseApi)
)
if (similarity >= SIMILARITY_THRESHOLD) {
const hasPrice = apiModel.cost.input > 0 || apiModel.cost.output > 0
// Prefer matches with non-zero prices
if (!bestMatch) {
bestMatch = { cost: apiModel.cost, similarity, apiId: apiModelId, hasPrice }
} else {
// Prefer non-zero prices over zero prices
if (hasPrice && !bestMatch.hasPrice) {
bestMatch = { cost: apiModel.cost, similarity, apiId: apiModelId, hasPrice }
} else if (hasPrice === bestMatch.hasPrice) {
// Same price status, prefer higher similarity (exact match wins)
if (similarity > bestMatch.similarity) {
bestMatch = { cost: apiModel.cost, similarity, apiId: apiModelId, hasPrice }
}
}
}
}
}
}
}
// Return best match if found
if (bestMatch && bestMatch.similarity >= SIMILARITY_THRESHOLD) {
const priceNote = bestMatch.hasPrice ? '' : ' (free model)'
const matchNote = bestMatch.similarity === 1.0 ? 'Exact match' : `Fuzzy matched`
console.log(` ${matchNote}: "${modelId}" -> "${bestMatch.apiId}" (${(bestMatch.similarity * 100).toFixed(1)}% similarity${priceNote})`)
return bestMatch.cost
}
return null
}
async function updatePrices() {
try {
// Fetch API data
const apiData = await fetchPrices()
// Read current models.json
const modelsContent = await readFile(MODELS_PATH, 'utf-8')
const modelsJson: ModelsJson = JSON.parse(modelsContent)
let updatedCount = 0
let notFoundCount = 0
const notFoundModels: string[] = []
// Update prices for each provider
for (const providerId in modelsJson.providers) {
const provider = modelsJson.providers[providerId]
if (!provider?.models) continue
for (const model of provider.models) {
const price = findModelPrice(apiData, model.id)
if (price) {
const oldCost = model.cost
model.cost = {
input: price.input,
output: price.output,
cacheRead: price.cache_read ?? 0,
cacheWrite: price.cache_write ?? 0
}
if (JSON.stringify(oldCost) !== JSON.stringify(model.cost)) {
console.log(`✓ Updated: ${model.id}`)
console.log(` Old: ${JSON.stringify(oldCost)}`)
console.log(` New: ${JSON.stringify(model.cost)}`)
updatedCount++
}
} else {
notFoundCount++
if (!notFoundModels.includes(model.id)) {
notFoundModels.push(model.id)
}
}
}
}
// Write back to models.json
await writeFile(MODELS_PATH, JSON.stringify(modelsJson, null, 2), 'utf-8')
console.log(`\n=== Summary ===`)
console.log(`Updated: ${updatedCount} models`)
console.log(`Not found: ${notFoundCount} models`)
if (notFoundModels.length > 0) {
console.log(`\nModels without price data:`)
notFoundModels.forEach(m => console.log(` - ${m}`))
}
console.log(`\n✓ Prices updated successfully!`)
} catch (error) {
console.error(`Error: ${error instanceof Error ? error.message : error}`)
process.exit(1)
}
}
// Run
updatePrices()