Skip to content

feat: model aliasing and fallbacks in api-proxy#2329

Merged
lpcox merged 6 commits into
mainfrom
copilot/add-model-syntax-configuration
May 1, 2026
Merged

feat: model aliasing and fallbacks in api-proxy#2329
lpcox merged 6 commits into
mainfrom
copilot/add-model-syntax-configuration

Conversation

Copilot AI commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

Agents using abstract model names (e.g. "sonnet", "") would get upstream errors when the requested model ID wasn't available. This adds a recursive alias resolution system to the api-proxy that rewrites the model field in request bodies before forwarding, using glob pattern matching and semver-sorted candidate selection.

Model ref syntax

Each alias maps to an ordered list of candidates. Candidates are either provider/modelpattern globs (matched against cached available models for the receiving provider) or other alias names (recursively expanded). The empty string key "" is the default policy applied when no model is specified.

# awf.yml
apiProxy:
  enabled: true
  models:
    sonnet:
      - "copilot/*sonnet*"
      - "anthropic/*sonnet*"
    gpt-5-codex:
      - "copilot/gpt-5*-codex"
      - "openai/gpt-5*-codex"
    "":           # default: no model specified → pick best available
      - "sonnet"
      - "gpt-5-codex"

Resolution: "" → sonnet → copilot/*sonnet* → [claude-sonnet-4.6, claude-sonnet-4.5] → claude-sonnet-4.6

Full resolution chain is logged at info level under event model_resolution / model_rewrite.

Changes

  • containers/api-proxy/model-resolver.js (new) — parseModelAliases, case-insensitive globMatch (* wildcard), compareByVersion (semver-style, highest first), resolveModel (recursive with loop detection), rewriteModelInBody
  • containers/api-proxy/model-resolver.test.js (new) — 49 tests
  • containers/api-proxy/server.js — loads AWF_MODEL_ALIASES at startup; makeModelBodyTransform(provider) wires the resolver into proxyRequest for POST/PUT/PATCH; updates content-length when body is rewritten; /reflect now includes model_aliases
  • src/config-file.ts — adds apiProxy.models: Record<string, string[]> to AwfFileConfig with validation
  • src/types.ts — adds modelAliases to WrapperConfig
  • src/docker-manager.ts — serialises modelAliases as AWF_MODEL_ALIASES JSON env var in the api-proxy container spec
  • src/cli.ts — plumbs modelAliases from config-file options into WrapperConfig

Key constraints

  • Each provider proxy only resolves within its own scope: copilot/* patterns are only evaluated on port 10002, openai/* on port 10000, etc.
  • Loop detection prevents infinite recursion in alias chains.
  • Resolution is best-effort: unresolvable aliases pass through unchanged (upstream handles the error).

Copilot AI linked an issue Apr 30, 2026 that may be closed by this pull request
Copilot AI added 2 commits April 30, 2026 23:22
- Add model-resolver.js: glob matching, semver sort, recursive alias
  resolution with loop detection
- Add model-resolver.test.js: 49 tests covering all resolution scenarios
- Modify server.js: intercept POST/PUT bodies, rewrite model field via
  alias map using makeModelBodyTransform() per-provider transforms
- Update /reflect endpoint to include model_aliases in response
- Add modelAliases to WrapperConfig in types.ts
- Add apiProxy.models section to AwfFileConfig in config-file.ts
- Add modelAliases to mapAwfFileConfigToCliOptions in config-file.ts
- Pass AWF_MODEL_ALIASES env var to api-proxy container in
  docker-manager.ts
- Add modelAliases to config construction in cli.ts
- Add proper JSDoc to proxyRequest documenting bodyTransform param
- Simplify redundant ternary in config-file.ts modelAliases mapping
- Extract modelAliases cast to a local variable in cli.ts

Agent-Logs-Url: https://github.com/github/gh-aw-firewall/sessions/ca3ee100-7aea-44c8-ad54-c375d07394c4
Copilot AI changed the title [WIP] Add model syntax for aliases and fallbacks in configuration feat: model aliasing and fallbacks in api-proxy Apr 30, 2026
Copilot AI requested a review from lpcox April 30, 2026 23:27
@github-actions

This comment has been minimized.

@lpcox lpcox marked this pull request as ready for review May 1, 2026 02:05
@lpcox lpcox requested a review from Mossaka as a code owner May 1, 2026 02:05
Copilot AI review requested due to automatic review settings May 1, 2026 02:05
@github-actions

github-actions Bot commented May 1, 2026

Copy link
Copy Markdown
Contributor

⚠️ Coverage Regression Detected

This PR decreases test coverage. Please add tests to maintain coverage levels.

Overall Coverage

Metric Base PR Delta
Lines 86.08% 86.02% 📉 -0.06%
Statements 86.08% 86.00% 📉 -0.08%
Functions 88.18% 87.93% 📉 -0.25%
Branches 80.32% 80.12% 📉 -0.20%
📁 Per-file Coverage Changes (3 files)
File Lines (Before → After) Statements (Before → After)
src/config-file.ts 100.0% → 96.8% (-3.15%) 99.5% → 96.5% (-2.98%)
src/cli.ts 58.9% → 58.8% (-0.09%) 59.4% → 59.3% (-0.08%)
src/docker-manager.ts 87.4% → 87.7% (+0.29%) 87.0% → 87.3% (+0.27%)

Coverage comparison generated by scripts/ci/compare-coverage.ts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds model aliasing + fallback resolution to the AWF api-proxy so agents can request abstract model names (including a default "" policy) and have requests rewritten to the best available concrete model ID per provider.

Changes:

  • Plumbs apiProxy.models from awf.yml into WrapperConfig.modelAliases, then into the api-proxy container via AWF_MODEL_ALIASES.
  • Adds a new model-resolver module with recursive alias expansion, glob matching, version-based candidate selection, and request-body rewriting.
  • Wires request-body rewriting into proxyRequest and exposes alias config via /reflect.
Show a summary per file
File Description
src/types.ts Documents new WrapperConfig.modelAliases option for api-proxy model rewriting.
src/docker-manager.ts Serializes alias map into AWF_MODEL_ALIASES env var for the api-proxy sidecar.
src/config-file.ts Adds apiProxy.models config + validation and maps it into CLI options.
src/cli.ts Plumbs modelAliases from parsed config options into WrapperConfig.
containers/api-proxy/server.js Loads aliases on startup, rewrites model in JSON bodies, updates content-length, and includes model_aliases in /reflect.
containers/api-proxy/model-resolver.js Implements alias parsing, glob matching, version comparison, recursive resolution, and JSON body rewriting.
containers/api-proxy/model-resolver.test.js Adds unit tests for parsing, matching, version sorting, resolution, and rewriting behavior.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 7/7 changed files
  • Comments generated: 6

Comment on lines +579 to +585
// Apply optional body transform (e.g. model alias rewriting) for mutating methods
if (bodyTransform && (req.method === 'POST' || req.method === 'PUT' || req.method === 'PATCH')) {
const transformed = bodyTransform(body);
if (transformed) {
body = transformed;
}
}
Comment on lines +576 to 578
let body = Buffer.concat(chunks);
const requestBytes = body.length;


// Update content-length when the body was rewritten (model alias substitution changes the size)
if (body.length !== requestBytes) {
headers['content-length'] = String(body.length);
Comment thread containers/api-proxy/model-resolver.js Outdated
// Build a regex from the glob pattern:
// - Escape all regex special chars except *
// - Replace * with .*
const regexStr = '^' + p.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*') + '$';
Comment thread src/types.ts Outdated
* Model alias map for the API proxy sidecar
*
* When enableApiProxy is true and model aliases are configured, the proxy
* intercepts POST/PUT request bodies containing a "model" field and rewrites
Comment thread src/types.ts Outdated
* {
* "sonnet": ["copilot/*sonnet*", "anthropic/*sonnet*"],
* "gpt-5-codex": ["copilot/gpt-5*-codex", "openai/gpt-5*-codex"],
* "": ["sonnet", "gpt-5*-codex"]
@lpcox

lpcox commented May 1, 2026

Copy link
Copy Markdown
Collaborator

@copilot address the review feedback

- Fix globMatch to escape ? and other regex metacharacters as literals
- Recompute requestBytes after body transform for accurate metrics/logs
- Strip transfer-encoding header when content-length is updated after rewrite
- Update types.ts doc to say POST/PUT/PATCH and fix gpt-5*-codex typo
- Add globMatch tests for ? and other metacharacter escaping
- Add integration-style tests in server.test.js for body rewriting

Agent-Logs-Url: https://github.com/github/gh-aw-firewall/sessions/76d3c1b2-6ef1-4f02-be0e-9ad3c6a049d5

Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>

Copilot AI commented May 1, 2026

Copy link
Copy Markdown
Contributor Author

@copilot address the review feedback

All six review items addressed in 9135680:

  1. globMatch metacharacter escaping — added ? to the escape set so it (and other regex metacharacters) match literally. Added test coverage for ?, ., and (...) patterns.
  2. requestBytes metric accuracy — renamed pre-transform size to inboundBytes and recomputed requestBytes = body.length after the transform, so metrics/logs reflect what was actually forwarded.
  3. transfer-encoding header conflict — added delete headers['transfer-encoding'] alongside the content-length update when the body was rewritten (RFC 7230 forbids sending both).
  4. Doc comment POST/PUTPOST/PUT/PATCH — updated in types.ts.
  5. gpt-5*-codex typo in example — fixed to gpt-5-codex to match the alias key defined above it.
  6. Integration-style tests — added makeModelBodyTransform integration describe block in server.test.js covering body rewrite, correct content-length/transfer-encoding fixup, forwarded byte count, and no-op case.

Copilot finished work on behalf of lpcox May 1, 2026 02:17
lpcox and others added 2 commits April 30, 2026 19:22
The new model-resolver module was required in server.js but not
included in the Dockerfile COPY step, causing the container to
crash on startup with 'Cannot find module ./model-resolver'.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The newer gh-aw compiler (v0.71.1) rejects sandbox.mcp.version in
strict mode. Remove it from smoke-opencode, smoke-services, and
smoke-gemini workflows and recompile to fix stale lock file errors.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

github-actions Bot commented May 1, 2026

Copy link
Copy Markdown
Contributor

⚠️ Coverage Regression Detected

This PR decreases test coverage. Please add tests to maintain coverage levels.

Overall Coverage

Metric Base PR Delta
Lines 86.08% 86.02% 📉 -0.06%
Statements 86.08% 86.00% 📉 -0.08%
Functions 88.18% 87.93% 📉 -0.25%
Branches 80.32% 80.12% 📉 -0.20%
📁 Per-file Coverage Changes (3 files)
File Lines (Before → After) Statements (Before → After)
src/config-file.ts 100.0% → 96.8% (-3.15%) 99.5% → 96.5% (-2.98%)
src/cli.ts 58.9% → 58.8% (-0.09%) 59.4% → 59.3% (-0.08%)
src/docker-manager.ts 87.4% → 87.7% (+0.29%) 87.0% → 87.3% (+0.27%)

Coverage comparison generated by scripts/ci/compare-coverage.ts

@github-actions

github-actions Bot commented May 1, 2026

Copy link
Copy Markdown
Contributor

Smoke Test: Copilot BYOK (Offline Mode) — PR #2329 by @Copilot, assigned to @lpcox and @Copilot

Test Result
GitHub MCP connectivity
GitHub.com HTTP ⚠️ (pre-step vars not expanded)
File write/read ⚠️ (pre-step vars not expanded)
BYOK inference (this response)

Running in BYOK offline mode (COPILOT_OFFLINE=true) via api-proxy → api.githubcopilot.com

Overall: PARTIAL PASS — BYOK inference and MCP confirmed working; pre-step template variables were not expanded so HTTP/file tests could not be fully validated.

🔑 BYOK report filed by Smoke Copilot BYOK

@github-actions

github-actions Bot commented May 1, 2026

Copy link
Copy Markdown
Contributor

Smoke Test Results

✅ GitHub MCP: fix: per-issue conclusion concurrency in issue-duplication-detector
✅ GitHub MCP: [docs] docs: sync authentication-architecture.md for 5-provider api-proxy
✅ Playwright: https://github.com loaded successfully
✅ File Writing: /tmp/gh-aw/agent/smoke-test-claude-25199258951.txt created
✅ Bash Tool: File verified with cat

Overall: PASS

💥 [THE END] — Illustrated by Smoke Claude

@github-actions

github-actions Bot commented May 1, 2026

Copy link
Copy Markdown
Contributor

🔥 Smoke Test Results

Test Status
GitHub MCP connectivity
GitHub.com HTTP ✅ 200
File write/read ⚠️ pre-step vars unexpanded

PR: feat: model aliasing and fallbacks in api-proxy
Author: @Copilot | Assignees: @lpcox @Copilot

Overall: PASS (2/2 verified tests passed; file test skipped due to unexpanded template vars)

📰 BREAKING: Report filed by Smoke Copilot

@github-actions

github-actions Bot commented May 1, 2026

Copy link
Copy Markdown
Contributor

Chroot Version Comparison Results

Runtime Host Version Chroot Version Match?
Python Python 3.12.13 Python 3.12.3
Node.js v24.14.1 v20.20.2
Go go1.22.12 go1.22.12

Result: ❌ Tests did not fully pass — Python and Node.js versions differ between host and chroot environments.

Tested by Smoke Chroot

@github-actions github-actions Bot mentioned this pull request May 1, 2026
@github-actions

github-actions Bot commented May 1, 2026

Copy link
Copy Markdown
Contributor

fix: per-issue conclusion concurrency in issue-duplication-detector
[docs] docs: sync authentication-architecture.md for 5-provider api-proxy
GitHub MCP review: ❌
safeinputs-gh query: ❌
Playwright title: ✅
Tavily search: ❌
File + bash + build + discussion: ✅
Overall: FAIL

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • registry.npmjs.org

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "registry.npmjs.org"

See Network Configuration for more information.

🔮 The oracle has spoken through Smoke Codex

@github-actions

github-actions Bot commented May 1, 2026

Copy link
Copy Markdown
Contributor

🏗️ Build Test Suite Results

Ecosystem Project Build/Install Tests Status
Bun elysia 1/1 passed ✅ PASS
Bun hono 1/1 passed ✅ PASS
C++ fmt N/A ✅ PASS
C++ json N/A ✅ PASS
Deno oak N/A 1/1 passed ✅ PASS
Deno std N/A 1/1 passed ✅ PASS
.NET hello-world N/A ✅ PASS
.NET json-parse N/A ✅ PASS
Go color 1/1 passed ✅ PASS
Go env 1/1 passed ✅ PASS
Go uuid 1/1 passed ✅ PASS
Java gson 1/1 passed ✅ PASS
Java caffeine 1/1 passed ✅ PASS
Node.js clsx All passed ✅ PASS
Node.js execa All passed ✅ PASS
Node.js p-limit All passed ✅ PASS
Rust fd 1/1 passed ✅ PASS
Rust zoxide 1/1 passed ✅ PASS

Overall: 8/8 ecosystems passed — ✅ PASS

Generated by Build Test Suite for issue #2329 · ● 460K ·

@github-actions

github-actions Bot commented May 1, 2026

Copy link
Copy Markdown
Contributor

Smoke Test: GitHub Actions Services Connectivity

Check Result
Redis PING ❌ timeout (no response)
PostgreSQL pg_isready ❌ no response
PostgreSQL SELECT 1 ❌ timeout

Overall: FAILhost.docker.internal is not reachable from this runner environment. Service containers may not be running or the hostname is not resolvable.

🔌 Service connectivity validated by Smoke Services

@lpcox lpcox merged commit 7060515 into main May 1, 2026
65 of 69 checks passed
@lpcox lpcox deleted the copilot/add-model-syntax-configuration branch May 1, 2026 02:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Model syntax

3 participants