@asm-agent/ai 0.8.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/README.md +1333 -0
- package/dist/api-registry.d.ts +20 -0
- package/dist/api-registry.d.ts.map +1 -0
- package/dist/api-registry.js +44 -0
- package/dist/api-registry.js.map +1 -0
- package/dist/bedrock-provider.d.ts +5 -0
- package/dist/bedrock-provider.d.ts.map +1 -0
- package/dist/bedrock-provider.js +6 -0
- package/dist/bedrock-provider.js.map +1 -0
- package/dist/cache-pricing.d.ts +13 -0
- package/dist/cache-pricing.d.ts.map +1 -0
- package/dist/cache-pricing.js +38 -0
- package/dist/cache-pricing.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +116 -0
- package/dist/cli.js.map +1 -0
- package/dist/env-api-keys.d.ts +19 -0
- package/dist/env-api-keys.d.ts.map +1 -0
- package/dist/env-api-keys.js +180 -0
- package/dist/env-api-keys.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/log.d.ts +27 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +69 -0
- package/dist/log.js.map +1 -0
- package/dist/mcp/catalog.d.ts +19 -0
- package/dist/mcp/catalog.d.ts.map +1 -0
- package/dist/mcp/catalog.js +42 -0
- package/dist/mcp/catalog.js.map +1 -0
- package/dist/mcp/index.d.ts +5 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +3 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/oauth.d.ts +15 -0
- package/dist/mcp/oauth.d.ts.map +1 -0
- package/dist/mcp/oauth.js +489 -0
- package/dist/mcp/oauth.js.map +1 -0
- package/dist/mcp.d.ts +2 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +2 -0
- package/dist/mcp.js.map +1 -0
- package/dist/models.d.ts +18 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.generated.d.ts +26696 -0
- package/dist/models.generated.d.ts.map +1 -0
- package/dist/models.generated.js +22192 -0
- package/dist/models.generated.js.map +1 -0
- package/dist/models.js +72 -0
- package/dist/models.js.map +1 -0
- package/dist/oauth.d.ts +2 -0
- package/dist/oauth.d.ts.map +1 -0
- package/dist/oauth.js +2 -0
- package/dist/oauth.js.map +1 -0
- package/dist/openrouter-reasoning.d.ts +11 -0
- package/dist/openrouter-reasoning.d.ts.map +1 -0
- package/dist/openrouter-reasoning.js +52 -0
- package/dist/openrouter-reasoning.js.map +1 -0
- package/dist/providers/amazon-bedrock.d.ts +38 -0
- package/dist/providers/amazon-bedrock.d.ts.map +1 -0
- package/dist/providers/amazon-bedrock.js +767 -0
- package/dist/providers/amazon-bedrock.js.map +1 -0
- package/dist/providers/anthropic.d.ts +54 -0
- package/dist/providers/anthropic.d.ts.map +1 -0
- package/dist/providers/anthropic.js +999 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/azure-openai-responses.d.ts +12 -0
- package/dist/providers/azure-openai-responses.d.ts.map +1 -0
- package/dist/providers/azure-openai-responses.js +206 -0
- package/dist/providers/azure-openai-responses.js.map +1 -0
- package/dist/providers/cloudflare.d.ts +10 -0
- package/dist/providers/cloudflare.d.ts.map +1 -0
- package/dist/providers/cloudflare.js +23 -0
- package/dist/providers/cloudflare.js.map +1 -0
- package/dist/providers/faux.d.ts +56 -0
- package/dist/providers/faux.d.ts.map +1 -0
- package/dist/providers/faux.js +368 -0
- package/dist/providers/faux.js.map +1 -0
- package/dist/providers/github-copilot-headers.d.ts +8 -0
- package/dist/providers/github-copilot-headers.d.ts.map +1 -0
- package/dist/providers/github-copilot-headers.js +26 -0
- package/dist/providers/github-copilot-headers.js.map +1 -0
- package/dist/providers/google-shared.d.ts +55 -0
- package/dist/providers/google-shared.d.ts.map +1 -0
- package/dist/providers/google-shared.js +312 -0
- package/dist/providers/google-shared.js.map +1 -0
- package/dist/providers/google-vertex.d.ts +15 -0
- package/dist/providers/google-vertex.d.ts.map +1 -0
- package/dist/providers/google-vertex.js +421 -0
- package/dist/providers/google-vertex.js.map +1 -0
- package/dist/providers/google.d.ts +13 -0
- package/dist/providers/google.d.ts.map +1 -0
- package/dist/providers/google.js +370 -0
- package/dist/providers/google.js.map +1 -0
- package/dist/providers/mistral.d.ts +20 -0
- package/dist/providers/mistral.d.ts.map +1 -0
- package/dist/providers/mistral.js +536 -0
- package/dist/providers/mistral.js.map +1 -0
- package/dist/providers/openai-codex-responses.d.ts +30 -0
- package/dist/providers/openai-codex-responses.d.ts.map +1 -0
- package/dist/providers/openai-codex-responses.js +1013 -0
- package/dist/providers/openai-codex-responses.js.map +1 -0
- package/dist/providers/openai-completions.d.ts +21 -0
- package/dist/providers/openai-completions.d.ts.map +1 -0
- package/dist/providers/openai-completions.js +1019 -0
- package/dist/providers/openai-completions.js.map +1 -0
- package/dist/providers/openai-responses-shared.d.ts +18 -0
- package/dist/providers/openai-responses-shared.d.ts.map +1 -0
- package/dist/providers/openai-responses-shared.js +488 -0
- package/dist/providers/openai-responses-shared.js.map +1 -0
- package/dist/providers/openai-responses.d.ts +10 -0
- package/dist/providers/openai-responses.d.ts.map +1 -0
- package/dist/providers/openai-responses.js +218 -0
- package/dist/providers/openai-responses.js.map +1 -0
- package/dist/providers/register-builtins.d.ts +35 -0
- package/dist/providers/register-builtins.d.ts.map +1 -0
- package/dist/providers/register-builtins.js +243 -0
- package/dist/providers/register-builtins.js.map +1 -0
- package/dist/providers/simple-options.d.ts +8 -0
- package/dist/providers/simple-options.d.ts.map +1 -0
- package/dist/providers/simple-options.js +40 -0
- package/dist/providers/simple-options.js.map +1 -0
- package/dist/providers/transform-messages.d.ts +8 -0
- package/dist/providers/transform-messages.d.ts.map +1 -0
- package/dist/providers/transform-messages.js +174 -0
- package/dist/providers/transform-messages.js.map +1 -0
- package/dist/session-resources.d.ts +4 -0
- package/dist/session-resources.d.ts.map +1 -0
- package/dist/session-resources.js +22 -0
- package/dist/session-resources.js.map +1 -0
- package/dist/stream.d.ts +8 -0
- package/dist/stream.d.ts.map +1 -0
- package/dist/stream.js +27 -0
- package/dist/stream.js.map +1 -0
- package/dist/types.d.ts +411 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/diagnostics.d.ts +19 -0
- package/dist/utils/diagnostics.d.ts.map +1 -0
- package/dist/utils/diagnostics.js +25 -0
- package/dist/utils/diagnostics.js.map +1 -0
- package/dist/utils/event-stream.d.ts +21 -0
- package/dist/utils/event-stream.d.ts.map +1 -0
- package/dist/utils/event-stream.js +78 -0
- package/dist/utils/event-stream.js.map +1 -0
- package/dist/utils/hash.d.ts +2 -0
- package/dist/utils/hash.d.ts.map +1 -0
- package/dist/utils/hash.js +13 -0
- package/dist/utils/hash.js.map +1 -0
- package/dist/utils/headers.d.ts +2 -0
- package/dist/utils/headers.d.ts.map +1 -0
- package/dist/utils/headers.js +8 -0
- package/dist/utils/headers.js.map +1 -0
- package/dist/utils/json-parse.d.ts +16 -0
- package/dist/utils/json-parse.d.ts.map +1 -0
- package/dist/utils/json-parse.js +113 -0
- package/dist/utils/json-parse.js.map +1 -0
- package/dist/utils/oauth/anthropic.d.ts +25 -0
- package/dist/utils/oauth/anthropic.d.ts.map +1 -0
- package/dist/utils/oauth/anthropic.js +335 -0
- package/dist/utils/oauth/anthropic.js.map +1 -0
- package/dist/utils/oauth/github-copilot.d.ts +24 -0
- package/dist/utils/oauth/github-copilot.d.ts.map +1 -0
- package/dist/utils/oauth/github-copilot.js +279 -0
- package/dist/utils/oauth/github-copilot.js.map +1 -0
- package/dist/utils/oauth/index.d.ts +45 -0
- package/dist/utils/oauth/index.d.ts.map +1 -0
- package/dist/utils/oauth/index.js +99 -0
- package/dist/utils/oauth/index.js.map +1 -0
- package/dist/utils/oauth/oauth-page.d.ts +3 -0
- package/dist/utils/oauth/oauth-page.d.ts.map +1 -0
- package/dist/utils/oauth/oauth-page.js +134 -0
- package/dist/utils/oauth/oauth-page.js.map +1 -0
- package/dist/utils/oauth/openai-codex.d.ts +34 -0
- package/dist/utils/oauth/openai-codex.d.ts.map +1 -0
- package/dist/utils/oauth/openai-codex.js +385 -0
- package/dist/utils/oauth/openai-codex.js.map +1 -0
- package/dist/utils/oauth/pkce.d.ts +13 -0
- package/dist/utils/oauth/pkce.d.ts.map +1 -0
- package/dist/utils/oauth/pkce.js +26 -0
- package/dist/utils/oauth/pkce.js.map +1 -0
- package/dist/utils/oauth/types.d.ts +57 -0
- package/dist/utils/oauth/types.d.ts.map +1 -0
- package/dist/utils/oauth/types.js +2 -0
- package/dist/utils/oauth/types.js.map +1 -0
- package/dist/utils/overflow.d.ts +55 -0
- package/dist/utils/overflow.d.ts.map +1 -0
- package/dist/utils/overflow.js +144 -0
- package/dist/utils/overflow.js.map +1 -0
- package/dist/utils/sanitize-unicode.d.ts +22 -0
- package/dist/utils/sanitize-unicode.d.ts.map +1 -0
- package/dist/utils/sanitize-unicode.js +26 -0
- package/dist/utils/sanitize-unicode.js.map +1 -0
- package/dist/utils/stream-failure.d.ts +55 -0
- package/dist/utils/stream-failure.d.ts.map +1 -0
- package/dist/utils/stream-failure.js +179 -0
- package/dist/utils/stream-failure.js.map +1 -0
- package/dist/utils/typebox-helpers.d.ts +17 -0
- package/dist/utils/typebox-helpers.d.ts.map +1 -0
- package/dist/utils/typebox-helpers.js +21 -0
- package/dist/utils/typebox-helpers.js.map +1 -0
- package/dist/utils/validation.d.ts +18 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +281 -0
- package/dist/utils/validation.js.map +1 -0
- package/package.json +113 -0
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
import { oauthErrorHtml, oauthSuccessHtml } from "../utils/oauth/oauth-page.js";
|
|
2
|
+
import { generatePKCE } from "../utils/oauth/pkce.js";
|
|
3
|
+
const CALLBACK_HOST = process.env.PI_OAUTH_CALLBACK_HOST || "127.0.0.1";
|
|
4
|
+
// A range (not one port) so a leaked/concurrent login can't wedge all logins with EADDRINUSE.
|
|
5
|
+
// Distinct from the Anthropic callback port (53692). All candidates are registered as redirect URIs.
|
|
6
|
+
const CALLBACK_PORT_BASE = Number(process.env.PI_MCP_OAUTH_CALLBACK_PORT || 53700);
|
|
7
|
+
const CALLBACK_PORT_COUNT = 10;
|
|
8
|
+
const CALLBACK_PATH = "/callback";
|
|
9
|
+
const CALLBACK_PORTS = Array.from({ length: CALLBACK_PORT_COUNT }, (_, i) => CALLBACK_PORT_BASE + i);
|
|
10
|
+
const redirectUriFor = (port) => `http://localhost:${port}${CALLBACK_PATH}`;
|
|
11
|
+
const ALL_REDIRECT_URIS = CALLBACK_PORTS.map(redirectUriFor);
|
|
12
|
+
const TOKEN_EXPIRY_BUFFER_MS = 5 * 60 * 1000;
|
|
13
|
+
function validatedHttpsUrl(value, name) {
|
|
14
|
+
let url;
|
|
15
|
+
try {
|
|
16
|
+
url = new URL(value);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
throw new Error(`${name} must be an absolute HTTPS URL`);
|
|
20
|
+
}
|
|
21
|
+
if (url.protocol !== "https:" || url.username || url.password || url.hash) {
|
|
22
|
+
throw new Error(`${name} must be an absolute HTTPS URL without credentials or a fragment`);
|
|
23
|
+
}
|
|
24
|
+
return url;
|
|
25
|
+
}
|
|
26
|
+
function canonicalResource(url) {
|
|
27
|
+
if (url.pathname === "/" && !url.search)
|
|
28
|
+
return url.origin;
|
|
29
|
+
return `${url.origin}${url.pathname}${url.search}`;
|
|
30
|
+
}
|
|
31
|
+
function authorizationServerMetadataUrls(issuer) {
|
|
32
|
+
const url = validatedHttpsUrl(issuer, "Authorization server issuer");
|
|
33
|
+
if (url.search)
|
|
34
|
+
throw new Error("Authorization server issuer must not contain a query string");
|
|
35
|
+
const path = url.pathname === "/" ? "" : url.pathname.replace(/\/$/, "");
|
|
36
|
+
return [
|
|
37
|
+
new URL(`/.well-known/oauth-authorization-server${path}`, url.origin).toString(),
|
|
38
|
+
new URL(`${path}/.well-known/openid-configuration`, url.origin).toString(),
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
async function fetchResponse(url, init) {
|
|
42
|
+
return fetch(url, { ...init, redirect: "error" });
|
|
43
|
+
}
|
|
44
|
+
async function fetchJson(url, init) {
|
|
45
|
+
const res = await fetchResponse(url, init);
|
|
46
|
+
if (!res.ok) {
|
|
47
|
+
throw new Error(`${init?.method ?? "GET"} ${url} failed: ${res.status}`);
|
|
48
|
+
}
|
|
49
|
+
return res.json();
|
|
50
|
+
}
|
|
51
|
+
function authorizationServerMetadata(value, issuer, requireExactIssuer) {
|
|
52
|
+
if (!value || typeof value !== "object")
|
|
53
|
+
throw new Error(`Authorization server metadata for ${issuer} is invalid`);
|
|
54
|
+
const metadata = value;
|
|
55
|
+
if (typeof metadata.issuer !== "string") {
|
|
56
|
+
throw new Error(`Authorization server metadata for ${issuer} is missing its issuer`);
|
|
57
|
+
}
|
|
58
|
+
if (requireExactIssuer) {
|
|
59
|
+
if (metadata.issuer !== issuer) {
|
|
60
|
+
throw new Error(`Authorization server metadata issuer does not exactly match ${issuer}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
const advertisedIssuer = validatedHttpsUrl(metadata.issuer, "Authorization server metadata issuer");
|
|
65
|
+
if (advertisedIssuer.origin !== new URL(issuer).origin || advertisedIssuer.search) {
|
|
66
|
+
throw new Error(`Origin authorization server metadata issuer must stay on ${new URL(issuer).origin}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (typeof metadata.authorization_endpoint !== "string" || typeof metadata.token_endpoint !== "string") {
|
|
70
|
+
throw new Error(`Authorization server metadata for ${issuer} is missing required endpoints`);
|
|
71
|
+
}
|
|
72
|
+
validatedHttpsUrl(metadata.authorization_endpoint, "Authorization endpoint");
|
|
73
|
+
validatedHttpsUrl(metadata.token_endpoint, "Token endpoint");
|
|
74
|
+
if (metadata.registration_endpoint)
|
|
75
|
+
validatedHttpsUrl(metadata.registration_endpoint, "Registration endpoint");
|
|
76
|
+
return metadata;
|
|
77
|
+
}
|
|
78
|
+
async function jsonMetadata(response, url) {
|
|
79
|
+
if (response.status !== 200)
|
|
80
|
+
throw new Error(`GET ${url} failed: ${response.status}`);
|
|
81
|
+
const contentType = response.headers.get("content-type")?.split(";", 1)[0].trim().toLowerCase();
|
|
82
|
+
if (contentType !== "application/json")
|
|
83
|
+
throw new Error(`GET ${url} did not return application/json`);
|
|
84
|
+
return response.json();
|
|
85
|
+
}
|
|
86
|
+
async function discoverAuthorizationServer(issuer, requireExactIssuer) {
|
|
87
|
+
const candidates = authorizationServerMetadataUrls(issuer);
|
|
88
|
+
let lastError;
|
|
89
|
+
for (const candidate of candidates) {
|
|
90
|
+
try {
|
|
91
|
+
const response = await fetchResponse(candidate);
|
|
92
|
+
if (response.status === 404)
|
|
93
|
+
continue;
|
|
94
|
+
return authorizationServerMetadata(await jsonMetadata(response, candidate), issuer, requireExactIssuer);
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
lastError = error;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
throw new Error(`Could not discover OAuth metadata for ${issuer}. Tried ${candidates.join(", ")}. Last error: ${String(lastError)}`);
|
|
101
|
+
}
|
|
102
|
+
/** Random, URL-safe CSRF `state` value, independent of the PKCE verifier. */
|
|
103
|
+
function randomState() {
|
|
104
|
+
const bytes = new Uint8Array(32);
|
|
105
|
+
crypto.getRandomValues(bytes);
|
|
106
|
+
return btoa(String.fromCharCode(...bytes))
|
|
107
|
+
.replace(/\+/g, "-")
|
|
108
|
+
.replace(/\//g, "_")
|
|
109
|
+
.replace(/=/g, "");
|
|
110
|
+
}
|
|
111
|
+
function resourceMetadata(value, resource) {
|
|
112
|
+
if (!value || typeof value !== "object")
|
|
113
|
+
throw new Error("Protected-resource metadata is invalid");
|
|
114
|
+
const metadata = value;
|
|
115
|
+
if (metadata.resource !== resource)
|
|
116
|
+
throw new Error(`Protected-resource metadata resource does not exactly match ${resource}`);
|
|
117
|
+
if (!Array.isArray(metadata.authorization_servers) || metadata.authorization_servers.length === 0) {
|
|
118
|
+
throw new Error("Protected-resource metadata has no authorization_servers");
|
|
119
|
+
}
|
|
120
|
+
for (const issuer of metadata.authorization_servers) {
|
|
121
|
+
if (typeof issuer !== "string")
|
|
122
|
+
throw new Error("Protected-resource metadata has an invalid authorization server");
|
|
123
|
+
validatedHttpsUrl(issuer, "Authorization server issuer");
|
|
124
|
+
}
|
|
125
|
+
return metadata;
|
|
126
|
+
}
|
|
127
|
+
function resourceMetadataUrl(resource) {
|
|
128
|
+
const path = resource.pathname === "/" ? "" : resource.pathname;
|
|
129
|
+
return `${resource.origin}/.well-known/oauth-protected-resource${path}${resource.search}`;
|
|
130
|
+
}
|
|
131
|
+
function headerResourceMetadata(value) {
|
|
132
|
+
const match = value?.match(/(?:^|[,\s])resource_metadata\s*=\s*"((?:[^"\\]|\\.)*)"/i);
|
|
133
|
+
if (!match)
|
|
134
|
+
return undefined;
|
|
135
|
+
return match[1].replace(/\\(.)/g, "$1");
|
|
136
|
+
}
|
|
137
|
+
async function tryProtectedResourceMetadata(url) {
|
|
138
|
+
const resource = validatedHttpsUrl(url, "MCP endpoint");
|
|
139
|
+
let headerUrl;
|
|
140
|
+
try {
|
|
141
|
+
// This probe deliberately has no Authorization header. It must not leak an existing token.
|
|
142
|
+
const response = await fetchResponse(resource.toString());
|
|
143
|
+
headerUrl = headerResourceMetadata(response.headers.get("www-authenticate"));
|
|
144
|
+
await response.body?.cancel();
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// The server need not support a GET probe; use the RFC well-known locations below.
|
|
148
|
+
}
|
|
149
|
+
const candidate = headerUrl
|
|
150
|
+
? validatedHttpsUrl(headerUrl, "resource_metadata").toString()
|
|
151
|
+
: resourceMetadataUrl(resource);
|
|
152
|
+
const response = await fetchResponse(candidate);
|
|
153
|
+
if (response.status === 404 && !headerUrl)
|
|
154
|
+
return undefined;
|
|
155
|
+
return resourceMetadata(await jsonMetadata(response, candidate), canonicalResource(resource));
|
|
156
|
+
}
|
|
157
|
+
/** Discover RFC 9728 protected-resource metadata before the origin-level authorization server fallback. */
|
|
158
|
+
async function discover(url) {
|
|
159
|
+
const protectedResource = await tryProtectedResourceMetadata(url);
|
|
160
|
+
if (protectedResource) {
|
|
161
|
+
const issuer = protectedResource.authorization_servers[0];
|
|
162
|
+
return {
|
|
163
|
+
metadata: await discoverAuthorizationServer(issuer, true),
|
|
164
|
+
resource: protectedResource.resource,
|
|
165
|
+
issuer,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
const issuer = validatedHttpsUrl(url, "MCP endpoint").origin;
|
|
169
|
+
return { metadata: await discoverAuthorizationServer(issuer, false) };
|
|
170
|
+
}
|
|
171
|
+
async function registerClient(registrationEndpoint, label) {
|
|
172
|
+
validatedHttpsUrl(registrationEndpoint, "Registration endpoint");
|
|
173
|
+
const body = {
|
|
174
|
+
client_name: label,
|
|
175
|
+
redirect_uris: ALL_REDIRECT_URIS,
|
|
176
|
+
grant_types: ["authorization_code", "refresh_token"],
|
|
177
|
+
response_types: ["code"],
|
|
178
|
+
token_endpoint_auth_method: "none",
|
|
179
|
+
};
|
|
180
|
+
const data = (await fetchJson(registrationEndpoint, {
|
|
181
|
+
method: "POST",
|
|
182
|
+
headers: { "Content-Type": "application/json" },
|
|
183
|
+
body: JSON.stringify(body),
|
|
184
|
+
}));
|
|
185
|
+
if (typeof data.client_id !== "string" || !data.client_id) {
|
|
186
|
+
throw new Error(`Dynamic client registration at ${registrationEndpoint} returned no client_id`);
|
|
187
|
+
}
|
|
188
|
+
return data.client_id;
|
|
189
|
+
}
|
|
190
|
+
async function startCallbackServer(label) {
|
|
191
|
+
const { createServer } = await import("node:http");
|
|
192
|
+
let settle;
|
|
193
|
+
const waitPromise = new Promise((resolve) => {
|
|
194
|
+
let settled = false;
|
|
195
|
+
settle = (value) => {
|
|
196
|
+
if (!settled) {
|
|
197
|
+
settled = true;
|
|
198
|
+
resolve(value);
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
});
|
|
202
|
+
const handler = (req, res) => {
|
|
203
|
+
const url = new URL(req.url || "", "http://localhost");
|
|
204
|
+
if (url.pathname !== CALLBACK_PATH) {
|
|
205
|
+
res.writeHead(404, { "Content-Type": "text/html; charset=utf-8" });
|
|
206
|
+
res.end(oauthErrorHtml("Callback route not found."));
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
const error = url.searchParams.get("error");
|
|
210
|
+
const code = url.searchParams.get("code");
|
|
211
|
+
const state = url.searchParams.get("state");
|
|
212
|
+
res.writeHead(error || !code ? 400 : 200, { "Content-Type": "text/html; charset=utf-8" });
|
|
213
|
+
if (error) {
|
|
214
|
+
res.end(oauthErrorHtml(`${label} authentication failed.`, `Error: ${error}`));
|
|
215
|
+
settle?.(null);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
if (!code || !state) {
|
|
219
|
+
res.end(oauthErrorHtml("Missing code or state parameter."));
|
|
220
|
+
settle?.(null);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
res.end(oauthSuccessHtml(`${label} authentication completed. You can close this window.`));
|
|
224
|
+
settle?.({ code, state });
|
|
225
|
+
};
|
|
226
|
+
// Try each candidate port with a FRESH server (a server that failed to listen
|
|
227
|
+
// can't be reused), so a leaked/concurrent login can't block us with EADDRINUSE.
|
|
228
|
+
let lastError;
|
|
229
|
+
for (const port of CALLBACK_PORTS) {
|
|
230
|
+
const server = createServer(handler);
|
|
231
|
+
// Persistent handler so a post-bind 'error' is never an unhandled crash.
|
|
232
|
+
let bindErr;
|
|
233
|
+
server.on("error", (err) => bindErr?.(err));
|
|
234
|
+
try {
|
|
235
|
+
const bound = await new Promise((resolve) => {
|
|
236
|
+
bindErr = () => resolve(false);
|
|
237
|
+
server.listen(port, CALLBACK_HOST, () => {
|
|
238
|
+
bindErr = undefined;
|
|
239
|
+
resolve(true);
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
if (bound) {
|
|
243
|
+
return {
|
|
244
|
+
server,
|
|
245
|
+
redirectUri: redirectUriFor(port),
|
|
246
|
+
cancel: () => settle?.(null),
|
|
247
|
+
waitForCode: () => waitPromise,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
lastError = `port ${port} in use`;
|
|
251
|
+
server.close();
|
|
252
|
+
}
|
|
253
|
+
catch (err) {
|
|
254
|
+
lastError = err;
|
|
255
|
+
server.close();
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
throw new Error(`Could not start the OAuth callback server: ports ${CALLBACK_PORT_BASE}-${CALLBACK_PORT_BASE + CALLBACK_PORT_COUNT - 1} are all in use. Close other login attempts and retry. (${String(lastError)})`);
|
|
259
|
+
}
|
|
260
|
+
function parseRedirectInput(input, expectedState) {
|
|
261
|
+
const value = input.trim();
|
|
262
|
+
let code;
|
|
263
|
+
let state;
|
|
264
|
+
try {
|
|
265
|
+
const url = new URL(value);
|
|
266
|
+
code = url.searchParams.get("code") ?? undefined;
|
|
267
|
+
state = url.searchParams.get("state") ?? undefined;
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
const params = new URLSearchParams(value);
|
|
271
|
+
code = params.get("code") ?? value;
|
|
272
|
+
state = params.get("state") ?? undefined;
|
|
273
|
+
}
|
|
274
|
+
if (state && state !== expectedState) {
|
|
275
|
+
throw new Error("OAuth state mismatch");
|
|
276
|
+
}
|
|
277
|
+
if (!code) {
|
|
278
|
+
throw new Error("Missing authorization code");
|
|
279
|
+
}
|
|
280
|
+
return { code, state: state ?? expectedState };
|
|
281
|
+
}
|
|
282
|
+
async function exchangeToken(tokenEndpoint, params) {
|
|
283
|
+
validatedHttpsUrl(tokenEndpoint, "Token endpoint");
|
|
284
|
+
const res = await fetchResponse(tokenEndpoint, {
|
|
285
|
+
method: "POST",
|
|
286
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
287
|
+
body: new URLSearchParams(params).toString(),
|
|
288
|
+
});
|
|
289
|
+
const text = await res.text();
|
|
290
|
+
if (!res.ok) {
|
|
291
|
+
throw new Error(`Token request to ${tokenEndpoint} failed: ${res.status}`);
|
|
292
|
+
}
|
|
293
|
+
let token;
|
|
294
|
+
try {
|
|
295
|
+
token = JSON.parse(text);
|
|
296
|
+
}
|
|
297
|
+
catch {
|
|
298
|
+
throw new Error(`Token request to ${tokenEndpoint} returned invalid JSON`);
|
|
299
|
+
}
|
|
300
|
+
if (!token || typeof token !== "object" || typeof token.access_token !== "string") {
|
|
301
|
+
throw new Error(`Token request to ${tokenEndpoint} returned no access_token`);
|
|
302
|
+
}
|
|
303
|
+
const result = token;
|
|
304
|
+
if (!result.access_token)
|
|
305
|
+
throw new Error(`Token request to ${tokenEndpoint} returned no access_token`);
|
|
306
|
+
if (result.refresh_token !== undefined && typeof result.refresh_token !== "string") {
|
|
307
|
+
throw new Error(`Token request to ${tokenEndpoint} returned an invalid refresh_token`);
|
|
308
|
+
}
|
|
309
|
+
if (result.expires_in !== undefined &&
|
|
310
|
+
(typeof result.expires_in !== "number" || !Number.isFinite(result.expires_in))) {
|
|
311
|
+
throw new Error(`Token request to ${tokenEndpoint} returned an invalid expires_in`);
|
|
312
|
+
}
|
|
313
|
+
return result;
|
|
314
|
+
}
|
|
315
|
+
function toCredentials(token, tokenEndpoint, clientId, endpoint, resource, issuer, previousRefresh) {
|
|
316
|
+
return {
|
|
317
|
+
access: token.access_token,
|
|
318
|
+
// Some servers omit refresh_token on refresh; keep the prior one.
|
|
319
|
+
refresh: token.refresh_token ?? previousRefresh ?? "",
|
|
320
|
+
expires: token.expires_in
|
|
321
|
+
? Date.now() + token.expires_in * 1000 - TOKEN_EXPIRY_BUFFER_MS
|
|
322
|
+
: Date.now() + 3600 * 1000 - TOKEN_EXPIRY_BUFFER_MS,
|
|
323
|
+
tokenEndpoint,
|
|
324
|
+
clientId,
|
|
325
|
+
endpoint,
|
|
326
|
+
resource,
|
|
327
|
+
issuer,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
export function createMcpOAuthProvider(config) {
|
|
331
|
+
const label = config.label ?? config.server;
|
|
332
|
+
async function login(callbacks) {
|
|
333
|
+
const discovery = await discover(config.url);
|
|
334
|
+
const { metadata: meta } = discovery;
|
|
335
|
+
callbacks.onProgress?.(`Discovered ${discovery.issuer ?? meta.issuer}`);
|
|
336
|
+
let clientId = config.clientId;
|
|
337
|
+
if (!clientId) {
|
|
338
|
+
if (!meta.registration_endpoint) {
|
|
339
|
+
throw new Error(`${label} does not support dynamic client registration and no clientId was configured. ` +
|
|
340
|
+
`Set a pre-registered client id for this server.`);
|
|
341
|
+
}
|
|
342
|
+
callbacks.onProgress?.("Registering OAuth client…");
|
|
343
|
+
clientId = await registerClient(meta.registration_endpoint, `ASM Agent (${label})`);
|
|
344
|
+
}
|
|
345
|
+
const { verifier, challenge } = await generatePKCE();
|
|
346
|
+
// `state` must be independent of the PKCE verifier — the verifier is the
|
|
347
|
+
// secret used at token exchange, while `state` is echoed on the redirect URL.
|
|
348
|
+
const state = randomState();
|
|
349
|
+
const scope = config.scopes ?? meta.scopes_supported?.join(" ");
|
|
350
|
+
const cb = await startCallbackServer(label);
|
|
351
|
+
try {
|
|
352
|
+
const authParams = new URLSearchParams({
|
|
353
|
+
client_id: clientId,
|
|
354
|
+
response_type: "code",
|
|
355
|
+
redirect_uri: cb.redirectUri,
|
|
356
|
+
code_challenge: challenge,
|
|
357
|
+
code_challenge_method: "S256",
|
|
358
|
+
state,
|
|
359
|
+
});
|
|
360
|
+
if (scope)
|
|
361
|
+
authParams.set("scope", scope);
|
|
362
|
+
if (discovery.resource)
|
|
363
|
+
authParams.set("resource", discovery.resource);
|
|
364
|
+
const authorizationUrl = new URL(meta.authorization_endpoint);
|
|
365
|
+
for (const [name, value] of authParams)
|
|
366
|
+
authorizationUrl.searchParams.set(name, value);
|
|
367
|
+
callbacks.onAuth({
|
|
368
|
+
url: authorizationUrl.toString(),
|
|
369
|
+
instructions: "Complete login in your browser. If the browser is on another machine, paste the final redirect URL here.",
|
|
370
|
+
});
|
|
371
|
+
// Race the local callback server against a manual paste (browser on
|
|
372
|
+
// another machine). The login dialog supplies onManualCodeInput; when
|
|
373
|
+
// absent we fall back to a blocking prompt after the callback resolves.
|
|
374
|
+
let result;
|
|
375
|
+
let manualCancelled = false;
|
|
376
|
+
let manualError;
|
|
377
|
+
if (callbacks.onManualCodeInput) {
|
|
378
|
+
// Manual paste races the browser callback. A real paste cancels the
|
|
379
|
+
// callback waiter (we're done). On manual cancellation we still settle
|
|
380
|
+
// the waiter to avoid hanging when no redirect arrives — but only after
|
|
381
|
+
// a short grace period so an in-flight browser redirect can win first.
|
|
382
|
+
const manual = callbacks
|
|
383
|
+
.onManualCodeInput()
|
|
384
|
+
.then((input) => {
|
|
385
|
+
const parsed = parseRedirectInput(input, state); // may throw a validation error
|
|
386
|
+
cb.cancel();
|
|
387
|
+
return parsed;
|
|
388
|
+
})
|
|
389
|
+
.catch(async (err) => {
|
|
390
|
+
// A validation error on a real paste (bad state / no code) is a genuine
|
|
391
|
+
// failure to surface; a UI cancellation is not. .catch also prevents an
|
|
392
|
+
// unhandled rejection when the callback wins the race.
|
|
393
|
+
if (err instanceof Error && /state mismatch|authorization code/i.test(err.message)) {
|
|
394
|
+
manualError = err;
|
|
395
|
+
}
|
|
396
|
+
else {
|
|
397
|
+
manualCancelled = true;
|
|
398
|
+
}
|
|
399
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
400
|
+
cb.cancel();
|
|
401
|
+
return null;
|
|
402
|
+
});
|
|
403
|
+
const fromCallback = await cb.waitForCode();
|
|
404
|
+
result = fromCallback ?? (await manual);
|
|
405
|
+
if (!result && manualError)
|
|
406
|
+
throw manualError;
|
|
407
|
+
}
|
|
408
|
+
else {
|
|
409
|
+
result = await cb.waitForCode();
|
|
410
|
+
if (!result) {
|
|
411
|
+
const input = await callbacks.onPrompt({
|
|
412
|
+
message: "Paste the authorization code or full redirect URL:",
|
|
413
|
+
placeholder: cb.redirectUri,
|
|
414
|
+
});
|
|
415
|
+
result = parseRedirectInput(input, state);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
if (!result) {
|
|
419
|
+
throw new Error(manualCancelled ? "Login cancelled" : "Missing authorization code");
|
|
420
|
+
}
|
|
421
|
+
if (result.state !== state) {
|
|
422
|
+
throw new Error("OAuth state mismatch");
|
|
423
|
+
}
|
|
424
|
+
callbacks.onProgress?.("Exchanging authorization code for tokens…");
|
|
425
|
+
const token = await exchangeToken(meta.token_endpoint, {
|
|
426
|
+
grant_type: "authorization_code",
|
|
427
|
+
code: result.code,
|
|
428
|
+
redirect_uri: cb.redirectUri,
|
|
429
|
+
client_id: clientId,
|
|
430
|
+
code_verifier: verifier,
|
|
431
|
+
...(discovery.resource ? { resource: discovery.resource } : {}),
|
|
432
|
+
});
|
|
433
|
+
return toCredentials(token, meta.token_endpoint, clientId, config.url, discovery.resource, discovery.issuer);
|
|
434
|
+
}
|
|
435
|
+
finally {
|
|
436
|
+
cb.server.close();
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
async function refreshToken(credentials) {
|
|
440
|
+
const creds = credentials;
|
|
441
|
+
if (creds.endpoint !== config.url) {
|
|
442
|
+
throw new Error(`Stored OAuth credentials are not bound to ${config.url}; re-run /mcp login ${config.server}`);
|
|
443
|
+
}
|
|
444
|
+
const configuredResource = canonicalResource(validatedHttpsUrl(config.url, "MCP endpoint"));
|
|
445
|
+
if (creds.resource !== undefined && creds.resource !== configuredResource) {
|
|
446
|
+
throw new Error(`Stored OAuth credentials are not bound to ${configuredResource}; re-run /mcp login ${config.server}`);
|
|
447
|
+
}
|
|
448
|
+
if ((creds.resource === undefined) !== (creds.issuer === undefined)) {
|
|
449
|
+
throw new Error(`Stored OAuth credentials for ${label} have incomplete resource binding; re-run /mcp login ${config.server}`);
|
|
450
|
+
}
|
|
451
|
+
if (creds.issuer !== undefined)
|
|
452
|
+
validatedHttpsUrl(creds.issuer, "Stored authorization server issuer");
|
|
453
|
+
if (!creds.refresh) {
|
|
454
|
+
throw new Error(`No refresh token stored for ${label}; re-run /mcp login ${config.server}`);
|
|
455
|
+
}
|
|
456
|
+
const discovery = await discover(config.url);
|
|
457
|
+
if ((creds.resource === undefined) !== (discovery.resource === undefined)) {
|
|
458
|
+
throw new Error(`OAuth discovery mode changed for ${config.url}; re-run /mcp login ${config.server}`);
|
|
459
|
+
}
|
|
460
|
+
if (creds.resource) {
|
|
461
|
+
if (discovery.resource !== creds.resource || discovery.issuer !== creds.issuer) {
|
|
462
|
+
throw new Error(`Stored OAuth credentials do not match current protected-resource metadata for ${config.url}`);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
const tokenEndpoint = creds.tokenEndpoint ?? discovery.metadata.token_endpoint;
|
|
466
|
+
if (creds.tokenEndpoint && discovery.metadata.token_endpoint !== creds.tokenEndpoint) {
|
|
467
|
+
throw new Error(`Stored OAuth token endpoint does not match current authorization-server metadata for ${config.url}`);
|
|
468
|
+
}
|
|
469
|
+
const clientId = creds.clientId ?? config.clientId;
|
|
470
|
+
if (!tokenEndpoint)
|
|
471
|
+
throw new Error(`No token endpoint stored for ${label}; re-run /mcp login ${config.server}`);
|
|
472
|
+
const token = await exchangeToken(tokenEndpoint, {
|
|
473
|
+
grant_type: "refresh_token",
|
|
474
|
+
refresh_token: creds.refresh,
|
|
475
|
+
...(clientId ? { client_id: clientId } : {}),
|
|
476
|
+
...(creds.resource ? { resource: creds.resource } : {}),
|
|
477
|
+
});
|
|
478
|
+
return toCredentials(token, tokenEndpoint, clientId ?? "", creds.endpoint, creds.resource, creds.issuer, creds.refresh);
|
|
479
|
+
}
|
|
480
|
+
return {
|
|
481
|
+
id: `mcp:${config.server}`,
|
|
482
|
+
name: label,
|
|
483
|
+
usesCallbackServer: true,
|
|
484
|
+
login,
|
|
485
|
+
refreshToken,
|
|
486
|
+
getApiKey: (credentials) => credentials.access,
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
//# sourceMappingURL=oauth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth.js","sourceRoot":"","sources":["../../src/mcp/oauth.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Server } from \"node:http\";\nimport { oauthErrorHtml, oauthSuccessHtml } from \"../utils/oauth/oauth-page.js\";\nimport { generatePKCE } from \"../utils/oauth/pkce.js\";\nimport type { OAuthCredentials, OAuthLoginCallbacks, OAuthProviderInterface } from \"../utils/oauth/types.js\";\n\nconst CALLBACK_HOST = process.env.PI_OAUTH_CALLBACK_HOST || \"127.0.0.1\";\n// A range (not one port) so a leaked/concurrent login can't wedge all logins with EADDRINUSE.\n// Distinct from the Anthropic callback port (53692). All candidates are registered as redirect URIs.\nconst CALLBACK_PORT_BASE = Number(process.env.PI_MCP_OAUTH_CALLBACK_PORT || 53700);\nconst CALLBACK_PORT_COUNT = 10;\nconst CALLBACK_PATH = \"/callback\";\nconst CALLBACK_PORTS = Array.from({ length: CALLBACK_PORT_COUNT }, (_, i) => CALLBACK_PORT_BASE + i);\nconst redirectUriFor = (port: number) => `http://localhost:${port}${CALLBACK_PATH}`;\nconst ALL_REDIRECT_URIS = CALLBACK_PORTS.map(redirectUriFor);\nconst TOKEN_EXPIRY_BUFFER_MS = 5 * 60 * 1000;\n\ninterface AuthServerMetadata {\n\tissuer: string;\n\tauthorization_endpoint: string;\n\ttoken_endpoint: string;\n\tregistration_endpoint?: string;\n\tscopes_supported?: string[];\n}\n\ninterface ProtectedResourceMetadata {\n\tresource: string;\n\tauthorization_servers: string[];\n}\n\ninterface Discovery {\n\tmetadata: AuthServerMetadata;\n\tresource?: string;\n\tissuer?: string;\n}\n\nexport interface McpOAuthConfig {\n\t/** MCP server name; provider id becomes `mcp:<server>`. */\n\tserver: string;\n\t/** Human-readable label shown in OAuth UI; defaults to `server`. */\n\tlabel?: string;\n\t/** MCP resource URL used for protected-resource and authorization-server discovery. */\n\turl: string;\n\t/** Pre-registered client id (servers without DCR, e.g. Slack). */\n\tclientId?: string;\n\t/** Requested OAuth scopes; defaults to the server's advertised scopes. */\n\tscopes?: string;\n}\n\ninterface McpCredentials extends OAuthCredentials {\n\ttokenEndpoint?: string;\n\tclientId?: string;\n\t/** MCP endpoint the token was issued for; consumers refuse to send it elsewhere. */\n\tendpoint?: string;\n\t/** RFC 9728 resource indicator. Its presence marks a PRM-based login. */\n\tresource?: string;\n\t/** RFC 8414/OIDC issuer selected by the protected-resource metadata. */\n\tissuer?: string;\n}\n\nfunction validatedHttpsUrl(value: string, name: string): URL {\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(value);\n\t} catch {\n\t\tthrow new Error(`${name} must be an absolute HTTPS URL`);\n\t}\n\tif (url.protocol !== \"https:\" || url.username || url.password || url.hash) {\n\t\tthrow new Error(`${name} must be an absolute HTTPS URL without credentials or a fragment`);\n\t}\n\treturn url;\n}\n\nfunction canonicalResource(url: URL): string {\n\tif (url.pathname === \"/\" && !url.search) return url.origin;\n\treturn `${url.origin}${url.pathname}${url.search}`;\n}\n\nfunction authorizationServerMetadataUrls(issuer: string): string[] {\n\tconst url = validatedHttpsUrl(issuer, \"Authorization server issuer\");\n\tif (url.search) throw new Error(\"Authorization server issuer must not contain a query string\");\n\tconst path = url.pathname === \"/\" ? \"\" : url.pathname.replace(/\\/$/, \"\");\n\treturn [\n\t\tnew URL(`/.well-known/oauth-authorization-server${path}`, url.origin).toString(),\n\t\tnew URL(`${path}/.well-known/openid-configuration`, url.origin).toString(),\n\t];\n}\n\nasync function fetchResponse(url: string, init?: RequestInit): Promise<Response> {\n\treturn fetch(url, { ...init, redirect: \"error\" });\n}\n\nasync function fetchJson(url: string, init?: RequestInit): Promise<unknown> {\n\tconst res = await fetchResponse(url, init);\n\tif (!res.ok) {\n\t\tthrow new Error(`${init?.method ?? \"GET\"} ${url} failed: ${res.status}`);\n\t}\n\treturn res.json();\n}\n\nfunction authorizationServerMetadata(value: unknown, issuer: string, requireExactIssuer: boolean): AuthServerMetadata {\n\tif (!value || typeof value !== \"object\") throw new Error(`Authorization server metadata for ${issuer} is invalid`);\n\tconst metadata = value as Partial<AuthServerMetadata>;\n\tif (typeof metadata.issuer !== \"string\") {\n\t\tthrow new Error(`Authorization server metadata for ${issuer} is missing its issuer`);\n\t}\n\tif (requireExactIssuer) {\n\t\tif (metadata.issuer !== issuer) {\n\t\t\tthrow new Error(`Authorization server metadata issuer does not exactly match ${issuer}`);\n\t\t}\n\t} else {\n\t\tconst advertisedIssuer = validatedHttpsUrl(metadata.issuer, \"Authorization server metadata issuer\");\n\t\tif (advertisedIssuer.origin !== new URL(issuer).origin || advertisedIssuer.search) {\n\t\t\tthrow new Error(`Origin authorization server metadata issuer must stay on ${new URL(issuer).origin}`);\n\t\t}\n\t}\n\tif (typeof metadata.authorization_endpoint !== \"string\" || typeof metadata.token_endpoint !== \"string\") {\n\t\tthrow new Error(`Authorization server metadata for ${issuer} is missing required endpoints`);\n\t}\n\tvalidatedHttpsUrl(metadata.authorization_endpoint, \"Authorization endpoint\");\n\tvalidatedHttpsUrl(metadata.token_endpoint, \"Token endpoint\");\n\tif (metadata.registration_endpoint) validatedHttpsUrl(metadata.registration_endpoint, \"Registration endpoint\");\n\treturn metadata as AuthServerMetadata;\n}\n\nasync function jsonMetadata(response: Response, url: string): Promise<unknown> {\n\tif (response.status !== 200) throw new Error(`GET ${url} failed: ${response.status}`);\n\tconst contentType = response.headers.get(\"content-type\")?.split(\";\", 1)[0].trim().toLowerCase();\n\tif (contentType !== \"application/json\") throw new Error(`GET ${url} did not return application/json`);\n\treturn response.json();\n}\n\nasync function discoverAuthorizationServer(issuer: string, requireExactIssuer: boolean): Promise<AuthServerMetadata> {\n\tconst candidates = authorizationServerMetadataUrls(issuer);\n\tlet lastError: unknown;\n\tfor (const candidate of candidates) {\n\t\ttry {\n\t\t\tconst response = await fetchResponse(candidate);\n\t\t\tif (response.status === 404) continue;\n\t\t\treturn authorizationServerMetadata(await jsonMetadata(response, candidate), issuer, requireExactIssuer);\n\t\t} catch (error) {\n\t\t\tlastError = error;\n\t\t}\n\t}\n\tthrow new Error(\n\t\t`Could not discover OAuth metadata for ${issuer}. Tried ${candidates.join(\", \")}. Last error: ${String(lastError)}`,\n\t);\n}\n\n/** Random, URL-safe CSRF `state` value, independent of the PKCE verifier. */\nfunction randomState(): string {\n\tconst bytes = new Uint8Array(32);\n\tcrypto.getRandomValues(bytes);\n\treturn btoa(String.fromCharCode(...bytes))\n\t\t.replace(/\\+/g, \"-\")\n\t\t.replace(/\\//g, \"_\")\n\t\t.replace(/=/g, \"\");\n}\n\nfunction resourceMetadata(value: unknown, resource: string): ProtectedResourceMetadata {\n\tif (!value || typeof value !== \"object\") throw new Error(\"Protected-resource metadata is invalid\");\n\tconst metadata = value as Partial<ProtectedResourceMetadata>;\n\tif (metadata.resource !== resource)\n\t\tthrow new Error(`Protected-resource metadata resource does not exactly match ${resource}`);\n\tif (!Array.isArray(metadata.authorization_servers) || metadata.authorization_servers.length === 0) {\n\t\tthrow new Error(\"Protected-resource metadata has no authorization_servers\");\n\t}\n\tfor (const issuer of metadata.authorization_servers) {\n\t\tif (typeof issuer !== \"string\")\n\t\t\tthrow new Error(\"Protected-resource metadata has an invalid authorization server\");\n\t\tvalidatedHttpsUrl(issuer, \"Authorization server issuer\");\n\t}\n\treturn metadata as ProtectedResourceMetadata;\n}\n\nfunction resourceMetadataUrl(resource: URL): string {\n\tconst path = resource.pathname === \"/\" ? \"\" : resource.pathname;\n\treturn `${resource.origin}/.well-known/oauth-protected-resource${path}${resource.search}`;\n}\n\nfunction headerResourceMetadata(value: string | null): string | undefined {\n\tconst match = value?.match(/(?:^|[,\\s])resource_metadata\\s*=\\s*\"((?:[^\"\\\\]|\\\\.)*)\"/i);\n\tif (!match) return undefined;\n\treturn match[1].replace(/\\\\(.)/g, \"$1\");\n}\n\nasync function tryProtectedResourceMetadata(url: string): Promise<ProtectedResourceMetadata | undefined> {\n\tconst resource = validatedHttpsUrl(url, \"MCP endpoint\");\n\tlet headerUrl: string | undefined;\n\ttry {\n\t\t// This probe deliberately has no Authorization header. It must not leak an existing token.\n\t\tconst response = await fetchResponse(resource.toString());\n\t\theaderUrl = headerResourceMetadata(response.headers.get(\"www-authenticate\"));\n\t\tawait response.body?.cancel();\n\t} catch {\n\t\t// The server need not support a GET probe; use the RFC well-known locations below.\n\t}\n\n\tconst candidate = headerUrl\n\t\t? validatedHttpsUrl(headerUrl, \"resource_metadata\").toString()\n\t\t: resourceMetadataUrl(resource);\n\tconst response = await fetchResponse(candidate);\n\tif (response.status === 404 && !headerUrl) return undefined;\n\treturn resourceMetadata(await jsonMetadata(response, candidate), canonicalResource(resource));\n}\n\n/** Discover RFC 9728 protected-resource metadata before the origin-level authorization server fallback. */\nasync function discover(url: string): Promise<Discovery> {\n\tconst protectedResource = await tryProtectedResourceMetadata(url);\n\tif (protectedResource) {\n\t\tconst issuer = protectedResource.authorization_servers[0];\n\t\treturn {\n\t\t\tmetadata: await discoverAuthorizationServer(issuer, true),\n\t\t\tresource: protectedResource.resource,\n\t\t\tissuer,\n\t\t};\n\t}\n\tconst issuer = validatedHttpsUrl(url, \"MCP endpoint\").origin;\n\treturn { metadata: await discoverAuthorizationServer(issuer, false) };\n}\n\nasync function registerClient(registrationEndpoint: string, label: string): Promise<string> {\n\tvalidatedHttpsUrl(registrationEndpoint, \"Registration endpoint\");\n\tconst body = {\n\t\tclient_name: label,\n\t\tredirect_uris: ALL_REDIRECT_URIS,\n\t\tgrant_types: [\"authorization_code\", \"refresh_token\"],\n\t\tresponse_types: [\"code\"],\n\t\ttoken_endpoint_auth_method: \"none\",\n\t};\n\tconst data = (await fetchJson(registrationEndpoint, {\n\t\tmethod: \"POST\",\n\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\tbody: JSON.stringify(body),\n\t})) as { client_id?: unknown };\n\tif (typeof data.client_id !== \"string\" || !data.client_id) {\n\t\tthrow new Error(`Dynamic client registration at ${registrationEndpoint} returned no client_id`);\n\t}\n\treturn data.client_id;\n}\n\ntype CallbackResult = { code: string; state: string } | null;\n\nasync function startCallbackServer(label: string): Promise<{\n\tserver: Server;\n\tredirectUri: string;\n\tcancel: () => void;\n\twaitForCode: () => Promise<CallbackResult>;\n}> {\n\tconst { createServer } = await import(\"node:http\");\n\tlet settle: ((value: CallbackResult) => void) | undefined;\n\tconst waitPromise = new Promise<CallbackResult>((resolve) => {\n\t\tlet settled = false;\n\t\tsettle = (value) => {\n\t\t\tif (!settled) {\n\t\t\t\tsettled = true;\n\t\t\t\tresolve(value);\n\t\t\t}\n\t\t};\n\t});\n\n\tconst handler = (req: import(\"node:http\").IncomingMessage, res: import(\"node:http\").ServerResponse) => {\n\t\tconst url = new URL(req.url || \"\", \"http://localhost\");\n\t\tif (url.pathname !== CALLBACK_PATH) {\n\t\t\tres.writeHead(404, { \"Content-Type\": \"text/html; charset=utf-8\" });\n\t\t\tres.end(oauthErrorHtml(\"Callback route not found.\"));\n\t\t\treturn;\n\t\t}\n\t\tconst error = url.searchParams.get(\"error\");\n\t\tconst code = url.searchParams.get(\"code\");\n\t\tconst state = url.searchParams.get(\"state\");\n\t\tres.writeHead(error || !code ? 400 : 200, { \"Content-Type\": \"text/html; charset=utf-8\" });\n\t\tif (error) {\n\t\t\tres.end(oauthErrorHtml(`${label} authentication failed.`, `Error: ${error}`));\n\t\t\tsettle?.(null);\n\t\t\treturn;\n\t\t}\n\t\tif (!code || !state) {\n\t\t\tres.end(oauthErrorHtml(\"Missing code or state parameter.\"));\n\t\t\tsettle?.(null);\n\t\t\treturn;\n\t\t}\n\t\tres.end(oauthSuccessHtml(`${label} authentication completed. You can close this window.`));\n\t\tsettle?.({ code, state });\n\t};\n\n\t// Try each candidate port with a FRESH server (a server that failed to listen\n\t// can't be reused), so a leaked/concurrent login can't block us with EADDRINUSE.\n\tlet lastError: unknown;\n\tfor (const port of CALLBACK_PORTS) {\n\t\tconst server = createServer(handler);\n\t\t// Persistent handler so a post-bind 'error' is never an unhandled crash.\n\t\tlet bindErr: ((err: unknown) => void) | undefined;\n\t\tserver.on(\"error\", (err) => bindErr?.(err));\n\t\ttry {\n\t\t\tconst bound = await new Promise<boolean>((resolve) => {\n\t\t\t\tbindErr = () => resolve(false);\n\t\t\t\tserver.listen(port, CALLBACK_HOST, () => {\n\t\t\t\t\tbindErr = undefined;\n\t\t\t\t\tresolve(true);\n\t\t\t\t});\n\t\t\t});\n\t\t\tif (bound) {\n\t\t\t\treturn {\n\t\t\t\t\tserver,\n\t\t\t\t\tredirectUri: redirectUriFor(port),\n\t\t\t\t\tcancel: () => settle?.(null),\n\t\t\t\t\twaitForCode: () => waitPromise,\n\t\t\t\t};\n\t\t\t}\n\t\t\tlastError = `port ${port} in use`;\n\t\t\tserver.close();\n\t\t} catch (err) {\n\t\t\tlastError = err;\n\t\t\tserver.close();\n\t\t}\n\t}\n\tthrow new Error(\n\t\t`Could not start the OAuth callback server: ports ${CALLBACK_PORT_BASE}-${\n\t\t\tCALLBACK_PORT_BASE + CALLBACK_PORT_COUNT - 1\n\t\t} are all in use. Close other login attempts and retry. (${String(lastError)})`,\n\t);\n}\n\nfunction parseRedirectInput(input: string, expectedState: string): { code: string; state: string } {\n\tconst value = input.trim();\n\tlet code: string | undefined;\n\tlet state: string | undefined;\n\ttry {\n\t\tconst url = new URL(value);\n\t\tcode = url.searchParams.get(\"code\") ?? undefined;\n\t\tstate = url.searchParams.get(\"state\") ?? undefined;\n\t} catch {\n\t\tconst params = new URLSearchParams(value);\n\t\tcode = params.get(\"code\") ?? value;\n\t\tstate = params.get(\"state\") ?? undefined;\n\t}\n\tif (state && state !== expectedState) {\n\t\tthrow new Error(\"OAuth state mismatch\");\n\t}\n\tif (!code) {\n\t\tthrow new Error(\"Missing authorization code\");\n\t}\n\treturn { code, state: state ?? expectedState };\n}\n\nasync function exchangeToken(\n\ttokenEndpoint: string,\n\tparams: Record<string, string>,\n): Promise<{ access_token: string; refresh_token?: string; expires_in?: number }> {\n\tvalidatedHttpsUrl(tokenEndpoint, \"Token endpoint\");\n\tconst res = await fetchResponse(tokenEndpoint, {\n\t\tmethod: \"POST\",\n\t\theaders: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n\t\tbody: new URLSearchParams(params).toString(),\n\t});\n\tconst text = await res.text();\n\tif (!res.ok) {\n\t\tthrow new Error(`Token request to ${tokenEndpoint} failed: ${res.status}`);\n\t}\n\tlet token: unknown;\n\ttry {\n\t\ttoken = JSON.parse(text);\n\t} catch {\n\t\tthrow new Error(`Token request to ${tokenEndpoint} returned invalid JSON`);\n\t}\n\tif (!token || typeof token !== \"object\" || typeof (token as { access_token?: unknown }).access_token !== \"string\") {\n\t\tthrow new Error(`Token request to ${tokenEndpoint} returned no access_token`);\n\t}\n\tconst result = token as { access_token: string; refresh_token?: unknown; expires_in?: unknown };\n\tif (!result.access_token) throw new Error(`Token request to ${tokenEndpoint} returned no access_token`);\n\tif (result.refresh_token !== undefined && typeof result.refresh_token !== \"string\") {\n\t\tthrow new Error(`Token request to ${tokenEndpoint} returned an invalid refresh_token`);\n\t}\n\tif (\n\t\tresult.expires_in !== undefined &&\n\t\t(typeof result.expires_in !== \"number\" || !Number.isFinite(result.expires_in))\n\t) {\n\t\tthrow new Error(`Token request to ${tokenEndpoint} returned an invalid expires_in`);\n\t}\n\treturn result as { access_token: string; refresh_token?: string; expires_in?: number };\n}\n\nfunction toCredentials(\n\ttoken: { access_token: string; refresh_token?: string; expires_in?: number },\n\ttokenEndpoint: string,\n\tclientId: string,\n\tendpoint: string | undefined,\n\tresource: string | undefined,\n\tissuer: string | undefined,\n\tpreviousRefresh?: string,\n): McpCredentials {\n\treturn {\n\t\taccess: token.access_token,\n\t\t// Some servers omit refresh_token on refresh; keep the prior one.\n\t\trefresh: token.refresh_token ?? previousRefresh ?? \"\",\n\t\texpires: token.expires_in\n\t\t\t? Date.now() + token.expires_in * 1000 - TOKEN_EXPIRY_BUFFER_MS\n\t\t\t: Date.now() + 3600 * 1000 - TOKEN_EXPIRY_BUFFER_MS,\n\t\ttokenEndpoint,\n\t\tclientId,\n\t\tendpoint,\n\t\tresource,\n\t\tissuer,\n\t};\n}\n\nexport function createMcpOAuthProvider(config: McpOAuthConfig): OAuthProviderInterface {\n\tconst label = config.label ?? config.server;\n\n\tasync function login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n\t\tconst discovery = await discover(config.url);\n\t\tconst { metadata: meta } = discovery;\n\t\tcallbacks.onProgress?.(`Discovered ${discovery.issuer ?? meta.issuer}`);\n\n\t\tlet clientId = config.clientId;\n\t\tif (!clientId) {\n\t\t\tif (!meta.registration_endpoint) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`${label} does not support dynamic client registration and no clientId was configured. ` +\n\t\t\t\t\t\t`Set a pre-registered client id for this server.`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tcallbacks.onProgress?.(\"Registering OAuth client…\");\n\t\t\tclientId = await registerClient(meta.registration_endpoint, `ASM Agent (${label})`);\n\t\t}\n\n\t\tconst { verifier, challenge } = await generatePKCE();\n\t\t// `state` must be independent of the PKCE verifier — the verifier is the\n\t\t// secret used at token exchange, while `state` is echoed on the redirect URL.\n\t\tconst state = randomState();\n\t\tconst scope = config.scopes ?? meta.scopes_supported?.join(\" \");\n\t\tconst cb = await startCallbackServer(label);\n\t\ttry {\n\t\t\tconst authParams = new URLSearchParams({\n\t\t\t\tclient_id: clientId,\n\t\t\t\tresponse_type: \"code\",\n\t\t\t\tredirect_uri: cb.redirectUri,\n\t\t\t\tcode_challenge: challenge,\n\t\t\t\tcode_challenge_method: \"S256\",\n\t\t\t\tstate,\n\t\t\t});\n\t\t\tif (scope) authParams.set(\"scope\", scope);\n\t\t\tif (discovery.resource) authParams.set(\"resource\", discovery.resource);\n\n\t\t\tconst authorizationUrl = new URL(meta.authorization_endpoint);\n\t\t\tfor (const [name, value] of authParams) authorizationUrl.searchParams.set(name, value);\n\t\t\tcallbacks.onAuth({\n\t\t\t\turl: authorizationUrl.toString(),\n\t\t\t\tinstructions:\n\t\t\t\t\t\"Complete login in your browser. If the browser is on another machine, paste the final redirect URL here.\",\n\t\t\t});\n\n\t\t\t// Race the local callback server against a manual paste (browser on\n\t\t\t// another machine). The login dialog supplies onManualCodeInput; when\n\t\t\t// absent we fall back to a blocking prompt after the callback resolves.\n\t\t\tlet result: { code: string; state: string } | null;\n\t\t\tlet manualCancelled = false;\n\t\t\tlet manualError: Error | undefined;\n\t\t\tif (callbacks.onManualCodeInput) {\n\t\t\t\t// Manual paste races the browser callback. A real paste cancels the\n\t\t\t\t// callback waiter (we're done). On manual cancellation we still settle\n\t\t\t\t// the waiter to avoid hanging when no redirect arrives — but only after\n\t\t\t\t// a short grace period so an in-flight browser redirect can win first.\n\t\t\t\tconst manual = callbacks\n\t\t\t\t\t.onManualCodeInput()\n\t\t\t\t\t.then((input) => {\n\t\t\t\t\t\tconst parsed = parseRedirectInput(input, state); // may throw a validation error\n\t\t\t\t\t\tcb.cancel();\n\t\t\t\t\t\treturn parsed;\n\t\t\t\t\t})\n\t\t\t\t\t.catch(async (err) => {\n\t\t\t\t\t\t// A validation error on a real paste (bad state / no code) is a genuine\n\t\t\t\t\t\t// failure to surface; a UI cancellation is not. .catch also prevents an\n\t\t\t\t\t\t// unhandled rejection when the callback wins the race.\n\t\t\t\t\t\tif (err instanceof Error && /state mismatch|authorization code/i.test(err.message)) {\n\t\t\t\t\t\t\tmanualError = err;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tmanualCancelled = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tawait new Promise((r) => setTimeout(r, 500));\n\t\t\t\t\t\tcb.cancel();\n\t\t\t\t\t\treturn null;\n\t\t\t\t\t});\n\t\t\t\tconst fromCallback = await cb.waitForCode();\n\t\t\t\tresult = fromCallback ?? (await manual);\n\t\t\t\tif (!result && manualError) throw manualError;\n\t\t\t} else {\n\t\t\t\tresult = await cb.waitForCode();\n\t\t\t\tif (!result) {\n\t\t\t\t\tconst input = await callbacks.onPrompt({\n\t\t\t\t\t\tmessage: \"Paste the authorization code or full redirect URL:\",\n\t\t\t\t\t\tplaceholder: cb.redirectUri,\n\t\t\t\t\t});\n\t\t\t\t\tresult = parseRedirectInput(input, state);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!result) {\n\t\t\t\tthrow new Error(manualCancelled ? \"Login cancelled\" : \"Missing authorization code\");\n\t\t\t}\n\t\t\tif (result.state !== state) {\n\t\t\t\tthrow new Error(\"OAuth state mismatch\");\n\t\t\t}\n\n\t\t\tcallbacks.onProgress?.(\"Exchanging authorization code for tokens…\");\n\t\t\tconst token = await exchangeToken(meta.token_endpoint, {\n\t\t\t\tgrant_type: \"authorization_code\",\n\t\t\t\tcode: result.code,\n\t\t\t\tredirect_uri: cb.redirectUri,\n\t\t\t\tclient_id: clientId,\n\t\t\t\tcode_verifier: verifier,\n\t\t\t\t...(discovery.resource ? { resource: discovery.resource } : {}),\n\t\t\t});\n\t\t\treturn toCredentials(token, meta.token_endpoint, clientId, config.url, discovery.resource, discovery.issuer);\n\t\t} finally {\n\t\t\tcb.server.close();\n\t\t}\n\t}\n\n\tasync function refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {\n\t\tconst creds = credentials as McpCredentials;\n\t\tif (creds.endpoint !== config.url) {\n\t\t\tthrow new Error(`Stored OAuth credentials are not bound to ${config.url}; re-run /mcp login ${config.server}`);\n\t\t}\n\t\tconst configuredResource = canonicalResource(validatedHttpsUrl(config.url, \"MCP endpoint\"));\n\t\tif (creds.resource !== undefined && creds.resource !== configuredResource) {\n\t\t\tthrow new Error(\n\t\t\t\t`Stored OAuth credentials are not bound to ${configuredResource}; re-run /mcp login ${config.server}`,\n\t\t\t);\n\t\t}\n\t\tif ((creds.resource === undefined) !== (creds.issuer === undefined)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Stored OAuth credentials for ${label} have incomplete resource binding; re-run /mcp login ${config.server}`,\n\t\t\t);\n\t\t}\n\t\tif (creds.issuer !== undefined) validatedHttpsUrl(creds.issuer, \"Stored authorization server issuer\");\n\t\tif (!creds.refresh) {\n\t\t\tthrow new Error(`No refresh token stored for ${label}; re-run /mcp login ${config.server}`);\n\t\t}\n\t\tconst discovery = await discover(config.url);\n\t\tif ((creds.resource === undefined) !== (discovery.resource === undefined)) {\n\t\t\tthrow new Error(`OAuth discovery mode changed for ${config.url}; re-run /mcp login ${config.server}`);\n\t\t}\n\t\tif (creds.resource) {\n\t\t\tif (discovery.resource !== creds.resource || discovery.issuer !== creds.issuer) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Stored OAuth credentials do not match current protected-resource metadata for ${config.url}`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\tconst tokenEndpoint = creds.tokenEndpoint ?? discovery.metadata.token_endpoint;\n\t\tif (creds.tokenEndpoint && discovery.metadata.token_endpoint !== creds.tokenEndpoint) {\n\t\t\tthrow new Error(\n\t\t\t\t`Stored OAuth token endpoint does not match current authorization-server metadata for ${config.url}`,\n\t\t\t);\n\t\t}\n\t\tconst clientId = creds.clientId ?? config.clientId;\n\t\tif (!tokenEndpoint) throw new Error(`No token endpoint stored for ${label}; re-run /mcp login ${config.server}`);\n\t\tconst token = await exchangeToken(tokenEndpoint, {\n\t\t\tgrant_type: \"refresh_token\",\n\t\t\trefresh_token: creds.refresh,\n\t\t\t...(clientId ? { client_id: clientId } : {}),\n\t\t\t...(creds.resource ? { resource: creds.resource } : {}),\n\t\t});\n\t\treturn toCredentials(\n\t\t\ttoken,\n\t\t\ttokenEndpoint,\n\t\t\tclientId ?? \"\",\n\t\t\tcreds.endpoint,\n\t\t\tcreds.resource,\n\t\t\tcreds.issuer,\n\t\t\tcreds.refresh,\n\t\t);\n\t}\n\n\treturn {\n\t\tid: `mcp:${config.server}`,\n\t\tname: label,\n\t\tusesCallbackServer: true,\n\t\tlogin,\n\t\trefreshToken,\n\t\tgetApiKey: (credentials) => credentials.access,\n\t};\n}\n"]}
|
package/dist/mcp.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC","sourcesContent":["export * from \"./mcp/index.js\";\n"]}
|
package/dist/mcp.js
ADDED
package/dist/mcp.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp.js","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC","sourcesContent":["export * from \"./mcp/index.js\";\n"]}
|
package/dist/models.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { MODELS } from "./models.generated.js";
|
|
2
|
+
import type { Api, KnownProvider, Model, ModelThinkingLevel, Usage } from "./types.js";
|
|
3
|
+
type ModelApi<TProvider extends KnownProvider, TModelId extends keyof (typeof MODELS)[TProvider]> = (typeof MODELS)[TProvider][TModelId] extends {
|
|
4
|
+
api: infer TApi;
|
|
5
|
+
} ? (TApi extends Api ? TApi : never) : never;
|
|
6
|
+
export declare function getModel<TProvider extends KnownProvider, TModelId extends keyof (typeof MODELS)[TProvider]>(provider: TProvider, modelId: TModelId): Model<ModelApi<TProvider, TModelId>>;
|
|
7
|
+
export declare function getProviders(): KnownProvider[];
|
|
8
|
+
export declare function getModels<TProvider extends KnownProvider>(provider: TProvider): Model<ModelApi<TProvider, keyof (typeof MODELS)[TProvider]>>[];
|
|
9
|
+
export declare function supportsFastMode<TApi extends Api>(model: Model<TApi>): boolean;
|
|
10
|
+
export interface CostOverrides {
|
|
11
|
+
cacheWrite?: number;
|
|
12
|
+
}
|
|
13
|
+
export declare function calculateCost<TApi extends Api>(model: Model<TApi>, usage: Usage, overrides?: CostOverrides): Usage["cost"];
|
|
14
|
+
export declare function getSupportedThinkingLevels<TApi extends Api>(model: Model<TApi>): ModelThinkingLevel[];
|
|
15
|
+
export declare function clampThinkingLevel<TApi extends Api>(model: Model<TApi>, level: ModelThinkingLevel): ModelThinkingLevel;
|
|
16
|
+
export declare function modelsAreEqual<TApi extends Api>(a: Model<TApi> | null | undefined, b: Model<TApi> | null | undefined): boolean;
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=models.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC/C,OAAO,KAAK,EAAE,GAAG,EAAE,aAAa,EAAE,KAAK,EAAE,kBAAkB,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAYvF,KAAK,QAAQ,CACZ,SAAS,SAAS,aAAa,EAC/B,QAAQ,SAAS,MAAM,CAAC,OAAO,MAAM,CAAC,CAAC,SAAS,CAAC,IAC9C,CAAC,OAAO,MAAM,CAAC,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,SAAS;IAAE,GAAG,EAAE,MAAM,IAAI,CAAA;CAAE,GAAG,CAAC,IAAI,SAAS,GAAG,GAAG,IAAI,GAAG,KAAK,CAAC,GAAG,KAAK,CAAC;AAEjH,wBAAgB,QAAQ,CAAC,SAAS,SAAS,aAAa,EAAE,QAAQ,SAAS,MAAM,CAAC,OAAO,MAAM,CAAC,CAAC,SAAS,CAAC,EAC1G,QAAQ,EAAE,SAAS,EACnB,OAAO,EAAE,QAAQ,GACf,KAAK,CAAC,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC,CAGtC;AAED,wBAAgB,YAAY,IAAI,aAAa,EAAE,CAE9C;AAED,wBAAgB,SAAS,CAAC,SAAS,SAAS,aAAa,EACxD,QAAQ,EAAE,SAAS,GACjB,KAAK,CAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,OAAO,MAAM,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAGhE;AAED,wBAAgB,gBAAgB,CAAC,IAAI,SAAS,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,OAAO,CAQ9E;AAED,MAAM,WAAW,aAAa;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,aAAa,CAAC,IAAI,SAAS,GAAG,EAC7C,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,EAClB,KAAK,EAAE,KAAK,EACZ,SAAS,CAAC,EAAE,aAAa,GACvB,KAAK,CAAC,MAAM,CAAC,CAOf;AAID,wBAAgB,0BAA0B,CAAC,IAAI,SAAS,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,kBAAkB,EAAE,CASrG;AAED,wBAAgB,kBAAkB,CAAC,IAAI,SAAS,GAAG,EAClD,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,EAClB,KAAK,EAAE,kBAAkB,GACvB,kBAAkB,CAgBpB;AAED,wBAAgB,cAAc,CAAC,IAAI,SAAS,GAAG,EAC9C,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,SAAS,EACjC,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,SAAS,GAC/B,OAAO,CAGT","sourcesContent":["import { MODELS } from \"./models.generated.js\";\nimport type { Api, KnownProvider, Model, ModelThinkingLevel, Usage } from \"./types.js\";\n\nconst modelRegistry: Map<string, Map<string, Model<Api>>> = new Map();\n\nfor (const [provider, models] of Object.entries(MODELS)) {\n\tconst providerModels = new Map<string, Model<Api>>();\n\tfor (const [id, model] of Object.entries(models)) {\n\t\tproviderModels.set(id, model as Model<Api>);\n\t}\n\tmodelRegistry.set(provider, providerModels);\n}\n\ntype ModelApi<\n\tTProvider extends KnownProvider,\n\tTModelId extends keyof (typeof MODELS)[TProvider],\n> = (typeof MODELS)[TProvider][TModelId] extends { api: infer TApi } ? (TApi extends Api ? TApi : never) : never;\n\nexport function getModel<TProvider extends KnownProvider, TModelId extends keyof (typeof MODELS)[TProvider]>(\n\tprovider: TProvider,\n\tmodelId: TModelId,\n): Model<ModelApi<TProvider, TModelId>> {\n\tconst providerModels = modelRegistry.get(provider);\n\treturn providerModels?.get(modelId as string) as Model<ModelApi<TProvider, TModelId>>;\n}\n\nexport function getProviders(): KnownProvider[] {\n\treturn Array.from(modelRegistry.keys()) as KnownProvider[];\n}\n\nexport function getModels<TProvider extends KnownProvider>(\n\tprovider: TProvider,\n): Model<ModelApi<TProvider, keyof (typeof MODELS)[TProvider]>>[] {\n\tconst models = modelRegistry.get(provider);\n\treturn models ? (Array.from(models.values()) as Model<ModelApi<TProvider, keyof (typeof MODELS)[TProvider]>>[]) : [];\n}\n\nexport function supportsFastMode<TApi extends Api>(model: Model<TApi>): boolean {\n\tconst eligibleId =\n\t\tmodel.id === \"gpt-5.4\" || model.id === \"gpt-5.5\" || model.id === \"gpt-5.6\" || model.id.startsWith(\"gpt-5.6-\");\n\treturn (\n\t\teligibleId &&\n\t\t((model.provider === \"openai-codex\" && model.api === \"openai-codex-responses\") ||\n\t\t\t(model.provider === \"openai\" && model.api === \"openai-responses\"))\n\t);\n}\n\nexport interface CostOverrides {\n\tcacheWrite?: number;\n}\n\nexport function calculateCost<TApi extends Api>(\n\tmodel: Model<TApi>,\n\tusage: Usage,\n\toverrides?: CostOverrides,\n): Usage[\"cost\"] {\n\tusage.cost.input = (model.cost.input / 1000000) * usage.input;\n\tusage.cost.output = (model.cost.output / 1000000) * usage.output;\n\tusage.cost.cacheRead = (model.cost.cacheRead / 1000000) * usage.cacheRead;\n\tusage.cost.cacheWrite = ((overrides?.cacheWrite ?? model.cost.cacheWrite) / 1000000) * usage.cacheWrite;\n\tusage.cost.total = usage.cost.input + usage.cost.output + usage.cost.cacheRead + usage.cost.cacheWrite;\n\treturn usage.cost;\n}\n\nconst EXTENDED_THINKING_LEVELS: ModelThinkingLevel[] = [\"off\", \"minimal\", \"low\", \"medium\", \"high\", \"xhigh\", \"max\"];\n\nexport function getSupportedThinkingLevels<TApi extends Api>(model: Model<TApi>): ModelThinkingLevel[] {\n\tif (!model.reasoning) return [\"off\"];\n\n\treturn EXTENDED_THINKING_LEVELS.filter((level) => {\n\t\tconst mapped = model.thinkingLevelMap?.[level];\n\t\tif (mapped === null) return false;\n\t\tif (level === \"xhigh\" || level === \"max\") return mapped !== undefined;\n\t\treturn true;\n\t});\n}\n\nexport function clampThinkingLevel<TApi extends Api>(\n\tmodel: Model<TApi>,\n\tlevel: ModelThinkingLevel,\n): ModelThinkingLevel {\n\tconst availableLevels = getSupportedThinkingLevels(model);\n\tif (availableLevels.includes(level)) return level;\n\n\tconst requestedIndex = EXTENDED_THINKING_LEVELS.indexOf(level);\n\tif (requestedIndex === -1) return availableLevels[0] ?? \"off\";\n\n\tfor (let i = requestedIndex; i < EXTENDED_THINKING_LEVELS.length; i++) {\n\t\tconst candidate = EXTENDED_THINKING_LEVELS[i];\n\t\tif (availableLevels.includes(candidate)) return candidate;\n\t}\n\tfor (let i = requestedIndex - 1; i >= 0; i--) {\n\t\tconst candidate = EXTENDED_THINKING_LEVELS[i];\n\t\tif (availableLevels.includes(candidate)) return candidate;\n\t}\n\treturn availableLevels[0] ?? \"off\";\n}\n\nexport function modelsAreEqual<TApi extends Api>(\n\ta: Model<TApi> | null | undefined,\n\tb: Model<TApi> | null | undefined,\n): boolean {\n\tif (!a || !b) return false;\n\treturn a.id === b.id && a.provider === b.provider;\n}\n"]}
|