@mastra/core 0.20.0 → 0.20.1-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +46 -0
- package/dist/agent/agent.d.ts +375 -9
- package/dist/agent/agent.d.ts.map +1 -1
- package/dist/agent/index.cjs +11 -11
- package/dist/agent/index.js +2 -2
- package/dist/agent/input-processor/index.cjs +6 -6
- package/dist/agent/input-processor/index.js +1 -1
- package/dist/agent/types.d.ts +66 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/workflows/prepare-stream/index.d.ts +19 -3
- package/dist/agent/workflows/prepare-stream/index.d.ts.map +1 -1
- package/dist/agent/workflows/prepare-stream/prepare-memory-step.d.ts +5 -1
- package/dist/agent/workflows/prepare-stream/prepare-memory-step.d.ts.map +1 -1
- package/dist/agent/workflows/prepare-stream/prepare-tools-step.d.ts +5 -1
- package/dist/agent/workflows/prepare-stream/prepare-tools-step.d.ts.map +1 -1
- package/dist/agent/workflows/prepare-stream/stream-step.d.ts +5 -1
- package/dist/agent/workflows/prepare-stream/stream-step.d.ts.map +1 -1
- package/dist/ai-tracing/index.cjs +32 -32
- package/dist/ai-tracing/index.js +1 -1
- package/dist/{chunk-3NNB72OL.cjs → chunk-33DTPWTJ.cjs} +7 -7
- package/dist/{chunk-3NNB72OL.cjs.map → chunk-33DTPWTJ.cjs.map} +1 -1
- package/dist/{chunk-CJDOU6WP.js → chunk-3KBXOXG6.js} +3 -3
- package/dist/{chunk-CJDOU6WP.js.map → chunk-3KBXOXG6.js.map} +1 -1
- package/dist/{chunk-ZNK5RN5D.cjs → chunk-3Z3DP6S2.cjs} +6 -6
- package/dist/{chunk-ZNK5RN5D.cjs.map → chunk-3Z3DP6S2.cjs.map} +1 -1
- package/dist/{chunk-KGBDRSMX.js → chunk-5I6DXBUR.js} +4 -4
- package/dist/{chunk-KGBDRSMX.js.map → chunk-5I6DXBUR.js.map} +1 -1
- package/dist/{chunk-7EUC32F3.cjs → chunk-6K7IMZVR.cjs} +617 -24
- package/dist/chunk-6K7IMZVR.cjs.map +1 -0
- package/dist/{chunk-FV4QVXO4.js → chunk-6OLRLZJ3.js} +37 -2
- package/dist/chunk-6OLRLZJ3.js.map +1 -0
- package/dist/{chunk-OXLN4CWA.js → chunk-B5GBHE4E.js} +3 -3
- package/dist/{chunk-OXLN4CWA.js.map → chunk-B5GBHE4E.js.map} +1 -1
- package/dist/{chunk-2SH5WPUA.cjs → chunk-BG5FC6ZZ.cjs} +407 -803
- package/dist/chunk-BG5FC6ZZ.cjs.map +1 -0
- package/dist/{chunk-COYTVUIL.js → chunk-EKFF7JLS.js} +610 -17
- package/dist/chunk-EKFF7JLS.js.map +1 -0
- package/dist/{chunk-BOJNXNRV.js → chunk-FJIABZVI.js} +3 -3
- package/dist/{chunk-BOJNXNRV.js.map → chunk-FJIABZVI.js.map} +1 -1
- package/dist/{chunk-RYFQKXXS.js → chunk-H4KO46HZ.js} +3 -3
- package/dist/{chunk-RYFQKXXS.js.map → chunk-H4KO46HZ.js.map} +1 -1
- package/dist/{chunk-T4H33PBR.cjs → chunk-IKLSJCMT.cjs} +360 -69
- package/dist/chunk-IKLSJCMT.cjs.map +1 -0
- package/dist/{chunk-K4AYIXVH.cjs → chunk-L5A4MRCK.cjs} +4 -4
- package/dist/{chunk-K4AYIXVH.cjs.map → chunk-L5A4MRCK.cjs.map} +1 -1
- package/dist/{chunk-DVHBWEYY.cjs → chunk-LCJHFYJS.cjs} +7 -7
- package/dist/{chunk-DVHBWEYY.cjs.map → chunk-LCJHFYJS.cjs.map} +1 -1
- package/dist/{chunk-I6TOPBP6.cjs → chunk-MHHMY2K4.cjs} +49 -18
- package/dist/chunk-MHHMY2K4.cjs.map +1 -0
- package/dist/{chunk-DQISKQDE.js → chunk-OBAFLVGD.js} +342 -51
- package/dist/chunk-OBAFLVGD.js.map +1 -0
- package/dist/{chunk-6R46VE63.js → chunk-OPHFW56S.js} +39 -8
- package/dist/chunk-OPHFW56S.js.map +1 -0
- package/dist/{chunk-LCJP7LWN.cjs → chunk-P4ZPZKZY.cjs} +6 -6
- package/dist/{chunk-LCJP7LWN.cjs.map → chunk-P4ZPZKZY.cjs.map} +1 -1
- package/dist/{chunk-QR5VZWWT.js → chunk-P7WEYMRS.js} +3 -3
- package/dist/{chunk-QR5VZWWT.js.map → chunk-P7WEYMRS.js.map} +1 -1
- package/dist/{chunk-YEEAHLAK.cjs → chunk-PWPESTZZ.cjs} +4 -4
- package/dist/{chunk-YEEAHLAK.cjs.map → chunk-PWPESTZZ.cjs.map} +1 -1
- package/dist/{chunk-7HUKQ6SZ.cjs → chunk-Q3S3BXHO.cjs} +4 -4
- package/dist/{chunk-7HUKQ6SZ.cjs.map → chunk-Q3S3BXHO.cjs.map} +1 -1
- package/dist/{chunk-WP2KQXPV.js → chunk-RMMGYPXG.js} +3 -3
- package/dist/{chunk-WP2KQXPV.js.map → chunk-RMMGYPXG.js.map} +1 -1
- package/dist/{chunk-A4RAEU6X.cjs → chunk-SE4PA467.cjs} +37 -2
- package/dist/chunk-SE4PA467.cjs.map +1 -0
- package/dist/{chunk-AND6J5LG.js → chunk-W2WXYTYI.js} +407 -803
- package/dist/chunk-W2WXYTYI.js.map +1 -0
- package/dist/{chunk-OKIHIKXV.cjs → chunk-WE6ILDQY.cjs} +4 -4
- package/dist/{chunk-OKIHIKXV.cjs.map → chunk-WE6ILDQY.cjs.map} +1 -1
- package/dist/{chunk-ZJ2UFCTS.js → chunk-Y2TIJVKC.js} +3 -3
- package/dist/{chunk-ZJ2UFCTS.js.map → chunk-Y2TIJVKC.js.map} +1 -1
- package/dist/index.cjs +52 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +11 -11
- package/dist/index.js.map +1 -1
- package/dist/integration/index.cjs +3 -3
- package/dist/integration/index.js +1 -1
- package/dist/llm/index.cjs +6 -6
- package/dist/llm/index.d.ts +1 -1
- package/dist/llm/index.d.ts.map +1 -1
- package/dist/llm/index.js +1 -1
- package/dist/llm/model/gateway-resolver.d.ts +7 -5
- package/dist/llm/model/gateway-resolver.d.ts.map +1 -1
- package/dist/llm/model/gateways/base.d.ts +8 -6
- package/dist/llm/model/gateways/base.d.ts.map +1 -1
- package/dist/llm/model/gateways/constants.d.ts +3 -0
- package/dist/llm/model/gateways/constants.d.ts.map +1 -0
- package/dist/llm/model/gateways/index.d.ts +5 -0
- package/dist/llm/model/gateways/index.d.ts.map +1 -1
- package/dist/llm/model/gateways/models-dev.d.ts +8 -2
- package/dist/llm/model/gateways/models-dev.d.ts.map +1 -1
- package/dist/llm/model/gateways/netlify.d.ts +11 -2
- package/dist/llm/model/gateways/netlify.d.ts.map +1 -1
- package/dist/llm/model/index.d.ts +1 -1
- package/dist/llm/model/index.d.ts.map +1 -1
- package/dist/llm/model/model.loop.d.ts +1 -1
- package/dist/llm/model/model.loop.d.ts.map +1 -1
- package/dist/llm/model/provider-registry.generated.d.ts +6 -7
- package/dist/llm/model/provider-registry.generated.d.ts.map +1 -1
- package/dist/llm/model/router.d.ts +23 -0
- package/dist/llm/model/router.d.ts.map +1 -0
- package/dist/loop/index.cjs +2 -2
- package/dist/loop/index.js +1 -1
- package/dist/loop/network/index.d.ts +5 -1
- package/dist/loop/network/index.d.ts.map +1 -1
- package/dist/loop/workflows/agentic-execution/index.d.ts +5 -1
- package/dist/loop/workflows/agentic-execution/index.d.ts.map +1 -1
- package/dist/loop/workflows/agentic-execution/llm-execution-step.d.ts +5 -1
- package/dist/loop/workflows/agentic-execution/llm-execution-step.d.ts.map +1 -1
- package/dist/loop/workflows/agentic-execution/llm-mapping-step.d.ts +5 -1
- package/dist/loop/workflows/agentic-execution/llm-mapping-step.d.ts.map +1 -1
- package/dist/loop/workflows/agentic-execution/tool-call-step.d.ts +5 -1
- package/dist/loop/workflows/agentic-execution/tool-call-step.d.ts.map +1 -1
- package/dist/loop/workflows/agentic-loop/index.d.ts +5 -1
- package/dist/loop/workflows/agentic-loop/index.d.ts.map +1 -1
- package/dist/mastra/index.cjs +2 -2
- package/dist/mastra/index.d.ts +695 -12
- package/dist/mastra/index.d.ts.map +1 -1
- package/dist/mastra/index.js +1 -1
- package/dist/memory/index.cjs +4 -4
- package/dist/memory/index.js +1 -1
- package/dist/processors/index.cjs +11 -11
- package/dist/processors/index.js +1 -1
- package/dist/relevance/index.cjs +4 -4
- package/dist/relevance/index.js +1 -1
- package/dist/scores/index.cjs +9 -9
- package/dist/scores/index.js +2 -2
- package/dist/scores/run-experiment/index.d.ts +2 -2
- package/dist/scores/run-experiment/index.d.ts.map +1 -1
- package/dist/scores/scoreTraces/index.cjs +8 -8
- package/dist/scores/scoreTraces/index.js +3 -3
- package/dist/scores/scoreTraces/scoreTracesWorkflow.d.ts +10 -2
- package/dist/scores/scoreTraces/scoreTracesWorkflow.d.ts.map +1 -1
- package/dist/storage/domains/operations/base.d.ts +6 -0
- package/dist/storage/domains/operations/base.d.ts.map +1 -1
- package/dist/storage/index.cjs +11 -3
- package/dist/storage/index.cjs.map +1 -1
- package/dist/storage/index.js +9 -1
- package/dist/storage/index.js.map +1 -1
- package/dist/stream/MastraAgentNetworkStream.d.ts +5 -1
- package/dist/stream/MastraAgentNetworkStream.d.ts.map +1 -1
- package/dist/stream/MastraWorkflowStream.d.ts +3 -3
- package/dist/stream/MastraWorkflowStream.d.ts.map +1 -1
- package/dist/stream/index.cjs +4 -4
- package/dist/stream/index.js +1 -1
- package/dist/test-utils/llm-mock.cjs +2 -2
- package/dist/test-utils/llm-mock.js +1 -1
- package/dist/tools/index.cjs +4 -4
- package/dist/tools/index.js +1 -1
- package/dist/tools/is-vercel-tool.cjs +2 -2
- package/dist/tools/is-vercel-tool.js +1 -1
- package/dist/tools/tool.d.ts +163 -0
- package/dist/tools/tool.d.ts.map +1 -1
- package/dist/utils.cjs +17 -17
- package/dist/utils.d.ts +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +1 -1
- package/dist/workflows/default.d.ts +13 -7
- package/dist/workflows/default.d.ts.map +1 -1
- package/dist/workflows/evented/index.cjs +10 -10
- package/dist/workflows/evented/index.js +1 -1
- package/dist/workflows/evented/step-executor.d.ts +7 -3
- package/dist/workflows/evented/step-executor.d.ts.map +1 -1
- package/dist/workflows/evented/workflow-event-processor/index.d.ts.map +1 -1
- package/dist/workflows/evented/workflow-event-processor/loop.d.ts.map +1 -1
- package/dist/workflows/evented/workflow-event-processor/parallel.d.ts.map +1 -1
- package/dist/workflows/evented/workflow-event-processor/utils.d.ts +1 -1
- package/dist/workflows/evented/workflow.d.ts +23 -19
- package/dist/workflows/evented/workflow.d.ts.map +1 -1
- package/dist/workflows/execution-engine.d.ts +5 -1
- package/dist/workflows/execution-engine.d.ts.map +1 -1
- package/dist/workflows/index.cjs +12 -12
- package/dist/workflows/index.js +1 -1
- package/dist/workflows/legacy/index.cjs +22 -22
- package/dist/workflows/legacy/index.js +1 -1
- package/dist/workflows/step.d.ts +11 -4
- package/dist/workflows/step.d.ts.map +1 -1
- package/dist/workflows/types.d.ts +33 -9
- package/dist/workflows/types.d.ts.map +1 -1
- package/dist/workflows/workflow.d.ts +85 -56
- package/dist/workflows/workflow.d.ts.map +1 -1
- package/dist/workflows/workflow.warning.d.ts +2 -2
- package/dist/workflows/workflow.warning.d.ts.map +1 -1
- package/package.json +9 -4
- package/dist/chunk-2SH5WPUA.cjs.map +0 -1
- package/dist/chunk-45CV4JYJ.cjs +0 -4
- package/dist/chunk-45CV4JYJ.cjs.map +0 -1
- package/dist/chunk-6R46VE63.js.map +0 -1
- package/dist/chunk-7EUC32F3.cjs.map +0 -1
- package/dist/chunk-A4RAEU6X.cjs.map +0 -1
- package/dist/chunk-AND6J5LG.js.map +0 -1
- package/dist/chunk-COYTVUIL.js.map +0 -1
- package/dist/chunk-DQISKQDE.js.map +0 -1
- package/dist/chunk-FV4QVXO4.js.map +0 -1
- package/dist/chunk-I6TOPBP6.cjs.map +0 -1
- package/dist/chunk-RFGQ3EQV.js +0 -3
- package/dist/chunk-RFGQ3EQV.js.map +0 -1
- package/dist/chunk-T4H33PBR.cjs.map +0 -1
- package/dist/llm/model/openai-compatible.d.ts +0 -37
- package/dist/llm/model/openai-compatible.d.ts.map +0 -1
|
@@ -1,5 +1,351 @@
|
|
|
1
1
|
import { InMemoryServerCache } from './chunk-EQV2PPN2.js';
|
|
2
2
|
import { MastraError } from './chunk-T3JFFQH2.js';
|
|
3
|
+
import { createHash } from 'crypto';
|
|
4
|
+
import { createAnthropic } from '@ai-sdk/anthropic-v5';
|
|
5
|
+
import { createGoogleGenerativeAI } from '@ai-sdk/google-v5';
|
|
6
|
+
import { createOpenAICompatible } from '@ai-sdk/openai-compatible-v5';
|
|
7
|
+
import { createOpenAI } from '@ai-sdk/openai-v5';
|
|
8
|
+
import { createXai } from '@ai-sdk/xai-v5';
|
|
9
|
+
import { createOpenRouter } from '@openrouter/ai-sdk-provider-v5';
|
|
10
|
+
|
|
11
|
+
// src/llm/model/gateway-resolver.ts
|
|
12
|
+
function parseModelRouterId(routerId, gatewayPrefix) {
|
|
13
|
+
if (gatewayPrefix && !routerId.startsWith(`${gatewayPrefix}/`)) {
|
|
14
|
+
throw new Error(`Expected ${gatewayPrefix}/ in model router ID ${routerId}`);
|
|
15
|
+
}
|
|
16
|
+
const idParts = routerId.split("/");
|
|
17
|
+
if (gatewayPrefix && idParts.length < 3) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
`Expected atleast 3 id parts ${gatewayPrefix}/provider/model, but only saw ${idParts.length} in ${routerId}`
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
const providerId = idParts.at(gatewayPrefix ? 1 : 0);
|
|
23
|
+
const modelId = idParts.slice(gatewayPrefix ? 2 : 1).join(`/`);
|
|
24
|
+
if (!routerId.includes(`/`) || !providerId || !modelId) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
`Attempted to parse provider/model from ${routerId} but this ID doesn't appear to contain a provider`
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
providerId,
|
|
31
|
+
modelId
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// src/llm/model/gateways/base.ts
|
|
36
|
+
var MastraModelGateway = class {
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// src/llm/model/gateways/constants.ts
|
|
40
|
+
var PROVIDERS_WITH_INSTALLED_PACKAGES = ["anthropic", "google", "openai", "openrouter", "xai"];
|
|
41
|
+
var EXCLUDED_PROVIDERS = ["github-copilot"];
|
|
42
|
+
|
|
43
|
+
// src/llm/model/gateways/models-dev.ts
|
|
44
|
+
var OPENAI_COMPATIBLE_OVERRIDES = {
|
|
45
|
+
cerebras: {
|
|
46
|
+
url: "https://api.cerebras.ai/v1/chat/completions"
|
|
47
|
+
},
|
|
48
|
+
xai: {
|
|
49
|
+
url: "https://api.x.ai/v1/chat/completions"
|
|
50
|
+
},
|
|
51
|
+
mistral: {
|
|
52
|
+
url: "https://api.mistral.ai/v1/chat/completions"
|
|
53
|
+
},
|
|
54
|
+
groq: {
|
|
55
|
+
url: "https://api.groq.com/openai/v1/chat/completions"
|
|
56
|
+
},
|
|
57
|
+
togetherai: {
|
|
58
|
+
url: "https://api.together.xyz/v1/chat/completions"
|
|
59
|
+
},
|
|
60
|
+
deepinfra: {
|
|
61
|
+
url: "https://api.deepinfra.com/v1/openai/chat/completions"
|
|
62
|
+
},
|
|
63
|
+
perplexity: {
|
|
64
|
+
url: "https://api.perplexity.ai/chat/completions"
|
|
65
|
+
},
|
|
66
|
+
vercel: {
|
|
67
|
+
url: "https://ai-gateway.vercel.sh/v1/chat/completions",
|
|
68
|
+
apiKeyEnvVar: "AI_GATEWAY_API_KEY"
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
var ModelsDevGateway = class extends MastraModelGateway {
|
|
72
|
+
name = "models.dev";
|
|
73
|
+
prefix = void 0;
|
|
74
|
+
// No prefix for registry gateway
|
|
75
|
+
providerConfigs = {};
|
|
76
|
+
constructor(providerConfigs) {
|
|
77
|
+
super();
|
|
78
|
+
if (providerConfigs) this.providerConfigs = providerConfigs;
|
|
79
|
+
}
|
|
80
|
+
async fetchProviders() {
|
|
81
|
+
console.info("Fetching providers from models.dev API...");
|
|
82
|
+
const response = await fetch("https://models.dev/api.json");
|
|
83
|
+
if (!response.ok) {
|
|
84
|
+
throw new Error(`Failed to fetch from models.dev: ${response.statusText}`);
|
|
85
|
+
}
|
|
86
|
+
const data = await response.json();
|
|
87
|
+
const providerConfigs = {};
|
|
88
|
+
for (const [providerId, providerInfo] of Object.entries(data)) {
|
|
89
|
+
if (EXCLUDED_PROVIDERS.includes(providerId)) continue;
|
|
90
|
+
if (!providerInfo || typeof providerInfo !== "object" || !providerInfo.models) continue;
|
|
91
|
+
const normalizedId = providerId;
|
|
92
|
+
const isOpenAICompatible = providerInfo.npm === "@ai-sdk/openai-compatible" || providerInfo.npm === "@ai-sdk/gateway" || // Vercel AI Gateway is OpenAI-compatible
|
|
93
|
+
normalizedId in OPENAI_COMPATIBLE_OVERRIDES;
|
|
94
|
+
const hasInstalledPackage = PROVIDERS_WITH_INSTALLED_PACKAGES.includes(providerId);
|
|
95
|
+
const hasApiAndEnv = providerInfo.api && providerInfo.env && providerInfo.env.length > 0;
|
|
96
|
+
if (isOpenAICompatible || hasInstalledPackage || hasApiAndEnv) {
|
|
97
|
+
const modelIds = Object.keys(providerInfo.models).sort();
|
|
98
|
+
let url = providerInfo.api || OPENAI_COMPATIBLE_OVERRIDES[normalizedId]?.url;
|
|
99
|
+
if (!hasInstalledPackage && url && !url.includes("/chat/completions") && !url.includes("/messages")) {
|
|
100
|
+
url = url.replace(/\/$/, "") + "/chat/completions";
|
|
101
|
+
}
|
|
102
|
+
if (!hasInstalledPackage && !url) {
|
|
103
|
+
console.info(`Skipping ${normalizedId}: No API URL available`);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const apiKeyEnvVar = providerInfo.env?.[0] || `${normalizedId.toUpperCase().replace(/-/g, "_")}_API_KEY`;
|
|
107
|
+
const apiKeyHeader = !hasInstalledPackage ? OPENAI_COMPATIBLE_OVERRIDES[normalizedId]?.apiKeyHeader || "Authorization" : void 0;
|
|
108
|
+
providerConfigs[normalizedId] = {
|
|
109
|
+
url,
|
|
110
|
+
apiKeyEnvVar,
|
|
111
|
+
apiKeyHeader,
|
|
112
|
+
name: providerInfo.name || providerId.charAt(0).toUpperCase() + providerId.slice(1),
|
|
113
|
+
models: modelIds,
|
|
114
|
+
docUrl: providerInfo.doc,
|
|
115
|
+
// Include documentation URL if available
|
|
116
|
+
gateway: `models.dev`
|
|
117
|
+
};
|
|
118
|
+
} else {
|
|
119
|
+
console.info(`Skipped provider ${providerInfo.name}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
this.providerConfigs = providerConfigs;
|
|
123
|
+
console.info(`Found ${Object.keys(providerConfigs).length} OpenAI-compatible providers`);
|
|
124
|
+
console.info("Providers:", Object.keys(providerConfigs).sort());
|
|
125
|
+
return providerConfigs;
|
|
126
|
+
}
|
|
127
|
+
buildUrl(routerId, envVars) {
|
|
128
|
+
const { providerId } = parseModelRouterId(routerId);
|
|
129
|
+
const config = this.providerConfigs[providerId];
|
|
130
|
+
if (!config?.url) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const baseUrlEnvVar = `${providerId.toUpperCase().replace(/-/g, "_")}_BASE_URL`;
|
|
134
|
+
const customBaseUrl = envVars?.[baseUrlEnvVar] || process.env[baseUrlEnvVar];
|
|
135
|
+
return customBaseUrl || config.url;
|
|
136
|
+
}
|
|
137
|
+
getApiKey(modelId) {
|
|
138
|
+
const [provider, model] = modelId.split("/");
|
|
139
|
+
if (!provider || !model) {
|
|
140
|
+
throw new Error(`Could not identify provider from model id ${modelId}`);
|
|
141
|
+
}
|
|
142
|
+
const config = this.providerConfigs[provider];
|
|
143
|
+
if (!config) {
|
|
144
|
+
throw new Error(`Could not find config for provider ${provider} with model id ${modelId}`);
|
|
145
|
+
}
|
|
146
|
+
const apiKey = typeof config.apiKeyEnvVar === `string` ? process.env[config.apiKeyEnvVar] : void 0;
|
|
147
|
+
if (!apiKey) {
|
|
148
|
+
throw new Error(`Could not find API key process.env.${config.apiKeyEnvVar} for model id ${modelId}`);
|
|
149
|
+
}
|
|
150
|
+
return Promise.resolve(apiKey);
|
|
151
|
+
}
|
|
152
|
+
async resolveLanguageModel({
|
|
153
|
+
modelId,
|
|
154
|
+
providerId,
|
|
155
|
+
apiKey
|
|
156
|
+
}) {
|
|
157
|
+
const baseURL = this.buildUrl(`${providerId}/${modelId}`);
|
|
158
|
+
switch (providerId) {
|
|
159
|
+
case "openai":
|
|
160
|
+
return createOpenAI({ apiKey }).responses(modelId);
|
|
161
|
+
case "gemini":
|
|
162
|
+
case "google":
|
|
163
|
+
return createGoogleGenerativeAI({
|
|
164
|
+
apiKey
|
|
165
|
+
}).chat(modelId);
|
|
166
|
+
case "anthropic":
|
|
167
|
+
return createAnthropic({ apiKey })(modelId);
|
|
168
|
+
case "openrouter":
|
|
169
|
+
return createOpenRouter({ apiKey })(modelId);
|
|
170
|
+
case "xai":
|
|
171
|
+
return createXai({
|
|
172
|
+
apiKey
|
|
173
|
+
})(modelId);
|
|
174
|
+
default:
|
|
175
|
+
if (!baseURL) throw new Error(`No API URL found for ${providerId}/${modelId}`);
|
|
176
|
+
return createOpenAICompatible({ name: providerId, apiKey, baseURL }).chatModel(modelId);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
var NetlifyGateway = class extends MastraModelGateway {
|
|
181
|
+
name = "netlify";
|
|
182
|
+
prefix = "netlify";
|
|
183
|
+
// All providers will be prefixed with "netlify/"
|
|
184
|
+
tokenCache = new InMemoryServerCache();
|
|
185
|
+
async fetchProviders() {
|
|
186
|
+
console.info("Fetching providers from Netlify AI Gateway...");
|
|
187
|
+
const response = await fetch("https://api.netlify.com/api/v1/ai-gateway/providers");
|
|
188
|
+
if (!response.ok) {
|
|
189
|
+
throw new Error(`Failed to fetch from Netlify: ${response.statusText}`);
|
|
190
|
+
}
|
|
191
|
+
const data = await response.json();
|
|
192
|
+
const netlify = {
|
|
193
|
+
apiKeyEnvVar: ["NETLIFY_TOKEN", "NETLIFY_SITE_ID"],
|
|
194
|
+
apiKeyHeader: "Authorization",
|
|
195
|
+
// Netlify uses standard Bearer auth
|
|
196
|
+
name: `Netlify`,
|
|
197
|
+
gateway: `netlify`,
|
|
198
|
+
models: [],
|
|
199
|
+
docUrl: "https://docs.netlify.com/build/ai-gateway/overview/"
|
|
200
|
+
};
|
|
201
|
+
for (const [providerId, provider] of Object.entries(data.providers)) {
|
|
202
|
+
for (const model of provider.models) {
|
|
203
|
+
netlify.models.push(`${providerId}/${model}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
console.info(`Found ${Object.keys(data.providers).length} models via Netlify Gateway`);
|
|
207
|
+
return { netlify };
|
|
208
|
+
}
|
|
209
|
+
async buildUrl(routerId, envVars) {
|
|
210
|
+
const siteId = envVars?.["NETLIFY_SITE_ID"] || process.env["NETLIFY_SITE_ID"];
|
|
211
|
+
const netlifyToken = envVars?.["NETLIFY_TOKEN"] || process.env["NETLIFY_TOKEN"];
|
|
212
|
+
if (!netlifyToken) {
|
|
213
|
+
throw new MastraError({
|
|
214
|
+
id: "NETLIFY_GATEWAY_NO_TOKEN",
|
|
215
|
+
domain: "LLM",
|
|
216
|
+
category: "UNKNOWN",
|
|
217
|
+
text: `Missing NETLIFY_TOKEN environment variable required for model: ${routerId}`
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
if (!siteId) {
|
|
221
|
+
throw new MastraError({
|
|
222
|
+
id: "NETLIFY_GATEWAY_NO_SITE_ID",
|
|
223
|
+
domain: "LLM",
|
|
224
|
+
category: "UNKNOWN",
|
|
225
|
+
text: `Missing NETLIFY_SITE_ID environment variable required for model: ${routerId}`
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
try {
|
|
229
|
+
const tokenData = await this.getOrFetchToken(siteId, netlifyToken);
|
|
230
|
+
return tokenData.url.endsWith(`/`) ? tokenData.url.substring(0, tokenData.url.length - 1) : tokenData.url;
|
|
231
|
+
} catch (error) {
|
|
232
|
+
throw new MastraError({
|
|
233
|
+
id: "NETLIFY_GATEWAY_TOKEN_ERROR",
|
|
234
|
+
domain: "LLM",
|
|
235
|
+
category: "UNKNOWN",
|
|
236
|
+
text: `Failed to get Netlify AI Gateway token for model ${routerId}: ${error instanceof Error ? error.message : String(error)}`
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Get cached token or fetch a new site-specific AI Gateway token from Netlify
|
|
242
|
+
*/
|
|
243
|
+
async getOrFetchToken(siteId, netlifyToken) {
|
|
244
|
+
const cacheKey = `netlify-token:${siteId}:${netlifyToken}`;
|
|
245
|
+
const cached = await this.tokenCache.get(cacheKey);
|
|
246
|
+
if (cached && cached.expiresAt > Date.now() / 1e3 + 60) {
|
|
247
|
+
return { token: cached.token, url: cached.url };
|
|
248
|
+
}
|
|
249
|
+
const response = await fetch(`https://api.netlify.com/api/v1/sites/${siteId}/ai-gateway/token`, {
|
|
250
|
+
method: "GET",
|
|
251
|
+
headers: {
|
|
252
|
+
Authorization: `Bearer ${netlifyToken}`
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
if (!response.ok) {
|
|
256
|
+
const error = await response.text();
|
|
257
|
+
throw new Error(`Failed to get Netlify AI Gateway token: ${response.status} ${error}`);
|
|
258
|
+
}
|
|
259
|
+
const tokenResponse = await response.json();
|
|
260
|
+
await this.tokenCache.set(cacheKey, {
|
|
261
|
+
token: tokenResponse.token,
|
|
262
|
+
url: tokenResponse.url,
|
|
263
|
+
expiresAt: tokenResponse.expires_at
|
|
264
|
+
});
|
|
265
|
+
return { token: tokenResponse.token, url: tokenResponse.url };
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Get cached token or fetch a new site-specific AI Gateway token from Netlify
|
|
269
|
+
*/
|
|
270
|
+
async getApiKey(modelId) {
|
|
271
|
+
const siteId = process.env["NETLIFY_SITE_ID"];
|
|
272
|
+
const netlifyToken = process.env["NETLIFY_TOKEN"];
|
|
273
|
+
if (!netlifyToken) {
|
|
274
|
+
throw new MastraError({
|
|
275
|
+
id: "NETLIFY_GATEWAY_NO_TOKEN",
|
|
276
|
+
domain: "LLM",
|
|
277
|
+
category: "UNKNOWN",
|
|
278
|
+
text: `Missing NETLIFY_TOKEN environment variable required for model: ${modelId}`
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
if (!siteId) {
|
|
282
|
+
throw new MastraError({
|
|
283
|
+
id: "NETLIFY_GATEWAY_NO_SITE_ID",
|
|
284
|
+
domain: "LLM",
|
|
285
|
+
category: "UNKNOWN",
|
|
286
|
+
text: `Missing NETLIFY_SITE_ID environment variable required for model: ${modelId}`
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
try {
|
|
290
|
+
return (await this.getOrFetchToken(siteId, netlifyToken)).token;
|
|
291
|
+
} catch (error) {
|
|
292
|
+
throw new MastraError({
|
|
293
|
+
id: "NETLIFY_GATEWAY_TOKEN_ERROR",
|
|
294
|
+
domain: "LLM",
|
|
295
|
+
category: "UNKNOWN",
|
|
296
|
+
text: `Failed to get Netlify AI Gateway token for model ${modelId}: ${error instanceof Error ? error.message : String(error)}`
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
async resolveLanguageModel({
|
|
301
|
+
modelId,
|
|
302
|
+
providerId,
|
|
303
|
+
apiKey
|
|
304
|
+
}) {
|
|
305
|
+
const baseURL = await this.buildUrl(`${providerId}/${modelId}`);
|
|
306
|
+
switch (providerId) {
|
|
307
|
+
case "openai":
|
|
308
|
+
return createOpenAI({ apiKey, baseURL }).responses(modelId);
|
|
309
|
+
case "gemini":
|
|
310
|
+
return createGoogleGenerativeAI({
|
|
311
|
+
baseURL: `${baseURL}/v1beta/`,
|
|
312
|
+
apiKey,
|
|
313
|
+
headers: {
|
|
314
|
+
"user-agent": "google-genai-sdk/"
|
|
315
|
+
}
|
|
316
|
+
}).chat(modelId);
|
|
317
|
+
case "anthropic":
|
|
318
|
+
return createAnthropic({
|
|
319
|
+
apiKey,
|
|
320
|
+
baseURL: `${baseURL}/v1/`,
|
|
321
|
+
headers: {
|
|
322
|
+
"anthropic-version": "2023-06-01",
|
|
323
|
+
"user-agent": "anthropic/"
|
|
324
|
+
}
|
|
325
|
+
})(modelId);
|
|
326
|
+
default:
|
|
327
|
+
return createOpenAICompatible({ name: providerId, apiKey, baseURL }).chatModel(modelId);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
// src/llm/model/gateways/index.ts
|
|
333
|
+
function findGatewayForModel(gatewayId, gateways2) {
|
|
334
|
+
const prefixedGateway = gateways2.find((g) => g.prefix && gatewayId.startsWith(`${g.prefix}/`));
|
|
335
|
+
if (prefixedGateway) {
|
|
336
|
+
return prefixedGateway;
|
|
337
|
+
}
|
|
338
|
+
const unprefixedGateways = gateways2.filter((g) => !g.prefix);
|
|
339
|
+
for (const gateway of unprefixedGateways) {
|
|
340
|
+
return gateway;
|
|
341
|
+
}
|
|
342
|
+
throw new MastraError({
|
|
343
|
+
id: "MODEL_ROUTER_NO_GATEWAY_FOUND",
|
|
344
|
+
category: "USER",
|
|
345
|
+
domain: "MODEL_ROUTER",
|
|
346
|
+
text: `No Mastra model router gateway found for model id ${gatewayId}`
|
|
347
|
+
});
|
|
348
|
+
}
|
|
3
349
|
|
|
4
350
|
// src/llm/model/provider-registry.generated.ts
|
|
5
351
|
var PROVIDER_REGISTRY = {
|
|
@@ -51,7 +397,6 @@ var PROVIDER_REGISTRY = {
|
|
|
51
397
|
xai: {
|
|
52
398
|
url: "https://api.x.ai/v1/chat/completions",
|
|
53
399
|
apiKeyEnvVar: "XAI_API_KEY",
|
|
54
|
-
apiKeyHeader: "Authorization",
|
|
55
400
|
name: "xAI",
|
|
56
401
|
models: [
|
|
57
402
|
"grok-2",
|
|
@@ -137,33 +482,6 @@ var PROVIDER_REGISTRY = {
|
|
|
137
482
|
docUrl: "https://console.groq.com/docs/models",
|
|
138
483
|
gateway: "models.dev"
|
|
139
484
|
},
|
|
140
|
-
"github-copilot": {
|
|
141
|
-
url: "https://api.githubcopilot.com/chat/completions",
|
|
142
|
-
apiKeyEnvVar: "GITHUB_TOKEN",
|
|
143
|
-
apiKeyHeader: "Authorization",
|
|
144
|
-
name: "GitHub Copilot",
|
|
145
|
-
models: [
|
|
146
|
-
"claude-3.5-sonnet",
|
|
147
|
-
"claude-3.7-sonnet",
|
|
148
|
-
"claude-3.7-sonnet-thought",
|
|
149
|
-
"claude-opus-4",
|
|
150
|
-
"claude-opus-41",
|
|
151
|
-
"claude-sonnet-4",
|
|
152
|
-
"claude-sonnet-4.5",
|
|
153
|
-
"gemini-2.0-flash-001",
|
|
154
|
-
"gemini-2.5-pro",
|
|
155
|
-
"gpt-4.1",
|
|
156
|
-
"gpt-4o",
|
|
157
|
-
"gpt-5",
|
|
158
|
-
"gpt-5-mini",
|
|
159
|
-
"grok-code-fast-1",
|
|
160
|
-
"o3",
|
|
161
|
-
"o3-mini",
|
|
162
|
-
"o4-mini"
|
|
163
|
-
],
|
|
164
|
-
docUrl: "https://docs.github.com/en/copilot",
|
|
165
|
-
gateway: "models.dev"
|
|
166
|
-
},
|
|
167
485
|
mistral: {
|
|
168
486
|
url: "https://api.mistral.ai/v1/chat/completions",
|
|
169
487
|
apiKeyEnvVar: "MISTRAL_API_KEY",
|
|
@@ -241,6 +559,7 @@ var PROVIDER_REGISTRY = {
|
|
|
241
559
|
"openai/gpt-4o",
|
|
242
560
|
"openai/gpt-4o-mini",
|
|
243
561
|
"openai/gpt-5",
|
|
562
|
+
"openai/gpt-5-codex",
|
|
244
563
|
"openai/gpt-5-mini",
|
|
245
564
|
"openai/gpt-5-nano",
|
|
246
565
|
"openai/gpt-oss-120b",
|
|
@@ -482,20 +801,22 @@ var PROVIDER_REGISTRY = {
|
|
|
482
801
|
url: "https://opencode.ai/zen/v1/chat/completions",
|
|
483
802
|
apiKeyEnvVar: "OPENCODE_API_KEY",
|
|
484
803
|
apiKeyHeader: "Authorization",
|
|
485
|
-
name: "
|
|
804
|
+
name: "OpenCode Zen",
|
|
486
805
|
models: [
|
|
487
806
|
"claude-3-5-haiku",
|
|
488
807
|
"claude-opus-4-1",
|
|
489
808
|
"claude-sonnet-4",
|
|
490
809
|
"claude-sonnet-4-5",
|
|
491
810
|
"code-supernova",
|
|
811
|
+
"glm-4.6",
|
|
492
812
|
"gpt-5",
|
|
813
|
+
"gpt-5-codex",
|
|
493
814
|
"grok-code",
|
|
494
815
|
"kimi-k2",
|
|
495
816
|
"qwen3-coder",
|
|
496
817
|
"qwen3-max"
|
|
497
818
|
],
|
|
498
|
-
docUrl: "https://opencode.ai/docs",
|
|
819
|
+
docUrl: "https://opencode.ai/docs/zen",
|
|
499
820
|
gateway: "models.dev"
|
|
500
821
|
},
|
|
501
822
|
fastrouter: {
|
|
@@ -523,9 +844,7 @@ var PROVIDER_REGISTRY = {
|
|
|
523
844
|
gateway: "models.dev"
|
|
524
845
|
},
|
|
525
846
|
google: {
|
|
526
|
-
url: "https://generativelanguage.googleapis.com/v1beta/chat/completions",
|
|
527
847
|
apiKeyEnvVar: "GOOGLE_GENERATIVE_AI_API_KEY",
|
|
528
|
-
apiKeyHeader: "Authorization",
|
|
529
848
|
name: "Google",
|
|
530
849
|
models: [
|
|
531
850
|
"gemini-1.5-flash",
|
|
@@ -580,11 +899,10 @@ var PROVIDER_REGISTRY = {
|
|
|
580
899
|
gateway: "models.dev"
|
|
581
900
|
},
|
|
582
901
|
openai: {
|
|
583
|
-
url: "https://api.openai.com/v1/chat/completions",
|
|
584
902
|
apiKeyEnvVar: "OPENAI_API_KEY",
|
|
585
|
-
apiKeyHeader: "Authorization",
|
|
586
903
|
name: "OpenAI",
|
|
587
904
|
models: [
|
|
905
|
+
"codex-mini-latest",
|
|
588
906
|
"gpt-3.5-turbo",
|
|
589
907
|
"gpt-4",
|
|
590
908
|
"gpt-4-turbo",
|
|
@@ -598,6 +916,7 @@ var PROVIDER_REGISTRY = {
|
|
|
598
916
|
"gpt-4o-mini",
|
|
599
917
|
"gpt-5",
|
|
600
918
|
"gpt-5-chat-latest",
|
|
919
|
+
"gpt-5-codex",
|
|
601
920
|
"gpt-5-mini",
|
|
602
921
|
"gpt-5-nano",
|
|
603
922
|
"o1",
|
|
@@ -633,9 +952,8 @@ var PROVIDER_REGISTRY = {
|
|
|
633
952
|
gateway: "models.dev"
|
|
634
953
|
},
|
|
635
954
|
openrouter: {
|
|
636
|
-
url: "https://openrouter.ai/api/v1
|
|
955
|
+
url: "https://openrouter.ai/api/v1",
|
|
637
956
|
apiKeyEnvVar: "OPENROUTER_API_KEY",
|
|
638
|
-
apiKeyHeader: "Authorization",
|
|
639
957
|
name: "OpenRouter",
|
|
640
958
|
models: [
|
|
641
959
|
"anthropic/claude-3.5-haiku",
|
|
@@ -695,6 +1013,7 @@ var PROVIDER_REGISTRY = {
|
|
|
695
1013
|
"openai/gpt-4o-mini",
|
|
696
1014
|
"openai/gpt-5",
|
|
697
1015
|
"openai/gpt-5-chat",
|
|
1016
|
+
"openai/gpt-5-codex",
|
|
698
1017
|
"openai/gpt-5-mini",
|
|
699
1018
|
"openai/gpt-5-nano",
|
|
700
1019
|
"openai/gpt-oss-120b",
|
|
@@ -769,7 +1088,8 @@ var PROVIDER_REGISTRY = {
|
|
|
769
1088
|
"hf:moonshotai/Kimi-K2-Instruct",
|
|
770
1089
|
"hf:moonshotai/Kimi-K2-Instruct-0905",
|
|
771
1090
|
"hf:openai/gpt-oss-120b",
|
|
772
|
-
"hf:zai-org/GLM-4.5"
|
|
1091
|
+
"hf:zai-org/GLM-4.5",
|
|
1092
|
+
"hf:zai-org/GLM-4.6"
|
|
773
1093
|
],
|
|
774
1094
|
docUrl: "https://synthetic.new/pricing",
|
|
775
1095
|
gateway: "models.dev"
|
|
@@ -886,9 +1206,7 @@ var PROVIDER_REGISTRY = {
|
|
|
886
1206
|
gateway: "models.dev"
|
|
887
1207
|
},
|
|
888
1208
|
anthropic: {
|
|
889
|
-
url: "https://api.anthropic.com/v1/chat/completions",
|
|
890
1209
|
apiKeyEnvVar: "ANTHROPIC_API_KEY",
|
|
891
|
-
apiKeyHeader: "x-api-key",
|
|
892
1210
|
name: "Anthropic",
|
|
893
1211
|
models: [
|
|
894
1212
|
"claude-3-5-haiku-20241022",
|
|
@@ -975,38 +1293,39 @@ var PROVIDER_REGISTRY = {
|
|
|
975
1293
|
name: "Netlify",
|
|
976
1294
|
gateway: "netlify",
|
|
977
1295
|
models: [
|
|
978
|
-
"anthropic/claude-3-5-haiku-20241022",
|
|
979
1296
|
"anthropic/claude-opus-4-20250514",
|
|
1297
|
+
"anthropic/claude-sonnet-4-5-20250929",
|
|
980
1298
|
"anthropic/claude-sonnet-4-20250514",
|
|
981
1299
|
"anthropic/claude-3-7-sonnet-20250219",
|
|
1300
|
+
"anthropic/claude-3-7-sonnet-latest",
|
|
1301
|
+
"anthropic/claude-3-5-haiku-20241022",
|
|
982
1302
|
"anthropic/claude-3-5-haiku-latest",
|
|
983
1303
|
"anthropic/claude-3-haiku-20240307",
|
|
984
1304
|
"anthropic/claude-opus-4-1-20250805",
|
|
985
|
-
"anthropic/claude-sonnet-4-5-20250929",
|
|
986
|
-
"anthropic/claude-3-7-sonnet-latest",
|
|
987
1305
|
"gemini/gemini-2.5-pro",
|
|
988
|
-
"gemini/gemini-2.5-flash",
|
|
989
|
-
"gemini/gemini-flash-lite-latest",
|
|
990
|
-
"gemini/gemini-2.0-flash-lite",
|
|
991
|
-
"gemini/gemini-2.5-flash-image-preview",
|
|
992
1306
|
"gemini/gemini-flash-latest",
|
|
993
|
-
"gemini/gemini-2.5-flash
|
|
1307
|
+
"gemini/gemini-2.5-flash",
|
|
994
1308
|
"gemini/gemini-2.5-flash-lite-preview-09-2025",
|
|
995
1309
|
"gemini/gemini-2.5-flash-lite",
|
|
1310
|
+
"gemini/gemini-2.5-flash-preview-09-2025",
|
|
1311
|
+
"gemini/gemini-flash-lite-latest",
|
|
996
1312
|
"gemini/gemini-2.0-flash",
|
|
1313
|
+
"gemini/gemini-2.0-flash-lite",
|
|
1314
|
+
"gemini/gemini-2.5-flash-image-preview",
|
|
1315
|
+
"openai/gpt-4.1-mini",
|
|
1316
|
+
"openai/gpt-4.1-nano",
|
|
1317
|
+
"openai/gpt-4o",
|
|
1318
|
+
"openai/gpt-5-pro",
|
|
1319
|
+
"openai/gpt-4o-mini",
|
|
997
1320
|
"openai/o4-mini",
|
|
998
1321
|
"openai/o3",
|
|
999
1322
|
"openai/o3-mini",
|
|
1000
1323
|
"openai/codex-mini-latest",
|
|
1324
|
+
"openai/gpt-5",
|
|
1001
1325
|
"openai/gpt-5-codex",
|
|
1002
1326
|
"openai/gpt-5-mini",
|
|
1003
1327
|
"openai/gpt-5-nano",
|
|
1004
|
-
"openai/gpt-4.1"
|
|
1005
|
-
"openai/gpt-4.1-mini",
|
|
1006
|
-
"openai/gpt-4.1-nano",
|
|
1007
|
-
"openai/gpt-4o",
|
|
1008
|
-
"openai/gpt-4o-mini",
|
|
1009
|
-
"openai/gpt-5"
|
|
1328
|
+
"openai/gpt-4.1"
|
|
1010
1329
|
],
|
|
1011
1330
|
docUrl: "https://docs.netlify.com/build/ai-gateway/overview/"
|
|
1012
1331
|
}
|
|
@@ -1032,331 +1351,12 @@ function parseModelString(modelString) {
|
|
|
1032
1351
|
};
|
|
1033
1352
|
}
|
|
1034
1353
|
|
|
1035
|
-
// src/llm/model/
|
|
1036
|
-
var MastraModelGateway = class {
|
|
1037
|
-
};
|
|
1038
|
-
|
|
1039
|
-
// src/llm/model/gateways/models-dev.ts
|
|
1040
|
-
var OPENAI_COMPATIBLE_OVERRIDES = {
|
|
1041
|
-
openai: {
|
|
1042
|
-
url: "https://api.openai.com/v1/chat/completions"
|
|
1043
|
-
},
|
|
1044
|
-
anthropic: {
|
|
1045
|
-
url: "https://api.anthropic.com/v1/chat/completions",
|
|
1046
|
-
apiKeyHeader: "x-api-key"
|
|
1047
|
-
},
|
|
1048
|
-
cerebras: {
|
|
1049
|
-
url: "https://api.cerebras.ai/v1/chat/completions"
|
|
1050
|
-
},
|
|
1051
|
-
xai: {
|
|
1052
|
-
url: "https://api.x.ai/v1/chat/completions"
|
|
1053
|
-
},
|
|
1054
|
-
mistral: {
|
|
1055
|
-
url: "https://api.mistral.ai/v1/chat/completions"
|
|
1056
|
-
},
|
|
1057
|
-
google: {
|
|
1058
|
-
url: "https://generativelanguage.googleapis.com/v1beta/chat/completions"
|
|
1059
|
-
},
|
|
1060
|
-
groq: {
|
|
1061
|
-
url: "https://api.groq.com/openai/v1/chat/completions"
|
|
1062
|
-
},
|
|
1063
|
-
togetherai: {
|
|
1064
|
-
url: "https://api.together.xyz/v1/chat/completions"
|
|
1065
|
-
},
|
|
1066
|
-
deepinfra: {
|
|
1067
|
-
url: "https://api.deepinfra.com/v1/openai/chat/completions"
|
|
1068
|
-
},
|
|
1069
|
-
perplexity: {
|
|
1070
|
-
url: "https://api.perplexity.ai/chat/completions"
|
|
1071
|
-
},
|
|
1072
|
-
vercel: {
|
|
1073
|
-
url: "https://ai-gateway.vercel.sh/v1/chat/completions",
|
|
1074
|
-
apiKeyEnvVar: "AI_GATEWAY_API_KEY"
|
|
1075
|
-
}
|
|
1076
|
-
};
|
|
1077
|
-
var ModelsDevGateway = class extends MastraModelGateway {
|
|
1078
|
-
name = "models.dev";
|
|
1079
|
-
prefix = void 0;
|
|
1080
|
-
// No prefix for registry gateway
|
|
1081
|
-
providerConfigs = {};
|
|
1082
|
-
constructor(providerConfigs) {
|
|
1083
|
-
super();
|
|
1084
|
-
if (providerConfigs) this.providerConfigs = providerConfigs;
|
|
1085
|
-
}
|
|
1086
|
-
async fetchProviders() {
|
|
1087
|
-
console.info("Fetching providers from models.dev API...");
|
|
1088
|
-
const response = await fetch("https://models.dev/api.json");
|
|
1089
|
-
if (!response.ok) {
|
|
1090
|
-
throw new Error(`Failed to fetch from models.dev: ${response.statusText}`);
|
|
1091
|
-
}
|
|
1092
|
-
const data = await response.json();
|
|
1093
|
-
const providerConfigs = {};
|
|
1094
|
-
for (const [providerId, providerInfo] of Object.entries(data)) {
|
|
1095
|
-
if (!providerInfo || typeof providerInfo !== "object" || !providerInfo.models) continue;
|
|
1096
|
-
const normalizedId = providerId;
|
|
1097
|
-
const isOpenAICompatible = providerInfo.npm === "@ai-sdk/openai-compatible" || providerInfo.npm === "@ai-sdk/gateway" || // Vercel AI Gateway is OpenAI-compatible
|
|
1098
|
-
normalizedId in OPENAI_COMPATIBLE_OVERRIDES;
|
|
1099
|
-
const hasApiAndEnv = providerInfo.api && providerInfo.env && providerInfo.env.length > 0;
|
|
1100
|
-
if (isOpenAICompatible || hasApiAndEnv) {
|
|
1101
|
-
const modelIds = Object.keys(providerInfo.models).sort();
|
|
1102
|
-
let url = providerInfo.api || OPENAI_COMPATIBLE_OVERRIDES[normalizedId]?.url;
|
|
1103
|
-
if (url && !url.includes("/chat/completions") && !url.includes("/messages")) {
|
|
1104
|
-
url = url.replace(/\/$/, "") + "/chat/completions";
|
|
1105
|
-
}
|
|
1106
|
-
if (!url) {
|
|
1107
|
-
console.info(`Skipping ${normalizedId}: No API URL available`);
|
|
1108
|
-
continue;
|
|
1109
|
-
}
|
|
1110
|
-
const apiKeyEnvVar = providerInfo.env?.[0] || `${normalizedId.toUpperCase().replace(/-/g, "_")}_API_KEY`;
|
|
1111
|
-
const apiKeyHeader = OPENAI_COMPATIBLE_OVERRIDES[normalizedId]?.apiKeyHeader || "Authorization";
|
|
1112
|
-
providerConfigs[normalizedId] = {
|
|
1113
|
-
url,
|
|
1114
|
-
apiKeyEnvVar,
|
|
1115
|
-
apiKeyHeader,
|
|
1116
|
-
name: providerInfo.name || providerId.charAt(0).toUpperCase() + providerId.slice(1),
|
|
1117
|
-
models: modelIds.filter((id) => !id.includes(`codex`)),
|
|
1118
|
-
// codex requires responses api
|
|
1119
|
-
docUrl: providerInfo.doc,
|
|
1120
|
-
// Include documentation URL if available
|
|
1121
|
-
gateway: `models.dev`
|
|
1122
|
-
};
|
|
1123
|
-
} else {
|
|
1124
|
-
console.info(`Skipped provider ${providerInfo.name}`);
|
|
1125
|
-
}
|
|
1126
|
-
}
|
|
1127
|
-
this.providerConfigs = providerConfigs;
|
|
1128
|
-
console.info(`Found ${Object.keys(providerConfigs).length} OpenAI-compatible providers`);
|
|
1129
|
-
console.info("Providers:", Object.keys(providerConfigs).sort());
|
|
1130
|
-
return providerConfigs;
|
|
1131
|
-
}
|
|
1132
|
-
buildUrl(modelId, envVars) {
|
|
1133
|
-
const [provider, ...modelParts] = modelId.split("/");
|
|
1134
|
-
if (!provider || !modelParts.length) {
|
|
1135
|
-
return false;
|
|
1136
|
-
}
|
|
1137
|
-
const config = this.providerConfigs[provider];
|
|
1138
|
-
if (!config?.url) {
|
|
1139
|
-
return false;
|
|
1140
|
-
}
|
|
1141
|
-
const baseUrlEnvVar = `${provider.toUpperCase().replace(/-/g, "_")}_BASE_URL`;
|
|
1142
|
-
const customBaseUrl = envVars[baseUrlEnvVar];
|
|
1143
|
-
return customBaseUrl || config.url;
|
|
1144
|
-
}
|
|
1145
|
-
buildHeaders(modelId, envVars) {
|
|
1146
|
-
const [provider] = modelId.split("/");
|
|
1147
|
-
if (!provider) {
|
|
1148
|
-
return {};
|
|
1149
|
-
}
|
|
1150
|
-
const config = this.providerConfigs[provider];
|
|
1151
|
-
if (!config) {
|
|
1152
|
-
return {};
|
|
1153
|
-
}
|
|
1154
|
-
const apiKey = typeof config.apiKeyEnvVar === `string` ? envVars[config.apiKeyEnvVar] : void 0;
|
|
1155
|
-
if (!apiKey) {
|
|
1156
|
-
return {};
|
|
1157
|
-
}
|
|
1158
|
-
const headers = {};
|
|
1159
|
-
if (config.apiKeyHeader === "Authorization" || !config.apiKeyHeader) {
|
|
1160
|
-
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
1161
|
-
} else {
|
|
1162
|
-
headers[config.apiKeyHeader] = apiKey;
|
|
1163
|
-
}
|
|
1164
|
-
if (provider === "anthropic") {
|
|
1165
|
-
headers["anthropic-version"] = "2023-06-01";
|
|
1166
|
-
}
|
|
1167
|
-
return headers;
|
|
1168
|
-
}
|
|
1169
|
-
};
|
|
1170
|
-
|
|
1171
|
-
// src/llm/model/gateways/netlify.ts
|
|
1172
|
-
var NetlifyGateway = class extends MastraModelGateway {
|
|
1173
|
-
name = "netlify";
|
|
1174
|
-
prefix = "netlify";
|
|
1175
|
-
// All providers will be prefixed with "netlify/"
|
|
1176
|
-
tokenCache = new InMemoryServerCache();
|
|
1177
|
-
async fetchProviders() {
|
|
1178
|
-
console.info("Fetching providers from Netlify AI Gateway...");
|
|
1179
|
-
const response = await fetch("https://api.netlify.com/api/v1/ai-gateway/providers");
|
|
1180
|
-
if (!response.ok) {
|
|
1181
|
-
throw new Error(`Failed to fetch from Netlify: ${response.statusText}`);
|
|
1182
|
-
}
|
|
1183
|
-
const data = await response.json();
|
|
1184
|
-
const netlify = {
|
|
1185
|
-
apiKeyEnvVar: ["NETLIFY_TOKEN", "NETLIFY_SITE_ID"],
|
|
1186
|
-
apiKeyHeader: "Authorization",
|
|
1187
|
-
// Netlify uses standard Bearer auth
|
|
1188
|
-
name: `Netlify`,
|
|
1189
|
-
gateway: `netlify`,
|
|
1190
|
-
models: [],
|
|
1191
|
-
docUrl: "https://docs.netlify.com/build/ai-gateway/overview/"
|
|
1192
|
-
};
|
|
1193
|
-
for (const [providerId, provider] of Object.entries(data.providers)) {
|
|
1194
|
-
for (const model of provider.models) {
|
|
1195
|
-
netlify.models.push(`${providerId}/${model}`);
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
console.info(`Found ${Object.keys(data.providers).length} models via Netlify Gateway`);
|
|
1199
|
-
return { netlify };
|
|
1200
|
-
}
|
|
1201
|
-
async buildUrl(modelId, envVars) {
|
|
1202
|
-
if (!modelId.startsWith(`${this.prefix}/`)) {
|
|
1203
|
-
return false;
|
|
1204
|
-
}
|
|
1205
|
-
const parts = modelId.split("/");
|
|
1206
|
-
if (parts.length < 3) {
|
|
1207
|
-
return false;
|
|
1208
|
-
}
|
|
1209
|
-
const provider = parts[1];
|
|
1210
|
-
if (!provider) {
|
|
1211
|
-
return false;
|
|
1212
|
-
}
|
|
1213
|
-
const siteId = envVars["NETLIFY_SITE_ID"];
|
|
1214
|
-
const netlifyToken = envVars["NETLIFY_TOKEN"];
|
|
1215
|
-
if (!netlifyToken) {
|
|
1216
|
-
throw new MastraError({
|
|
1217
|
-
id: "NETLIFY_GATEWAY_NO_TOKEN",
|
|
1218
|
-
domain: "LLM",
|
|
1219
|
-
category: "UNKNOWN",
|
|
1220
|
-
text: `Missing NETLIFY_TOKEN environment variable required for model: ${modelId}`
|
|
1221
|
-
});
|
|
1222
|
-
}
|
|
1223
|
-
if (!siteId) {
|
|
1224
|
-
throw new MastraError({
|
|
1225
|
-
id: "NETLIFY_GATEWAY_NO_SITE_ID",
|
|
1226
|
-
domain: "LLM",
|
|
1227
|
-
category: "UNKNOWN",
|
|
1228
|
-
text: `Missing NETLIFY_SITE_ID environment variable required for model: ${modelId}`
|
|
1229
|
-
});
|
|
1230
|
-
}
|
|
1231
|
-
try {
|
|
1232
|
-
const tokenData = await this.getOrFetchToken(siteId, netlifyToken);
|
|
1233
|
-
return `${tokenData.url}chat/completions`;
|
|
1234
|
-
} catch (error) {
|
|
1235
|
-
throw new MastraError({
|
|
1236
|
-
id: "NETLIFY_GATEWAY_TOKEN_ERROR",
|
|
1237
|
-
domain: "LLM",
|
|
1238
|
-
category: "UNKNOWN",
|
|
1239
|
-
text: `Failed to get Netlify AI Gateway token for model ${modelId}: ${error instanceof Error ? error.message : String(error)}`
|
|
1240
|
-
});
|
|
1241
|
-
}
|
|
1242
|
-
}
|
|
1243
|
-
/**
|
|
1244
|
-
* Get cached token or fetch a new site-specific AI Gateway token from Netlify
|
|
1245
|
-
*/
|
|
1246
|
-
async getOrFetchToken(siteId, netlifyToken) {
|
|
1247
|
-
const cacheKey = `netlify-token:${siteId}:${netlifyToken}`;
|
|
1248
|
-
const cached = await this.tokenCache.get(cacheKey);
|
|
1249
|
-
if (cached && cached.expiresAt > Date.now() / 1e3 + 60) {
|
|
1250
|
-
return { token: cached.token, url: cached.url };
|
|
1251
|
-
}
|
|
1252
|
-
const response = await fetch(`https://api.netlify.com/api/v1/sites/${siteId}/ai-gateway/token`, {
|
|
1253
|
-
method: "GET",
|
|
1254
|
-
headers: {
|
|
1255
|
-
Authorization: `Bearer ${netlifyToken}`
|
|
1256
|
-
}
|
|
1257
|
-
});
|
|
1258
|
-
if (!response.ok) {
|
|
1259
|
-
const error = await response.text();
|
|
1260
|
-
throw new Error(`Failed to get Netlify AI Gateway token: ${response.status} ${error}`);
|
|
1261
|
-
}
|
|
1262
|
-
const tokenResponse = await response.json();
|
|
1263
|
-
await this.tokenCache.set(cacheKey, {
|
|
1264
|
-
token: tokenResponse.token,
|
|
1265
|
-
url: tokenResponse.url,
|
|
1266
|
-
expiresAt: tokenResponse.expires_at
|
|
1267
|
-
});
|
|
1268
|
-
return { token: tokenResponse.token, url: tokenResponse.url };
|
|
1269
|
-
}
|
|
1270
|
-
async buildHeaders(modelId, envVars) {
|
|
1271
|
-
const siteId = envVars["NETLIFY_SITE_ID"];
|
|
1272
|
-
const netlifyToken = envVars["NETLIFY_TOKEN"];
|
|
1273
|
-
if (!netlifyToken) {
|
|
1274
|
-
throw new MastraError({
|
|
1275
|
-
id: "NETLIFY_GATEWAY_NO_TOKEN",
|
|
1276
|
-
domain: "LLM",
|
|
1277
|
-
category: "UNKNOWN",
|
|
1278
|
-
text: `Missing NETLIFY_TOKEN environment variable required for model: ${modelId}`
|
|
1279
|
-
});
|
|
1280
|
-
}
|
|
1281
|
-
if (!siteId) {
|
|
1282
|
-
throw new MastraError({
|
|
1283
|
-
id: "NETLIFY_GATEWAY_NO_SITE_ID",
|
|
1284
|
-
domain: "LLM",
|
|
1285
|
-
category: "UNKNOWN",
|
|
1286
|
-
text: `Missing NETLIFY_SITE_ID environment variable required for model: ${modelId}`
|
|
1287
|
-
});
|
|
1288
|
-
}
|
|
1289
|
-
try {
|
|
1290
|
-
const tokenData = await this.getOrFetchToken(siteId, netlifyToken);
|
|
1291
|
-
return {
|
|
1292
|
-
Authorization: `Bearer ${tokenData.token}`
|
|
1293
|
-
};
|
|
1294
|
-
} catch (error) {
|
|
1295
|
-
throw new MastraError({
|
|
1296
|
-
id: "NETLIFY_GATEWAY_TOKEN_ERROR",
|
|
1297
|
-
domain: "LLM",
|
|
1298
|
-
category: "UNKNOWN",
|
|
1299
|
-
text: `Failed to get Netlify AI Gateway token for model ${modelId}: ${error instanceof Error ? error.message : String(error)}`
|
|
1300
|
-
});
|
|
1301
|
-
}
|
|
1302
|
-
}
|
|
1303
|
-
};
|
|
1304
|
-
|
|
1305
|
-
// src/llm/model/gateway-resolver.ts
|
|
1354
|
+
// src/llm/model/router.ts
|
|
1306
1355
|
function getStaticProvidersByGateway(name) {
|
|
1307
1356
|
return Object.fromEntries(Object.entries(PROVIDER_REGISTRY).filter(([_provider, config]) => config.gateway === name));
|
|
1308
1357
|
}
|
|
1309
1358
|
var gateways = [new NetlifyGateway(), new ModelsDevGateway(getStaticProvidersByGateway(`models.dev`))];
|
|
1310
|
-
|
|
1311
|
-
const prefixedGateway = gateways.find((g) => g.prefix && modelId.startsWith(`${g.prefix}/`));
|
|
1312
|
-
if (prefixedGateway) {
|
|
1313
|
-
return prefixedGateway;
|
|
1314
|
-
}
|
|
1315
|
-
const unprefixedGateways = gateways.filter((g) => !g.prefix);
|
|
1316
|
-
for (const gateway of unprefixedGateways) {
|
|
1317
|
-
return gateway;
|
|
1318
|
-
}
|
|
1319
|
-
return null;
|
|
1320
|
-
}
|
|
1321
|
-
async function resolveModelConfig(modelId, envVars = process.env) {
|
|
1322
|
-
const gateway = findGatewayForModel(modelId);
|
|
1323
|
-
if (!gateway) {
|
|
1324
|
-
return { url: false, headers: {}, resolvedModelId: modelId };
|
|
1325
|
-
}
|
|
1326
|
-
const url = await gateway.buildUrl(modelId, envVars);
|
|
1327
|
-
if (url === false) {
|
|
1328
|
-
return { url: false, headers: {}, resolvedModelId: modelId };
|
|
1329
|
-
}
|
|
1330
|
-
const headers = gateway.buildHeaders ? await gateway.buildHeaders(modelId, envVars) : {};
|
|
1331
|
-
let resolvedModelId = modelId;
|
|
1332
|
-
const prefix = gateway.prefix ? `${gateway.prefix}/` : null;
|
|
1333
|
-
if (prefix && resolvedModelId.startsWith(prefix)) {
|
|
1334
|
-
resolvedModelId = resolvedModelId.substring(prefix.length);
|
|
1335
|
-
}
|
|
1336
|
-
const firstSlashIndex = resolvedModelId.indexOf("/");
|
|
1337
|
-
if (firstSlashIndex !== -1) {
|
|
1338
|
-
resolvedModelId = resolvedModelId.substring(firstSlashIndex + 1);
|
|
1339
|
-
}
|
|
1340
|
-
return { url, headers, resolvedModelId };
|
|
1341
|
-
}
|
|
1342
|
-
|
|
1343
|
-
// src/llm/model/openai-compatible.ts
|
|
1344
|
-
function resolveApiKey({ provider, apiKey }) {
|
|
1345
|
-
if (apiKey) return apiKey;
|
|
1346
|
-
if (provider) {
|
|
1347
|
-
const config = getProviderConfig(provider);
|
|
1348
|
-
if (typeof config?.apiKeyEnvVar === `string`) {
|
|
1349
|
-
return process.env[config.apiKeyEnvVar];
|
|
1350
|
-
}
|
|
1351
|
-
if (Array.isArray(config?.apiKeyEnvVar)) {
|
|
1352
|
-
for (const key of config.apiKeyEnvVar) {
|
|
1353
|
-
if (process.env[key]) return process.env[key];
|
|
1354
|
-
}
|
|
1355
|
-
}
|
|
1356
|
-
}
|
|
1357
|
-
return void 0;
|
|
1358
|
-
}
|
|
1359
|
-
var OpenAICompatibleModel = class {
|
|
1359
|
+
var ModelRouterLanguageModel = class _ModelRouterLanguageModel {
|
|
1360
1360
|
specificationVersion = "v2";
|
|
1361
1361
|
defaultObjectGenerationMode = "json";
|
|
1362
1362
|
supportsStructuredOutputs = true;
|
|
@@ -1365,215 +1365,29 @@ var OpenAICompatibleModel = class {
|
|
|
1365
1365
|
modelId;
|
|
1366
1366
|
provider;
|
|
1367
1367
|
config;
|
|
1368
|
-
|
|
1369
|
-
// Store the full model ID for gateway resolution
|
|
1368
|
+
gateway;
|
|
1370
1369
|
constructor(config) {
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
}
|
|
1379
|
-
if (isUrl) {
|
|
1380
|
-
parsedConfig = {
|
|
1381
|
-
id: "unknown",
|
|
1382
|
-
url: config
|
|
1383
|
-
};
|
|
1384
|
-
this.provider = "openai-compatible";
|
|
1385
|
-
this.fullModelId = "unknown";
|
|
1386
|
-
this.config = { id: "unknown", url: config };
|
|
1387
|
-
} else {
|
|
1388
|
-
this.fullModelId = config;
|
|
1389
|
-
const firstSlashIndex = config.indexOf("/");
|
|
1390
|
-
if (firstSlashIndex !== -1) {
|
|
1391
|
-
const provider = config.substring(0, firstSlashIndex);
|
|
1392
|
-
const modelId = config.substring(firstSlashIndex + 1);
|
|
1393
|
-
parsedConfig = {
|
|
1394
|
-
id: modelId,
|
|
1395
|
-
apiKey: resolveApiKey({ provider })
|
|
1396
|
-
};
|
|
1397
|
-
this.provider = provider;
|
|
1398
|
-
} else {
|
|
1399
|
-
throw new Error(`Invalid model string: "${config}". Use "provider/model" format or a direct URL.`);
|
|
1400
|
-
}
|
|
1401
|
-
}
|
|
1402
|
-
} else {
|
|
1403
|
-
parsedConfig = config;
|
|
1404
|
-
this.fullModelId = config.id;
|
|
1405
|
-
const parsed = parseModelString(config.id);
|
|
1406
|
-
this.provider = parsed.provider || "openai-compatible";
|
|
1407
|
-
if (parsed.provider && parsed.modelId !== config.id) {
|
|
1408
|
-
parsedConfig.id = parsed.modelId;
|
|
1409
|
-
}
|
|
1410
|
-
if (!parsedConfig.apiKey) {
|
|
1411
|
-
parsedConfig.apiKey = resolveApiKey({ provider: parsed.provider || void 0 });
|
|
1412
|
-
}
|
|
1370
|
+
if (typeof config === `string`) config = { id: config };
|
|
1371
|
+
const parsedConfig = { ...config, routerId: config.id };
|
|
1372
|
+
this.gateway = findGatewayForModel(config.id, gateways);
|
|
1373
|
+
const parsed = parseModelRouterId(config.id, this.gateway.prefix);
|
|
1374
|
+
this.provider = parsed.providerId || "openai-compatible";
|
|
1375
|
+
if (parsed.providerId && parsed.modelId !== config.id) {
|
|
1376
|
+
parsedConfig.id = parsed.modelId;
|
|
1413
1377
|
}
|
|
1414
1378
|
this.modelId = parsedConfig.id;
|
|
1415
1379
|
this.config = parsedConfig;
|
|
1416
|
-
|
|
1417
|
-
convertMessagesToOpenAI(messages) {
|
|
1418
|
-
return messages.map((msg) => {
|
|
1419
|
-
if (msg.role === "system") {
|
|
1420
|
-
return {
|
|
1421
|
-
role: "system",
|
|
1422
|
-
content: msg.content
|
|
1423
|
-
};
|
|
1424
|
-
}
|
|
1425
|
-
if (msg.role === "user") {
|
|
1426
|
-
const contentParts = msg.content.map((part) => {
|
|
1427
|
-
if (part.type === "text") {
|
|
1428
|
-
return { type: "text", text: part.text };
|
|
1429
|
-
}
|
|
1430
|
-
if (part.type === "file") {
|
|
1431
|
-
return {
|
|
1432
|
-
type: "image_url",
|
|
1433
|
-
image_url: { url: part.data }
|
|
1434
|
-
};
|
|
1435
|
-
}
|
|
1436
|
-
return null;
|
|
1437
|
-
}).filter(Boolean);
|
|
1438
|
-
if (contentParts.every((p) => p?.type === "text")) {
|
|
1439
|
-
return {
|
|
1440
|
-
role: "user",
|
|
1441
|
-
content: contentParts.map((p) => p?.text || "").join("")
|
|
1442
|
-
};
|
|
1443
|
-
}
|
|
1444
|
-
return {
|
|
1445
|
-
role: "user",
|
|
1446
|
-
content: contentParts
|
|
1447
|
-
};
|
|
1448
|
-
}
|
|
1449
|
-
if (msg.role === "assistant") {
|
|
1450
|
-
const textContent = msg.content.filter((part) => part.type === "text").map((part) => part.text).join("");
|
|
1451
|
-
const toolCalls = msg.content.filter((part) => part.type === "tool-call").map((part) => ({
|
|
1452
|
-
id: part.toolCallId,
|
|
1453
|
-
type: "function",
|
|
1454
|
-
function: {
|
|
1455
|
-
name: part.toolName,
|
|
1456
|
-
arguments: JSON.stringify(part.input || {})
|
|
1457
|
-
}
|
|
1458
|
-
}));
|
|
1459
|
-
return {
|
|
1460
|
-
role: "assistant",
|
|
1461
|
-
content: textContent || null,
|
|
1462
|
-
...toolCalls.length > 0 && { tool_calls: toolCalls }
|
|
1463
|
-
};
|
|
1464
|
-
}
|
|
1465
|
-
if (msg.role === "tool") {
|
|
1466
|
-
return msg.content.map((toolResponse) => ({
|
|
1467
|
-
role: "tool",
|
|
1468
|
-
tool_call_id: toolResponse.toolCallId,
|
|
1469
|
-
content: JSON.stringify(toolResponse.output)
|
|
1470
|
-
}));
|
|
1471
|
-
}
|
|
1472
|
-
return msg;
|
|
1473
|
-
}).flat();
|
|
1474
|
-
}
|
|
1475
|
-
convertToolsToOpenAI(tools) {
|
|
1476
|
-
if (!tools || Object.keys(tools).length === 0) return void 0;
|
|
1477
|
-
return Object.entries(tools).map(([name, tool]) => {
|
|
1478
|
-
if (tool.type === "function") {
|
|
1479
|
-
return {
|
|
1480
|
-
type: "function",
|
|
1481
|
-
function: {
|
|
1482
|
-
name: tool.name,
|
|
1483
|
-
description: tool.description,
|
|
1484
|
-
parameters: tool.inputSchema || {}
|
|
1485
|
-
}
|
|
1486
|
-
};
|
|
1487
|
-
}
|
|
1488
|
-
return {
|
|
1489
|
-
type: "function",
|
|
1490
|
-
function: {
|
|
1491
|
-
name,
|
|
1492
|
-
description: `Provider tool: ${name}`,
|
|
1493
|
-
parameters: {}
|
|
1494
|
-
}
|
|
1495
|
-
};
|
|
1496
|
-
});
|
|
1497
|
-
}
|
|
1498
|
-
mapFinishReason(reason) {
|
|
1499
|
-
switch (reason) {
|
|
1500
|
-
case "stop":
|
|
1501
|
-
return "stop";
|
|
1502
|
-
case "length":
|
|
1503
|
-
case "max_tokens":
|
|
1504
|
-
return "length";
|
|
1505
|
-
case "tool_calls":
|
|
1506
|
-
case "function_call":
|
|
1507
|
-
return "tool-calls";
|
|
1508
|
-
case "content_filter":
|
|
1509
|
-
return "content-filter";
|
|
1510
|
-
default:
|
|
1511
|
-
return "unknown";
|
|
1512
|
-
}
|
|
1513
|
-
}
|
|
1514
|
-
/**
|
|
1515
|
-
* Resolve URL and headers for the request
|
|
1516
|
-
* This is called fresh for each request to ensure we get the latest values
|
|
1517
|
-
* (e.g., Netlify tokens can expire and need to be refreshed)
|
|
1518
|
-
*/
|
|
1519
|
-
async resolveRequestConfig() {
|
|
1520
|
-
const shouldUseGateway = !this.config.url;
|
|
1521
|
-
if (shouldUseGateway) {
|
|
1522
|
-
const { url, headers, resolvedModelId } = await resolveModelConfig(this.fullModelId);
|
|
1523
|
-
if (url === false) {
|
|
1524
|
-
throw new Error(`No gateway can handle model: ${this.fullModelId}`);
|
|
1525
|
-
}
|
|
1526
|
-
const finalHeaders = {
|
|
1527
|
-
"Content-Type": "application/json",
|
|
1528
|
-
...headers,
|
|
1529
|
-
...this.config.headers
|
|
1530
|
-
};
|
|
1531
|
-
return { url, headers: finalHeaders, modelId: resolvedModelId };
|
|
1532
|
-
} else {
|
|
1533
|
-
if (!this.config.url) {
|
|
1534
|
-
throw new Error("URL is required for OpenAI-compatible model");
|
|
1535
|
-
}
|
|
1536
|
-
const headers = {
|
|
1537
|
-
"Content-Type": "application/json",
|
|
1538
|
-
...this.config.headers
|
|
1539
|
-
};
|
|
1540
|
-
if (this.config.apiKey) {
|
|
1541
|
-
const providerConfig = this.provider !== "openai-compatible" ? getProviderConfig(this.provider) : void 0;
|
|
1542
|
-
if (providerConfig?.apiKeyHeader === "x-api-key") {
|
|
1543
|
-
headers["x-api-key"] = this.config.apiKey;
|
|
1544
|
-
} else {
|
|
1545
|
-
headers["Authorization"] = `Bearer ${this.config.apiKey}`;
|
|
1546
|
-
}
|
|
1547
|
-
}
|
|
1548
|
-
return { url: this.config.url, headers, modelId: this.modelId };
|
|
1549
|
-
}
|
|
1550
|
-
}
|
|
1551
|
-
validateApiKey() {
|
|
1552
|
-
const willUseGateway = !this.config.url;
|
|
1553
|
-
if (willUseGateway) {
|
|
1554
|
-
return;
|
|
1555
|
-
}
|
|
1556
|
-
if (!this.config.apiKey && this.provider !== "openai-compatible") {
|
|
1557
|
-
const providerConfig = getProviderConfig(this.provider);
|
|
1558
|
-
if (providerConfig?.apiKeyEnvVar) {
|
|
1559
|
-
throw new Error(
|
|
1560
|
-
`API key not found for provider "${this.provider}". Please set the ${providerConfig.apiKeyEnvVar} environment variable.`
|
|
1561
|
-
);
|
|
1562
|
-
} else {
|
|
1563
|
-
throw new Error(
|
|
1564
|
-
`API key not found for provider "${this.provider}". Please provide an API key in the configuration.`
|
|
1565
|
-
);
|
|
1566
|
-
}
|
|
1567
|
-
}
|
|
1380
|
+
this.gateway = findGatewayForModel(parsedConfig.routerId, gateways);
|
|
1568
1381
|
}
|
|
1569
1382
|
async doGenerate() {
|
|
1570
1383
|
throw new Error(
|
|
1571
|
-
"doGenerate is not supported by
|
|
1384
|
+
"doGenerate is not supported by Mastra model router. Mastra only uses streaming (doStream) for all LLM calls."
|
|
1572
1385
|
);
|
|
1573
1386
|
}
|
|
1574
1387
|
async doStream(options) {
|
|
1388
|
+
let apiKey;
|
|
1575
1389
|
try {
|
|
1576
|
-
this.
|
|
1390
|
+
apiKey = await this.gateway.getApiKey(this.config.routerId);
|
|
1577
1391
|
} catch (error) {
|
|
1578
1392
|
return {
|
|
1579
1393
|
stream: new ReadableStream({
|
|
@@ -1583,239 +1397,29 @@ var OpenAICompatibleModel = class {
|
|
|
1583
1397
|
error: error instanceof Error ? error.message : String(error)
|
|
1584
1398
|
});
|
|
1585
1399
|
}
|
|
1586
|
-
})
|
|
1587
|
-
warnings: []
|
|
1588
|
-
};
|
|
1589
|
-
}
|
|
1590
|
-
try {
|
|
1591
|
-
const { url, headers, modelId: resolvedModelId } = await this.resolveRequestConfig();
|
|
1592
|
-
const { prompt, tools, toolChoice, providerOptions } = options;
|
|
1593
|
-
const body = {
|
|
1594
|
-
messages: this.convertMessagesToOpenAI(prompt),
|
|
1595
|
-
model: resolvedModelId,
|
|
1596
|
-
stream: true,
|
|
1597
|
-
...providerOptions
|
|
1598
|
-
};
|
|
1599
|
-
const openAITools = this.convertToolsToOpenAI(tools);
|
|
1600
|
-
if (openAITools) {
|
|
1601
|
-
body.tools = openAITools;
|
|
1602
|
-
if (toolChoice) {
|
|
1603
|
-
body.tool_choice = toolChoice.type === "none" ? "none" : toolChoice.type === "required" ? "required" : toolChoice.type === "auto" ? "auto" : toolChoice.type === "tool" ? { type: "function", function: { name: toolChoice.toolName } } : "auto";
|
|
1604
|
-
}
|
|
1605
|
-
}
|
|
1606
|
-
if (options.responseFormat?.type === "json") {
|
|
1607
|
-
body.response_format = {
|
|
1608
|
-
type: "json_schema",
|
|
1609
|
-
json_schema: {
|
|
1610
|
-
name: "response",
|
|
1611
|
-
strict: true,
|
|
1612
|
-
schema: options.responseFormat.schema
|
|
1613
|
-
}
|
|
1614
|
-
};
|
|
1615
|
-
}
|
|
1616
|
-
const fetchArgs = {
|
|
1617
|
-
method: "POST",
|
|
1618
|
-
headers,
|
|
1619
|
-
body: JSON.stringify(body),
|
|
1620
|
-
signal: options.abortSignal
|
|
1621
|
-
};
|
|
1622
|
-
const response = await fetch(url, fetchArgs);
|
|
1623
|
-
if (!response.ok) {
|
|
1624
|
-
const error = await response.text();
|
|
1625
|
-
if (response.status === 401 || response.status === 403) {
|
|
1626
|
-
const providerConfig = getProviderConfig(this.provider);
|
|
1627
|
-
if (providerConfig?.apiKeyEnvVar) {
|
|
1628
|
-
throw new Error(
|
|
1629
|
-
`Authentication failed for provider "${this.provider}". Please ensure the ${providerConfig.apiKeyEnvVar} environment variable is set with a valid API key.`
|
|
1630
|
-
);
|
|
1631
|
-
}
|
|
1632
|
-
}
|
|
1633
|
-
throw new Error(`OpenAI-compatible API error: ${response.status} - ${error}`);
|
|
1634
|
-
}
|
|
1635
|
-
const reader = response.body?.getReader();
|
|
1636
|
-
if (!reader) {
|
|
1637
|
-
throw new Error("Response body is not readable");
|
|
1638
|
-
}
|
|
1639
|
-
const decoder = new TextDecoder();
|
|
1640
|
-
let buffer = "";
|
|
1641
|
-
let sentStart = false;
|
|
1642
|
-
const toolCallBuffers = /* @__PURE__ */ new Map();
|
|
1643
|
-
const mapFinishReason = this.mapFinishReason.bind(this);
|
|
1644
|
-
const modelId = this.modelId;
|
|
1645
|
-
let isActiveText = false;
|
|
1646
|
-
const stream = new ReadableStream({
|
|
1647
|
-
async start(controller) {
|
|
1648
|
-
try {
|
|
1649
|
-
controller.enqueue({
|
|
1650
|
-
type: "stream-start",
|
|
1651
|
-
warnings: []
|
|
1652
|
-
});
|
|
1653
|
-
while (true) {
|
|
1654
|
-
const { done, value } = await reader.read();
|
|
1655
|
-
if (done) {
|
|
1656
|
-
for (const [_, toolCall] of toolCallBuffers) {
|
|
1657
|
-
if (!toolCall.sent && toolCall.id && toolCall.name && toolCall.args) {
|
|
1658
|
-
controller.enqueue({
|
|
1659
|
-
type: "tool-call",
|
|
1660
|
-
toolCallId: toolCall.id,
|
|
1661
|
-
toolName: toolCall.name,
|
|
1662
|
-
input: toolCall.args || "{}"
|
|
1663
|
-
});
|
|
1664
|
-
}
|
|
1665
|
-
}
|
|
1666
|
-
controller.close();
|
|
1667
|
-
break;
|
|
1668
|
-
}
|
|
1669
|
-
buffer += decoder.decode(value, { stream: true });
|
|
1670
|
-
const lines = buffer.split("\n");
|
|
1671
|
-
buffer = lines.pop() || "";
|
|
1672
|
-
for (const line of lines) {
|
|
1673
|
-
if (line.trim() === "" || line.trim() === "data: [DONE]") {
|
|
1674
|
-
continue;
|
|
1675
|
-
}
|
|
1676
|
-
if (line.startsWith("data: ")) {
|
|
1677
|
-
try {
|
|
1678
|
-
const data = JSON.parse(line.slice(6));
|
|
1679
|
-
if (!sentStart && data.id) {
|
|
1680
|
-
controller.enqueue({
|
|
1681
|
-
type: "response-metadata",
|
|
1682
|
-
id: data.id,
|
|
1683
|
-
modelId: data.model || modelId,
|
|
1684
|
-
timestamp: new Date(data.created ? data.created * 1e3 : Date.now())
|
|
1685
|
-
});
|
|
1686
|
-
sentStart = true;
|
|
1687
|
-
}
|
|
1688
|
-
const choice = data.choices?.[0];
|
|
1689
|
-
if (!choice) continue;
|
|
1690
|
-
if (choice.delta?.content) {
|
|
1691
|
-
if (!isActiveText) {
|
|
1692
|
-
controller.enqueue({ type: "text-start", id: "text-1" });
|
|
1693
|
-
isActiveText = true;
|
|
1694
|
-
}
|
|
1695
|
-
controller.enqueue({
|
|
1696
|
-
type: "text-delta",
|
|
1697
|
-
id: "text-1",
|
|
1698
|
-
delta: choice.delta.content
|
|
1699
|
-
});
|
|
1700
|
-
} else if (isActiveText) {
|
|
1701
|
-
controller.enqueue({ type: "text-end", id: "text-1" });
|
|
1702
|
-
isActiveText = false;
|
|
1703
|
-
}
|
|
1704
|
-
if (choice.delta?.tool_calls) {
|
|
1705
|
-
for (const toolCall of choice.delta.tool_calls) {
|
|
1706
|
-
const index = toolCall.index;
|
|
1707
|
-
if (!toolCallBuffers.has(index)) {
|
|
1708
|
-
if (toolCall.id && toolCall.function?.name) {
|
|
1709
|
-
controller.enqueue({
|
|
1710
|
-
type: "tool-input-start",
|
|
1711
|
-
id: toolCall.id,
|
|
1712
|
-
toolName: toolCall.function.name
|
|
1713
|
-
});
|
|
1714
|
-
}
|
|
1715
|
-
toolCallBuffers.set(index, {
|
|
1716
|
-
id: toolCall.id || "",
|
|
1717
|
-
name: toolCall.function?.name || "",
|
|
1718
|
-
args: ""
|
|
1719
|
-
});
|
|
1720
|
-
}
|
|
1721
|
-
const buffer2 = toolCallBuffers.get(index);
|
|
1722
|
-
if (toolCall.id) {
|
|
1723
|
-
buffer2.id = toolCall.id;
|
|
1724
|
-
}
|
|
1725
|
-
if (toolCall.function?.name) {
|
|
1726
|
-
buffer2.name = toolCall.function.name;
|
|
1727
|
-
}
|
|
1728
|
-
if (toolCall.function?.arguments) {
|
|
1729
|
-
buffer2.args += toolCall.function.arguments;
|
|
1730
|
-
controller.enqueue({
|
|
1731
|
-
type: "tool-input-delta",
|
|
1732
|
-
id: buffer2.id,
|
|
1733
|
-
delta: toolCall.function.arguments
|
|
1734
|
-
});
|
|
1735
|
-
try {
|
|
1736
|
-
JSON.parse(buffer2.args);
|
|
1737
|
-
if (buffer2.id && buffer2.name) {
|
|
1738
|
-
controller.enqueue({
|
|
1739
|
-
type: "tool-input-end",
|
|
1740
|
-
id: buffer2.id
|
|
1741
|
-
});
|
|
1742
|
-
controller.enqueue({
|
|
1743
|
-
type: "tool-call",
|
|
1744
|
-
toolCallId: buffer2.id,
|
|
1745
|
-
toolName: buffer2.name,
|
|
1746
|
-
input: buffer2.args
|
|
1747
|
-
});
|
|
1748
|
-
toolCallBuffers.set(index, {
|
|
1749
|
-
id: buffer2.id,
|
|
1750
|
-
name: buffer2.name,
|
|
1751
|
-
args: buffer2.args,
|
|
1752
|
-
sent: true
|
|
1753
|
-
});
|
|
1754
|
-
}
|
|
1755
|
-
} catch {
|
|
1756
|
-
}
|
|
1757
|
-
}
|
|
1758
|
-
}
|
|
1759
|
-
}
|
|
1760
|
-
if (choice.finish_reason) {
|
|
1761
|
-
toolCallBuffers.clear();
|
|
1762
|
-
controller.enqueue({
|
|
1763
|
-
type: "finish",
|
|
1764
|
-
finishReason: mapFinishReason(choice.finish_reason),
|
|
1765
|
-
usage: data.usage ? {
|
|
1766
|
-
inputTokens: data.usage.prompt_tokens || 0,
|
|
1767
|
-
outputTokens: data.usage.completion_tokens || 0,
|
|
1768
|
-
totalTokens: data.usage.total_tokens || 0
|
|
1769
|
-
} : {
|
|
1770
|
-
inputTokens: 0,
|
|
1771
|
-
outputTokens: 0,
|
|
1772
|
-
totalTokens: 0
|
|
1773
|
-
}
|
|
1774
|
-
});
|
|
1775
|
-
}
|
|
1776
|
-
} catch (e) {
|
|
1777
|
-
console.error("Error parsing SSE data:", e);
|
|
1778
|
-
}
|
|
1779
|
-
}
|
|
1780
|
-
}
|
|
1781
|
-
}
|
|
1782
|
-
} catch (error) {
|
|
1783
|
-
return {
|
|
1784
|
-
stream: new ReadableStream({
|
|
1785
|
-
start(controller2) {
|
|
1786
|
-
controller2.enqueue({
|
|
1787
|
-
type: "error",
|
|
1788
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1789
|
-
});
|
|
1790
|
-
}
|
|
1791
|
-
}),
|
|
1792
|
-
warnings: []
|
|
1793
|
-
};
|
|
1794
|
-
}
|
|
1795
|
-
}
|
|
1796
|
-
});
|
|
1797
|
-
return {
|
|
1798
|
-
stream,
|
|
1799
|
-
request: { body: JSON.stringify(body) },
|
|
1800
|
-
response: { headers: Object.fromEntries(response.headers.entries()) },
|
|
1801
|
-
warnings: []
|
|
1802
|
-
};
|
|
1803
|
-
} catch (error) {
|
|
1804
|
-
return {
|
|
1805
|
-
stream: new ReadableStream({
|
|
1806
|
-
start(controller) {
|
|
1807
|
-
controller.enqueue({
|
|
1808
|
-
type: "error",
|
|
1809
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1810
|
-
});
|
|
1811
|
-
}
|
|
1812
|
-
}),
|
|
1813
|
-
warnings: []
|
|
1400
|
+
})
|
|
1814
1401
|
};
|
|
1815
1402
|
}
|
|
1403
|
+
const model = await this.resolveLanguageModel({
|
|
1404
|
+
apiKey,
|
|
1405
|
+
...parseModelRouterId(this.config.routerId, this.gateway.prefix)
|
|
1406
|
+
});
|
|
1407
|
+
return model.doStream(options);
|
|
1408
|
+
}
|
|
1409
|
+
async resolveLanguageModel({
|
|
1410
|
+
modelId,
|
|
1411
|
+
providerId,
|
|
1412
|
+
apiKey
|
|
1413
|
+
}) {
|
|
1414
|
+
const key = createHash("sha256").update(this.gateway.name + modelId + providerId + apiKey).digest("hex");
|
|
1415
|
+
if (_ModelRouterLanguageModel.modelInstances.has(key)) return _ModelRouterLanguageModel.modelInstances.get(key);
|
|
1416
|
+
const modelInstance = await this.gateway.resolveLanguageModel({ modelId, providerId, apiKey });
|
|
1417
|
+
_ModelRouterLanguageModel.modelInstances.set(key, modelInstance);
|
|
1418
|
+
return modelInstance;
|
|
1816
1419
|
}
|
|
1420
|
+
static modelInstances = /* @__PURE__ */ new Map();
|
|
1817
1421
|
};
|
|
1818
1422
|
|
|
1819
|
-
export {
|
|
1820
|
-
//# sourceMappingURL=chunk-
|
|
1821
|
-
//# sourceMappingURL=chunk-
|
|
1423
|
+
export { ModelRouterLanguageModel, PROVIDER_REGISTRY, getProviderConfig, parseModelString };
|
|
1424
|
+
//# sourceMappingURL=chunk-W2WXYTYI.js.map
|
|
1425
|
+
//# sourceMappingURL=chunk-W2WXYTYI.js.map
|