Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/model-api-mapping-updater.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions .github/workflows/model-api-mapping-updater.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@ on:
workflow_dispatch:
permissions:
contents: read
pull-requests: read
issues: read
tools:
github:
mode: gh-proxy
toolsets: [repos]
bash: true
edit:
network:
allowed:
- defaults
Expand Down
3 changes: 2 additions & 1 deletion containers/api-proxy/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ COPY server.js logging.js metrics.js rate-limiter.js rate-limiter-window.js \
upstream-log.js upstream-retry.js upstream-token.js \
anthropic-cache.js otel.js otel-exporters.js otel-serialization.js \
token-budget-log.js blocked-request-diagnostics.js \
provider-env-constants.js provider-names.js ./
provider-env-constants.js provider-names.js \
model-api-mapping.js model-api-mapping.json ./
COPY guards/ ./guards/
COPY providers/ ./providers/
COPY transforms/ ./transforms/
Expand Down
2 changes: 2 additions & 0 deletions containers/api-proxy/management.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

const metrics = require('./metrics');
const { getModelApiMappingReflect } = require('./model-api-mapping');

/**
* @typedef {object} ManagementDeps
Expand Down Expand Up @@ -109,6 +110,7 @@ function createManagementHandlers(deps) {
runs: getMaxRunsUsage(),
cache_misses: getMaxCacheMissesUsage(),
permission_denied: getPermissionDeniedUsage(),
model_api_mapping: getModelApiMappingReflect(),
};
}

Expand Down
145 changes: 145 additions & 0 deletions containers/api-proxy/model-api-mapping.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
'use strict';

/**
* AWF API Proxy — Model-to-API Endpoint Mapping
*
* Loads the model-api-mapping.json reference file and exposes it for
* the /reflect management endpoint. This mapping documents which API
* endpoints each model family supports (e.g. responses-only vs
* chat/completions vs both).
*
* The mapping is informational — it does not alter proxy routing behavior.
* Consumers (e.g. SDK drivers, harness scripts) can query /reflect to
* determine the correct endpoint for a given model.
*/

const fs = require('fs');
const path = require('path');

/**
* Paths to search for the mapping file, in priority order.
* The first path that exists wins.
*/
const MAPPING_FILE_SEARCH_PATHS = [
// Injected via env var (e.g. in CI or Docker)
process.env.AWF_MODEL_API_MAPPING_PATH,
// Relative to the api-proxy container directory
path.join(__dirname, 'model-api-mapping.json'),
// Relative to the repo root (local dev)
path.join(__dirname, '../../docs/model-api-mapping.json'),
];

let _mapping = null;
let _loadError = null;

/**
* Attempt to load the model-api-mapping.json from the search paths.
* Called once at module load time. Safe to call again to reload.
*/
function loadMapping() {
for (const filePath of MAPPING_FILE_SEARCH_PATHS) {
if (!filePath) continue;
try {
if (fs.existsSync(filePath)) {
const raw = fs.readFileSync(filePath, 'utf8');
_mapping = JSON.parse(raw);
_loadError = null;
return;
}
} catch (err) {
_loadError = err.message;
}
}
// Not found in any search path — this is fine, mapping is optional
_mapping = null;
if (!_loadError) {
_loadError = 'model-api-mapping.json not found in search paths';
}
}

// Load on first require
loadMapping();

/**
* Get the loaded mapping object, or null if not available.
* @returns {object|null}
*/
function getModelApiMapping() {
return _mapping;
}

/**
* Look up the supported endpoints for a given model string.
* Returns the matching entry or null if no match is found.
*
* @param {string} model - Model identifier (e.g. "gpt-5.5", "claude-sonnet-4-6")
* @param {string} [provider] - Optional provider hint ("openai" or "anthropic")
* @returns {{ family: string, endpoints: string[], notes: string } | null}
*/
function lookupModelEndpoints(model, provider) {
if (!_mapping || !model) return null;

const providers = provider
? [_mapping.providers[provider]].filter(Boolean)
: Object.values(_mapping.providers || {});

for (const prov of providers) {
if (!prov || !Array.isArray(prov.models)) continue;
for (const entry of prov.models) {
if (!entry.patterns) continue;
for (const pattern of entry.patterns) {
if (matchesGlobPattern(model, pattern)) {
return {
family: entry.family,
endpoints: entry.endpoints,
notes: entry.notes || '',
};
}
}
}
}
return null;
}

/**
* Simple glob matching: supports trailing `*` wildcard only.
* @param {string} value
* @param {string} pattern
* @returns {boolean}
*/
function matchesGlobPattern(value, pattern) {
if (pattern.endsWith('*')) {
return value.startsWith(pattern.slice(0, -1));
}
return value === pattern;
}

/**
* Get the reflect-friendly summary for inclusion in /reflect response.
* @returns {{ available: boolean, last_updated: string|null, providers: string[], error: string|null }}
*/
function getModelApiMappingReflect() {
if (!_mapping) {
return {
available: false,
last_updated: null,
providers: [],
error: _loadError,
};
}
return {
available: true,
last_updated: _mapping.lastUpdated || null,
providers: Object.keys(_mapping.providers || {}),
models: _mapping.providers,
error: null,
};
}

module.exports = {
getModelApiMapping,
getModelApiMappingReflect,
lookupModelEndpoints,
loadMapping,
matchesGlobPattern,
};
155 changes: 155 additions & 0 deletions containers/api-proxy/model-api-mapping.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
{
"title": "Model-to-API Endpoint Mapping",
"description": "Maps AI model families to their supported API endpoints. Used to determine which endpoint (chat/completions vs responses vs messages) a model requires.",
"lastUpdated": "2026-06-30T04:00:00Z",
"sources": {
"openai": "https://platform.openai.com/docs/models",
"anthropic": "https://docs.anthropic.com/en/docs/about-claude/models"
},
"providers": {
"openai": {
"endpoints": {
"responses": {
"path": "/v1/responses",
"description": "Newer unified API for agentic, tool-using, and multimodal workflows. Required for GPT-5+ models; some older models (o1, o3, gpt-4o, gpt-4.1) support it alongside chat/completions."
},
"chat_completions": {
"path": "/v1/chat/completions",
"description": "Legacy conversational API. Being deprecated for newer models by end of 2026."
}
},
"models": [
{
"family": "gpt-5.5",
"patterns": ["gpt-5.5*"],
"endpoints": ["responses"],
"notes": "Responses API only. Not accessible via /chat/completions."
},
{
"family": "gpt-5.4",
"patterns": ["gpt-5.4*"],
"endpoints": ["responses"],
"notes": "Responses API only."
},
{
"family": "gpt-5.3",
"patterns": ["gpt-5.3*"],
"endpoints": ["responses"],
"notes": "Responses API only. Includes gpt-5.3-codex variants."
},
{
"family": "gpt-5.2",
"patterns": ["gpt-5.2*"],
"endpoints": ["responses"],
"notes": "Responses API only."
},
{
"family": "gpt-5",
"patterns": ["gpt-5", "gpt-5-*"],
"endpoints": ["responses"],
"notes": "Responses API only. Includes gpt-5-mini, gpt-5-nano, gpt-5-pro."
},
{
"family": "o4",
"patterns": ["o4*"],
"endpoints": ["responses"],
"notes": "Reasoning model. Responses API only. Includes o4-mini."
},
{
"family": "o3",
"patterns": ["o3*"],
"endpoints": ["chat_completions", "responses"],
"notes": "Reasoning model. Supports both endpoints."
},
{
"family": "o1",
"patterns": ["o1*"],
"endpoints": ["chat_completions", "responses"],
"notes": "Reasoning model. Supports both endpoints."
},
{
"family": "gpt-4o",
"patterns": ["gpt-4o*"],
"endpoints": ["chat_completions", "responses"],
"notes": "Multimodal. Supports both endpoints."
},
{
"family": "gpt-4.1",
"patterns": ["gpt-4.1*"],
"endpoints": ["chat_completions", "responses"],
"notes": "Supports both endpoints."
},
{
"family": "gpt-4-turbo",
"patterns": ["gpt-4-turbo*"],
"endpoints": ["chat_completions"],
"notes": "Legacy. Chat completions only."
},
{
"family": "gpt-4",
"patterns": ["gpt-4", "gpt-4-*"],
"endpoints": ["chat_completions"],
"notes": "Legacy. Chat completions only."
},
{
"family": "gpt-3.5-turbo",
"patterns": ["gpt-3.5-turbo*"],
"endpoints": ["chat_completions"],
"notes": "Legacy. Chat completions only. Deprecated."
}
]
},
"anthropic": {
"endpoints": {
"messages": {
"path": "/v1/messages",
"description": "Unified Messages API for all Claude models. Supports tool use, streaming, extended thinking, and vision."
}
},
"models": [
{
"family": "claude-fable-5",
"patterns": ["claude-fable-5*"],
"endpoints": ["messages"],
"notes": "Latest flagship. 1M token context."
},
{
"family": "claude-opus-4",
"patterns": ["claude-opus-4*"],
"endpoints": ["messages"],
"notes": "Includes claude-opus-4-8, claude-opus-4-7, claude-opus-4-6, claude-opus-4-5. 1M token context."
},
{
"family": "claude-sonnet-4",
"patterns": ["claude-sonnet-4*"],
"endpoints": ["messages"],
"notes": "Includes claude-sonnet-4-6, claude-sonnet-4-5. Production workhorse. 1M token context."
},
{
"family": "claude-haiku-4",
"patterns": ["claude-haiku-4*"],
"endpoints": ["messages"],
"notes": "Includes claude-haiku-4-5. Fast/cheap. 200K token context."
},
{
"family": "claude-3.5-sonnet",
"patterns": ["claude-3-5-sonnet*", "claude-3.5-sonnet*"],
"endpoints": ["messages"],
"notes": "Previous generation. 200K token context."
},
{
"family": "claude-3.5-haiku",
"patterns": ["claude-3-5-haiku*", "claude-3.5-haiku*"],
"endpoints": ["messages"],
"notes": "Previous generation fast model."
},
{
"family": "claude-3-opus",
"patterns": ["claude-3-opus*"],
"endpoints": ["messages"],
"notes": "Previous generation. 200K token context."
}
]
}
}
}
Loading
Loading