Skip to content

Commit a952852

Browse files
wardpeetMastra Code (openai/gpt-5.5)
andauthored
Support gateway-owned auth resolution (#17608)
## Summary - Add an interface-first model gateway shape with optional gateway-owned `resolveAuth` hooks while preserving the existing `MastraModelGateway` base class path. - Route model auth through gateway `resolveAuth` before falling back to legacy `getApiKey` behavior. - Move MastraCode's gateway-routed OAuth/provider model construction behind a custom gateway implementation. - Add focused tests, minimal docs updates, and a changeset for `@mastra/core` and `mastracode`. ## Test plan - `pnpm build:core` - `pnpm --filter ./packages/core check` - `pnpm --filter ./packages/core test:unit src/llm/model/model-auth-resolver.test.ts src/llm/model/gateways/custom-gateway.test.ts` - `pnpm --filter ./mastracode check` - `pnpm --filter ./mastracode test src/agents/__tests__/model.test.ts` - `pnpm --dir docs exec remark src/content/en/models/gateways/custom-gateways.mdx src/content/en/reference/core/mastra-model-gateway.mdx` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## ELI5 Imagine you have different "gates" that route API requests to different services. Previously, each gate just asked for an API key using the same method. This PR lets each gate have its own "security desk" to handle authentication first—checking if it knows how to get the API key itself—before falling back to the standard process. This makes the system more flexible and lets gates handle authentication however they want. ## Summary This PR introduces **gateway-owned authentication resolution** for Mastra's model routing system, shifting from a single auth-resolution path to a three-tier flow: explicit credentials → gateway-specific `resolveAuth` hook → legacy `getApiKey` fallback. ### Core Feature: Gateway-Owned Auth Resolution **New Interface-Based Gateway Contract** - Introduces `MastraModelGatewayInterface` to support both class-based and plain object gateways - Adds optional `resolveAuth()` hook that runs before legacy `getApiKey` during model authentication - `MastraModelGateway` abstract class now implements the interface, maintaining backward compatibility - Adds `GatewayAuthRequest` (with `gatewayId`, `providerId`, `modelId`, `routerId`) and `GatewayAuthResult` (returning `apiKey`, `bearerToken`, `headers`, and `source`) **Three-Tier Auth Resolution in `ModelRouterLanguageModel`** - Centralizes auth logic into `resolveAuth()` method that threads `GatewayAuthResult` through request handling - First checks explicit credentials (passed directly); second checks `gateway.resolveAuth()`; falls back to `gateway.getApiKey()` if both are unavailable - Auth results include a `source` field tracking the resolution path (`'explicit'`, `'gateway'`, or `'legacy'`) - Deprecated standalone `resolveModelAuth` helper (though exported in new `model-auth-resolver.ts` module) ### MastraCode Gateway Refactoring **New `MastraCode Gateway` Abstraction** - Introduces `createMastraCodeGateway()` that implements `MastraModelGatewayInterface` for provider-specific auth - Exports `resolveAuth()` function that retrieves effective API keys using `AuthStorage` and optional memory gateway credentials - Encapsulates provider selection logic (OpenAI, Anthropic, GitHub Copilot, Moonshot) with per-provider API key resolution - Supports custom OpenAI-compatible providers and OAuth/API-key fallback chains - Simplifies `resolveModel` by delegating provider routing to the gateway's `resolveLanguageModel` method ### Gateway Infrastructure Updates **Utility Functions** - `getGatewayId(gateway)`: Extracts `id` from gateway (via optional `getId()` method or direct `id` property) - `shouldEnableGateway(gateway)`: Checks if gateway is enabled (via optional `shouldEnable()` method, defaults to `true`) - `serializeGatewayForSpan(gateway)`: Serializes gateway for tracing **Gateway Deduplication & Matching** - `findGatewayForModel()` and related registry functions now use `getGatewayId()` for consistent gateway identification - Fixes `defaultGateways` deduplication to use gateway IDs instead of registry keys - Extends gateway support in `Mastra` to accept plain object gateways via `addGateway()` and `listGateways()` ### Documentation & Types - Updated `custom-gateways.mdx` to recommend `MastraModelGatewayInterface` implementation pattern with plain object examples - Documented optional `resolveAuth()` hook with example showing `{ apiKey, source: 'gateway' }` return - Updated `MastraModelGateway` reference to label it as "Base class and interface" - Added TypeScript autocomplete guidance and rewritten "Best practices" section - Expanded public API re-exports to include `GatewayAuthRequest`, `GatewayAuthResult`, and gateway utility functions ### Tests & Validation - **Custom gateway tests**: New `custom-gateway.test.ts` verifies plain object gateways and `resolveAuth` precedence - **Model auth resolver tests**: `model-auth-resolver.test.ts` validates three-tier precedence (explicit → gateway → legacy) - **MastraCode tests**: Updated `model.test.ts` with mocks for `ModelRouterLanguageModel`, `resolveAuth`, and `resolveLanguageModel` to validate gateway delegation - **Regression fix**: `_llm-recorder` vite plugin regex anchored to path boundaries to fix directory suffix matching ### Breaking Changes / API Shifts - Gateway type parameters across `ModelRouterLanguageModel`, `GatewayRegistry`, `Mastra` config shifted from `MastraModelGateway` (class) to `MastraModelGatewayInterface` (interface) - Gateways now expected to implement optional `resolveAuth()` for authentication ownership; if absent, system falls back to legacy `getApiKey()` <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Mastra Code (openai/gpt-5.5) <noreply@mastra.ai>
1 parent 053735a commit a952852

18 files changed

Lines changed: 1088 additions & 361 deletions

File tree

.changeset/tall-dots-watch.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
---
2+
'@mastra/core': minor
3+
'mastracode': patch
4+
---
5+
6+
Added interface-first model gateways while keeping the existing `MastraModelGateway` base class backwards compatible.
7+
8+
Added `MastraModelGatewayInterface` for plain object/custom gateway implementations and optional gateway `resolveAuth` hooks.
9+
10+
Moved MastraCode gateway-routed OAuth model construction into a custom Mastra gateway so `ModelRouterLanguageModel` can route through gateway `resolveAuth` and provider-specific `resolveLanguageModel` behavior.
11+
12+
**Usage:**
13+
14+
```typescript
15+
import { MastraModelGatewayInterface, ModelRouterLanguageModel } from '@mastra/core/llm';
16+
17+
const myGateway: MastraModelGatewayInterface = {
18+
id: 'my-gateway',
19+
name: 'My Gateway',
20+
async fetchProviders() { return {}; },
21+
buildUrl() { return 'https://api.example.com'; },
22+
async getApiKey() { return process.env.API_KEY ?? ''; },
23+
// Optional: own authentication lookup
24+
async resolveAuth(request) {
25+
return { apiKey: process.env.API_KEY, source: 'gateway' };
26+
},
27+
async resolveLanguageModel({ modelId, providerId, apiKey }) {
28+
// Return an AI SDK language model instance
29+
},
30+
};
31+
32+
// Register and route through the gateway
33+
const router = new ModelRouterLanguageModel({ modelId: 'my-gateway/provider/model' }, [myGateway]);
34+
```
35+
36+
**Additional changes in this release:**
37+
38+
- Inline three-tier auth resolution (explicit → gateway.resolveAuth → legacy getApiKey) into `ModelRouterLanguageModel.resolveAuth` and deprecate the standalone `resolveModelAuth` helper.
39+
- Fix `defaultGateways` deduplication in the `Mastra` class to use `getGatewayId(gateway)` instead of registry keys.
40+
- Remove no-op `resolveModelId` identity function in mastracode in favor of direct usage.
41+
- Fix `defaultNameGenerator` regex in `_llm-recorder` to anchor directory matches to path boundaries (prevents false matches like `-auth` suffixes).

docs/src/content/en/models/gateways/custom-gateways.mdx

Lines changed: 67 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ description: "Create custom model gateways for private or specialized LLM deploy
55

66
# Custom model gateways
77

8-
Custom model gateways allow you to implement private or specialized LLM provider integrations by extending the `MastraModelGateway` base class.
8+
Custom model gateways allow you to implement private or specialized LLM provider integrations with the `MastraModelGatewayInterface` interface or the `MastraModelGateway` base class.
99

1010
## Overview
1111

@@ -25,7 +25,43 @@ Create custom gateways to support:
2525

2626
## Creating a Custom Gateway
2727

28-
Extend the `MastraModelGateway` class and implement the required methods:
28+
Implement `MastraModelGatewayInterface` for a plain object gateway, or extend `MastraModelGateway` when you want base class defaults.
29+
30+
```typescript
31+
import { type MastraModelGatewayInterface, type ProviderConfig } from '@mastra/core/llm';
32+
import { createOpenAICompatible } from '@ai-sdk/openai-compatible-v5';
33+
34+
const myPrivateGateway: MastraModelGatewayInterface = {
35+
id: 'private',
36+
name: 'My Private Gateway',
37+
async fetchProviders(): Promise<Record<string, ProviderConfig>> {
38+
return {
39+
'my-provider': {
40+
name: 'My Provider',
41+
models: ['__GATEWAY_OPENAI_MODEL__'],
42+
apiKeyEnvVar: 'MY_API_KEY',
43+
gateway: 'private',
44+
url: 'https://api.myprovider.com/v1',
45+
},
46+
};
47+
},
48+
buildUrl() {
49+
return 'https://api.myprovider.com/v1';
50+
},
51+
async getApiKey() {
52+
return process.env.MY_API_KEY ?? '';
53+
},
54+
async resolveLanguageModel({ modelId, providerId, apiKey }) {
55+
return createOpenAICompatible({
56+
name: providerId,
57+
apiKey,
58+
baseURL: 'https://api.myprovider.com/v1',
59+
}).chatModel(modelId);
60+
},
61+
};
62+
```
63+
64+
The following example extends the `MastraModelGateway` class:
2965

3066
```typescript
3167
import { MastraModelGateway, type ProviderConfig } from '@mastra/core/llm';
@@ -48,7 +84,7 @@ class MyPrivateGateway extends MastraModelGateway {
4884
return {
4985
'my-provider': {
5086
name: 'My Provider',
51-
models: ['model-1', 'model-2', 'model-3'],
87+
models: ['__GATEWAY_OPENAI_MODEL__', '__GATEWAY_ANTHROPIC_MODEL_SONNET__'],
5288
apiKeyEnvVar: 'MY_API_KEY',
5389
gateway: this.id,
5490
url: 'https://api.myprovider.com/v1',
@@ -102,6 +138,20 @@ class MyPrivateGateway extends MastraModelGateway {
102138
}
103139
```
104140

141+
### Gateway-owned authentication
142+
143+
Add `resolveAuth` when the gateway owns credential lookup. Mastra uses this hook before falling back to `getApiKey()`.
144+
145+
```typescript
146+
const myPrivateGateway: MastraModelGatewayInterface = {
147+
// ...gateway fields and methods
148+
async resolveAuth() {
149+
const apiKey = process.env.MY_API_KEY;
150+
return apiKey ? { apiKey, source: 'gateway' } : undefined;
151+
},
152+
};
153+
```
154+
105155
## Registering Custom Gateways
106156

107157
### During Initialization
@@ -145,7 +195,7 @@ const agent = new Agent({
145195
id: 'my-agent',
146196
name: 'My Agent',
147197
instructions: 'You are a helpful assistant',
148-
model: 'private/my-provider/model-1', // Uses MyPrivateGateway
198+
model: 'private/my-provider/__GATEWAY_OPENAI_MODEL__', // Uses MyPrivateGateway
149199
});
150200

151201
mastra.addAgent(agent, 'myAgent');
@@ -155,16 +205,16 @@ When you create an agent or use a model, Mastra's model router automatically sel
155205

156206
### TypeScript Autocomplete
157207

158-
**Automatic Type Generation in Development**
208+
#### Automatic type generation in development
159209

160-
When running in development mode (`MASTRA_DEV=true`), Mastra automatically generates TypeScript types for your custom gateways!
210+
When running in development mode (`MASTRA_DEV=true`), Mastra automatically generates TypeScript types for your custom gateways.
161211

162212
1. **Set the environment variable**:
163213
```bash
164214
export MASTRA_DEV=true
165215
```
166216

167-
2. **Register your gateways**:
217+
1. **Register your gateways**:
168218
```typescript
169219
const mastra = new Mastra({
170220
gateways: {
@@ -173,20 +223,20 @@ When running in development mode (`MASTRA_DEV=true`), Mastra automatically gener
173223
});
174224
```
175225

176-
3. **Types are generated automatically**:
226+
1. **Types are generated automatically**:
177227
- When you add a gateway, Mastra syncs with the GatewayRegistry
178228
- The registry fetches providers from your custom gateway
179229
- TypeScript types are regenerated in `~/.cache/mastra/`
180230
- Your IDE picks up the new types within seconds
181231

182-
4. **Autocomplete now works**:
232+
1. **Autocomplete now works**:
183233
```typescript
184234
const agent = new Agent({
185235
model: 'my-gateway-id/my-provider/model-1', // Full autocomplete!
186236
});
187237
```
188238

189-
**How It Works**
239+
#### How it works
190240

191241
The GatewayRegistry runs an hourly sync that:
192242
- Calls `fetchProviders()` on all registered gateways
@@ -517,19 +567,19 @@ describe('MyPrivateGateway', () => {
517567
readonly id = 'my-gateway-v1';
518568
```
519569

520-
2. **Implement proper error handling**: Throw descriptive errors with actionable messages
570+
1. **Implement proper error handling**: Throw descriptive errors with actionable messages
521571

522-
3. **Cache expensive operations**: Cache tokens, URLs, or provider configurations when appropriate
572+
1. **Cache expensive operations**: Cache tokens, URLs, or provider configurations when appropriate
523573

524-
4. **Validate environment variables**: Check for required environment variables in `getApiKey` and `buildUrl`
574+
1. **Validate environment variables**: Check for required environment variables in `getApiKey` and `buildUrl`
525575

526-
5. **Document your gateway**: Add JSDoc comments explaining the gateway's purpose and configuration
576+
1. **Document your gateway**: Add JSDoc comments explaining the gateway's purpose and configuration
527577

528-
6. **Follow naming conventions**: Use clear, consistent naming for providers and models
578+
1. **Follow naming conventions**: Use clear, consistent naming for providers and models
529579

530-
7. **Handle async operations**: Use `async/await` for network requests and I/O operations
580+
1. **Handle async operations**: Use `async/await` for network requests and I/O operations
531581

532-
8. **Test thoroughly**: Write unit tests for all gateway methods
582+
1. **Test thoroughly**: Write unit tests for all gateway methods
533583

534584
## Built-in Gateways
535585

docs/src/content/en/reference/core/mastra-model-gateway.mdx

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: "Reference: MastraModelGateway | Core"
3-
description: "Base class for creating custom model gateways"
3+
description: "Base class and interface for creating custom model gateways"
44
packages:
55
- "@mastra/core"
66
---
@@ -9,6 +9,8 @@ packages:
99

1010
Abstract base class for implementing custom model gateways. Gateways handle provider-specific logic for accessing language models, including provider configuration, authentication, URL construction, and model instantiation.
1111

12+
Use `MastraModelGatewayInterface` when you want to provide a plain object gateway instead of extending the base class.
13+
1214
## Class overview
1315

1416
```typescript
@@ -24,7 +26,7 @@ class MyCustomGateway extends MastraModelGateway {
2426
return {
2527
'my-provider': {
2628
name: 'My Provider',
27-
models: ['model-1', 'model-2'],
29+
models: ['__GATEWAY_OPENAI_MODEL__', '__GATEWAY_ANTHROPIC_MODEL_SONNET__'],
2830
apiKeyEnvVar: 'MY_API_KEY',
2931
gateway: this.id,
3032
},
@@ -175,6 +177,27 @@ Retrieves the API key for authentication.
175177

176178
**Returns:** `Promise<string>`
177179

180+
### `resolveAuth()`
181+
182+
Resolves credentials before Mastra creates the language model. Implement this optional hook when a gateway owns authentication. If omitted, Mastra falls back to `getApiKey()`.
183+
184+
**Parameters:**
185+
186+
<PropertiesTable
187+
content={[
188+
{
189+
name: 'request',
190+
type: 'GatewayAuthRequest',
191+
description:
192+
'Incoming request context containing gatewayId, providerId, modelId, and routerId for the gateway to inspect and validate.',
193+
},
194+
]}
195+
/>
196+
197+
**Returns:** `GatewayAuthResult | undefined | Promise<GatewayAuthResult | undefined>`
198+
199+
A `GatewayAuthResult` may include `apiKey`, `bearerToken`, `headers`, and an optional `source` field to trace where auth came from.
200+
178201
### `resolveLanguageModel()`
179202

180203
Creates a language model instance.

0 commit comments

Comments
 (0)