@earendil-works/pi-ai 0.80.6 → 0.80.7
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 +3 -2
- package/dist/api/anthropic-messages.d.ts.map +1 -1
- package/dist/api/anthropic-messages.js +95 -46
- package/dist/api/anthropic-messages.js.map +1 -1
- package/dist/api/bedrock-converse-stream.d.ts.map +1 -1
- package/dist/api/bedrock-converse-stream.js +14 -7
- package/dist/api/bedrock-converse-stream.js.map +1 -1
- package/dist/api/openai-codex-responses.d.ts +1 -0
- package/dist/api/openai-codex-responses.d.ts.map +1 -1
- package/dist/api/openai-codex-responses.js +6 -3
- package/dist/api/openai-codex-responses.js.map +1 -1
- package/dist/api/openai-completions.d.ts.map +1 -1
- package/dist/api/openai-completions.js +12 -3
- package/dist/api/openai-completions.js.map +1 -1
- package/dist/api/openai-responses-shared.d.ts +3 -1
- package/dist/api/openai-responses-shared.d.ts.map +1 -1
- package/dist/api/openai-responses-shared.js +51 -0
- package/dist/api/openai-responses-shared.js.map +1 -1
- package/dist/api/openai-responses.d.ts +1 -0
- package/dist/api/openai-responses.d.ts.map +1 -1
- package/dist/api/openai-responses.js +24 -8
- package/dist/api/openai-responses.js.map +1 -1
- package/dist/api/pi-messages.d.ts +97 -0
- package/dist/api/pi-messages.d.ts.map +1 -0
- package/dist/api/pi-messages.js +309 -0
- package/dist/api/pi-messages.js.map +1 -0
- package/dist/api/pi-messages.lazy.d.ts +3 -0
- package/dist/api/pi-messages.lazy.d.ts.map +1 -0
- package/dist/api/pi-messages.lazy.js +3 -0
- package/dist/api/pi-messages.lazy.js.map +1 -0
- package/dist/compat.d.ts +2 -0
- package/dist/compat.d.ts.map +1 -1
- package/dist/compat.js +5 -1
- package/dist/compat.js.map +1 -1
- package/dist/env-api-keys.d.ts.map +1 -1
- package/dist/env-api-keys.js +1 -0
- package/dist/env-api-keys.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/models.generated.d.ts +498 -180
- package/dist/models.generated.d.ts.map +1 -1
- package/dist/providers/all.d.ts +9 -5
- package/dist/providers/all.d.ts.map +1 -1
- package/dist/providers/all.js.map +1 -1
- package/dist/providers/amazon-bedrock.d.ts.map +1 -1
- package/dist/providers/amazon-bedrock.js +5 -1
- package/dist/providers/amazon-bedrock.js.map +1 -1
- package/dist/providers/amazon-bedrock.models.d.ts +60 -0
- package/dist/providers/amazon-bedrock.models.d.ts.map +1 -1
- package/dist/providers/amazon-bedrock.models.js +54 -0
- package/dist/providers/amazon-bedrock.models.js.map +1 -1
- package/dist/providers/azure-openai-responses.models.d.ts +17 -0
- package/dist/providers/azure-openai-responses.models.d.ts.map +1 -1
- package/dist/providers/azure-openai-responses.models.js +17 -0
- package/dist/providers/azure-openai-responses.models.js.map +1 -1
- package/dist/providers/cerebras.models.d.ts.map +1 -1
- package/dist/providers/cerebras.models.js +1 -1
- package/dist/providers/cerebras.models.js.map +1 -1
- package/dist/providers/cloudflare-ai-gateway.models.d.ts +92 -0
- package/dist/providers/cloudflare-ai-gateway.models.d.ts.map +1 -1
- package/dist/providers/cloudflare-ai-gateway.models.js +72 -0
- package/dist/providers/cloudflare-ai-gateway.models.js.map +1 -1
- package/dist/providers/cloudflare-auth.d.ts.map +1 -1
- package/dist/providers/cloudflare-auth.js +9 -6
- package/dist/providers/cloudflare-auth.js.map +1 -1
- package/dist/providers/github-copilot.models.d.ts +93 -6
- package/dist/providers/github-copilot.models.d.ts.map +1 -1
- package/dist/providers/github-copilot.models.js +60 -3
- package/dist/providers/github-copilot.models.js.map +1 -1
- package/dist/providers/openai-codex.models.d.ts +18 -0
- package/dist/providers/openai-codex.models.d.ts.map +1 -1
- package/dist/providers/openai-codex.models.js +6 -0
- package/dist/providers/openai-codex.models.js.map +1 -1
- package/dist/providers/openai.models.d.ts +38 -0
- package/dist/providers/openai.models.d.ts.map +1 -1
- package/dist/providers/openai.models.js +24 -0
- package/dist/providers/openai.models.js.map +1 -1
- package/dist/providers/opencode.models.d.ts +123 -0
- package/dist/providers/opencode.models.d.ts.map +1 -1
- package/dist/providers/opencode.models.js +74 -1
- package/dist/providers/opencode.models.js.map +1 -1
- package/dist/providers/openrouter.models.d.ts +28 -43
- package/dist/providers/openrouter.models.d.ts.map +1 -1
- package/dist/providers/openrouter.models.js +92 -109
- package/dist/providers/openrouter.models.js.map +1 -1
- package/dist/providers/vercel-ai-gateway.models.d.ts +7 -109
- package/dist/providers/vercel-ai-gateway.models.d.ts.map +1 -1
- package/dist/providers/vercel-ai-gateway.models.js +34 -136
- package/dist/providers/vercel-ai-gateway.models.js.map +1 -1
- package/dist/types.d.ts +25 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/deferred-tools.d.ts +9 -0
- package/dist/utils/deferred-tools.d.ts.map +1 -0
- package/dist/utils/deferred-tools.js +36 -0
- package/dist/utils/deferred-tools.js.map +1 -0
- package/dist/utils/estimate.d.ts.map +1 -1
- package/dist/utils/estimate.js +18 -5
- package/dist/utils/estimate.js.map +1 -1
- package/dist/utils/oauth/index.d.ts +1 -0
- package/dist/utils/oauth/index.d.ts.map +1 -1
- package/dist/utils/oauth/index.js +9 -0
- package/dist/utils/oauth/index.js.map +1 -1
- package/dist/utils/oauth/radius.d.ts +39 -0
- package/dist/utils/oauth/radius.d.ts.map +1 -0
- package/dist/utils/oauth/radius.js +415 -0
- package/dist/utils/oauth/radius.js.map +1 -0
- package/package.json +1 -1
package/dist/utils/estimate.js
CHANGED
|
@@ -80,6 +80,11 @@ function estimateMessages(messages) {
|
|
|
80
80
|
tokens += estimateMessageTokens(message);
|
|
81
81
|
return { tokens, usageTokens: 0, trailingTokens: tokens, lastUsageIndex: null };
|
|
82
82
|
}
|
|
83
|
+
function estimateToolsTokens(tools) {
|
|
84
|
+
if (!tools || tools.length === 0)
|
|
85
|
+
return 0;
|
|
86
|
+
return estimateTextTokens(safeJsonStringify(tools));
|
|
87
|
+
}
|
|
83
88
|
function isMessageArray(value) {
|
|
84
89
|
return Array.isArray(value);
|
|
85
90
|
}
|
|
@@ -87,12 +92,20 @@ export function estimateContextTokens(context) {
|
|
|
87
92
|
if (isMessageArray(context))
|
|
88
93
|
return estimateMessages(context);
|
|
89
94
|
const estimate = estimateMessages(context.messages);
|
|
90
|
-
if (estimate.lastUsageIndex !== null)
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
+
if (estimate.lastUsageIndex !== null) {
|
|
96
|
+
const addedNames = new Set(context.messages
|
|
97
|
+
.slice(estimate.lastUsageIndex + 1)
|
|
98
|
+
.filter((message) => message.role === "toolResult")
|
|
99
|
+
.flatMap((message) => message.addedToolNames ?? []));
|
|
100
|
+
const addedToolTokens = estimateToolsTokens(context.tools?.filter((tool) => addedNames.has(tool.name)));
|
|
101
|
+
return {
|
|
102
|
+
tokens: estimate.tokens + addedToolTokens,
|
|
103
|
+
usageTokens: estimate.usageTokens,
|
|
104
|
+
trailingTokens: estimate.trailingTokens + addedToolTokens,
|
|
105
|
+
lastUsageIndex: estimate.lastUsageIndex,
|
|
106
|
+
};
|
|
95
107
|
}
|
|
108
|
+
const prefixTokens = (context.systemPrompt ? estimateTextTokens(context.systemPrompt) : 0) + estimateToolsTokens(context.tools);
|
|
96
109
|
return {
|
|
97
110
|
tokens: estimate.tokens + prefixTokens,
|
|
98
111
|
usageTokens: estimate.usageTokens,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"estimate.js","sourceRoot":"","sources":["../../src/utils/estimate.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AssistantMessage, Context, ImageContent, Message, TextContent, Usage } from \"../types.ts\";\n\nexport interface ContextUsageEstimate {\n\t/** Estimated total context tokens. */\n\ttokens: number;\n\t/** Tokens reported by the most recent applicable assistant usage block. */\n\tusageTokens: number;\n\t/** Estimated tokens after the most recent applicable assistant usage block. */\n\ttrailingTokens: number;\n\t/** Index of the applicable message that provided usage, or null when none exists. */\n\tlastUsageIndex: number | null;\n}\n\nconst CHARS_PER_TOKEN = 4;\nconst ESTIMATED_IMAGE_CHARS = 4800;\n\nexport function calculateContextTokens(usage: Usage): number {\n\treturn usage.totalTokens || usage.input + usage.output + usage.cacheRead + usage.cacheWrite;\n}\n\nfunction safeJsonStringify(value: unknown): string {\n\ttry {\n\t\treturn JSON.stringify(value) ?? \"undefined\";\n\t} catch {\n\t\treturn \"[unserializable]\";\n\t}\n}\n\nfunction estimateTextAndImageContentChars(content: string | Array<TextContent | ImageContent>): number {\n\tif (typeof content === \"string\") return content.length;\n\n\tlet chars = 0;\n\tfor (const block of content) chars += block.type === \"text\" ? block.text.length : ESTIMATED_IMAGE_CHARS;\n\treturn chars;\n}\n\nexport function estimateTextTokens(text: string): number {\n\treturn Math.ceil(text.length / CHARS_PER_TOKEN);\n}\n\nexport function estimateTextAndImageContentTokens(content: string | Array<TextContent | ImageContent>): number {\n\treturn Math.ceil(estimateTextAndImageContentChars(content) / CHARS_PER_TOKEN);\n}\n\nexport function estimateMessageTokens(message: Message): number {\n\tlet chars = 0;\n\n\tif (message.role === \"user\") return estimateTextAndImageContentTokens(message.content);\n\tif (message.role === \"toolResult\") return estimateTextAndImageContentTokens(message.content);\n\n\tfor (const block of message.content) {\n\t\tif (block.type === \"text\") {\n\t\t\tchars += block.text.length;\n\t\t} else if (block.type === \"thinking\") {\n\t\t\tchars += block.thinking.length;\n\t\t} else {\n\t\t\tchars += block.name.length + safeJsonStringify(block.arguments).length;\n\t\t}\n\t}\n\treturn Math.ceil(chars / CHARS_PER_TOKEN);\n}\n\nfunction getLastAssistantUsageInfo(messages: readonly Message[]): { usage: Usage; index: number } | undefined {\n\tlet latestPrefixTimestamp = Number.NEGATIVE_INFINITY;\n\tlet usageInfo: { usage: Usage; index: number } | undefined;\n\n\tfor (let i = 0; i < messages.length; i++) {\n\t\tconst message = messages[i];\n\t\tif (message.role === \"assistant\") {\n\t\t\tconst assistant = message as AssistantMessage;\n\t\t\t// A newer prefix message was inserted after this response (for example, a\n\t\t\t// compaction summary), so its usage cannot describe the current prefix.\n\t\t\tconst usageAppliesToPrefix = assistant.timestamp >= latestPrefixTimestamp;\n\t\t\tif (\n\t\t\t\tusageAppliesToPrefix &&\n\t\t\t\tassistant.stopReason !== \"aborted\" &&\n\t\t\t\tassistant.stopReason !== \"error\" &&\n\t\t\t\tcalculateContextTokens(assistant.usage) > 0\n\t\t\t) {\n\t\t\t\tusageInfo = { usage: assistant.usage, index: i };\n\t\t\t}\n\t\t}\n\t\tlatestPrefixTimestamp = Math.max(latestPrefixTimestamp, message.timestamp);\n\t}\n\n\treturn usageInfo;\n}\n\nfunction estimateMessages(messages: readonly Message[]): ContextUsageEstimate {\n\tconst usageInfo = getLastAssistantUsageInfo(messages);\n\tif (usageInfo) {\n\t\tconst usageTokens = calculateContextTokens(usageInfo.usage);\n\t\tlet trailingTokens = 0;\n\t\tfor (let i = usageInfo.index + 1; i < messages.length; i++) {\n\t\t\ttrailingTokens += estimateMessageTokens(messages[i]);\n\t\t}\n\t\treturn { tokens: usageTokens + trailingTokens, usageTokens, trailingTokens, lastUsageIndex: usageInfo.index };\n\t}\n\n\tlet tokens = 0;\n\tfor (const message of messages) tokens += estimateMessageTokens(message);\n\treturn { tokens, usageTokens: 0, trailingTokens: tokens, lastUsageIndex: null };\n}\n\nfunction isMessageArray(value: Context | readonly Message[]): value is readonly Message[] {\n\treturn Array.isArray(value);\n}\n\nexport function estimateContextTokens(context: Context | readonly Message[]): ContextUsageEstimate {\n\tif (isMessageArray(context)) return estimateMessages(context);\n\n\tconst estimate = estimateMessages(context.messages);\n\tif (estimate.lastUsageIndex !== null) return estimate;\n\n\tlet prefixTokens = context.systemPrompt ? estimateTextTokens(context.systemPrompt) : 0;\n\tif (context.tools && context.tools.length > 0) {\n\t\tprefixTokens += estimateTextTokens(safeJsonStringify(context.tools));\n\t}\n\n\treturn {\n\t\ttokens: estimate.tokens + prefixTokens,\n\t\tusageTokens: estimate.usageTokens,\n\t\ttrailingTokens: estimate.trailingTokens + prefixTokens,\n\t\tlastUsageIndex: estimate.lastUsageIndex,\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"estimate.js","sourceRoot":"","sources":["../../src/utils/estimate.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AssistantMessage, Context, ImageContent, Message, TextContent, Tool, Usage } from \"../types.ts\";\n\nexport interface ContextUsageEstimate {\n\t/** Estimated total context tokens. */\n\ttokens: number;\n\t/** Tokens reported by the most recent applicable assistant usage block. */\n\tusageTokens: number;\n\t/** Estimated tokens after the most recent applicable assistant usage block. */\n\ttrailingTokens: number;\n\t/** Index of the applicable message that provided usage, or null when none exists. */\n\tlastUsageIndex: number | null;\n}\n\nconst CHARS_PER_TOKEN = 4;\nconst ESTIMATED_IMAGE_CHARS = 4800;\n\nexport function calculateContextTokens(usage: Usage): number {\n\treturn usage.totalTokens || usage.input + usage.output + usage.cacheRead + usage.cacheWrite;\n}\n\nfunction safeJsonStringify(value: unknown): string {\n\ttry {\n\t\treturn JSON.stringify(value) ?? \"undefined\";\n\t} catch {\n\t\treturn \"[unserializable]\";\n\t}\n}\n\nfunction estimateTextAndImageContentChars(content: string | Array<TextContent | ImageContent>): number {\n\tif (typeof content === \"string\") return content.length;\n\n\tlet chars = 0;\n\tfor (const block of content) chars += block.type === \"text\" ? block.text.length : ESTIMATED_IMAGE_CHARS;\n\treturn chars;\n}\n\nexport function estimateTextTokens(text: string): number {\n\treturn Math.ceil(text.length / CHARS_PER_TOKEN);\n}\n\nexport function estimateTextAndImageContentTokens(content: string | Array<TextContent | ImageContent>): number {\n\treturn Math.ceil(estimateTextAndImageContentChars(content) / CHARS_PER_TOKEN);\n}\n\nexport function estimateMessageTokens(message: Message): number {\n\tlet chars = 0;\n\n\tif (message.role === \"user\") return estimateTextAndImageContentTokens(message.content);\n\tif (message.role === \"toolResult\") return estimateTextAndImageContentTokens(message.content);\n\n\tfor (const block of message.content) {\n\t\tif (block.type === \"text\") {\n\t\t\tchars += block.text.length;\n\t\t} else if (block.type === \"thinking\") {\n\t\t\tchars += block.thinking.length;\n\t\t} else {\n\t\t\tchars += block.name.length + safeJsonStringify(block.arguments).length;\n\t\t}\n\t}\n\treturn Math.ceil(chars / CHARS_PER_TOKEN);\n}\n\nfunction getLastAssistantUsageInfo(messages: readonly Message[]): { usage: Usage; index: number } | undefined {\n\tlet latestPrefixTimestamp = Number.NEGATIVE_INFINITY;\n\tlet usageInfo: { usage: Usage; index: number } | undefined;\n\n\tfor (let i = 0; i < messages.length; i++) {\n\t\tconst message = messages[i];\n\t\tif (message.role === \"assistant\") {\n\t\t\tconst assistant = message as AssistantMessage;\n\t\t\t// A newer prefix message was inserted after this response (for example, a\n\t\t\t// compaction summary), so its usage cannot describe the current prefix.\n\t\t\tconst usageAppliesToPrefix = assistant.timestamp >= latestPrefixTimestamp;\n\t\t\tif (\n\t\t\t\tusageAppliesToPrefix &&\n\t\t\t\tassistant.stopReason !== \"aborted\" &&\n\t\t\t\tassistant.stopReason !== \"error\" &&\n\t\t\t\tcalculateContextTokens(assistant.usage) > 0\n\t\t\t) {\n\t\t\t\tusageInfo = { usage: assistant.usage, index: i };\n\t\t\t}\n\t\t}\n\t\tlatestPrefixTimestamp = Math.max(latestPrefixTimestamp, message.timestamp);\n\t}\n\n\treturn usageInfo;\n}\n\nfunction estimateMessages(messages: readonly Message[]): ContextUsageEstimate {\n\tconst usageInfo = getLastAssistantUsageInfo(messages);\n\tif (usageInfo) {\n\t\tconst usageTokens = calculateContextTokens(usageInfo.usage);\n\t\tlet trailingTokens = 0;\n\t\tfor (let i = usageInfo.index + 1; i < messages.length; i++) {\n\t\t\ttrailingTokens += estimateMessageTokens(messages[i]);\n\t\t}\n\t\treturn { tokens: usageTokens + trailingTokens, usageTokens, trailingTokens, lastUsageIndex: usageInfo.index };\n\t}\n\n\tlet tokens = 0;\n\tfor (const message of messages) tokens += estimateMessageTokens(message);\n\treturn { tokens, usageTokens: 0, trailingTokens: tokens, lastUsageIndex: null };\n}\n\nfunction estimateToolsTokens(tools: readonly Tool[] | undefined): number {\n\tif (!tools || tools.length === 0) return 0;\n\treturn estimateTextTokens(safeJsonStringify(tools));\n}\n\nfunction isMessageArray(value: Context | readonly Message[]): value is readonly Message[] {\n\treturn Array.isArray(value);\n}\n\nexport function estimateContextTokens(context: Context | readonly Message[]): ContextUsageEstimate {\n\tif (isMessageArray(context)) return estimateMessages(context);\n\n\tconst estimate = estimateMessages(context.messages);\n\tif (estimate.lastUsageIndex !== null) {\n\t\tconst addedNames = new Set(\n\t\t\tcontext.messages\n\t\t\t\t.slice(estimate.lastUsageIndex + 1)\n\t\t\t\t.filter((message) => message.role === \"toolResult\")\n\t\t\t\t.flatMap((message) => message.addedToolNames ?? []),\n\t\t);\n\t\tconst addedToolTokens = estimateToolsTokens(context.tools?.filter((tool) => addedNames.has(tool.name)));\n\t\treturn {\n\t\t\ttokens: estimate.tokens + addedToolTokens,\n\t\t\tusageTokens: estimate.usageTokens,\n\t\t\ttrailingTokens: estimate.trailingTokens + addedToolTokens,\n\t\t\tlastUsageIndex: estimate.lastUsageIndex,\n\t\t};\n\t}\n\n\tconst prefixTokens =\n\t\t(context.systemPrompt ? estimateTextTokens(context.systemPrompt) : 0) + estimateToolsTokens(context.tools);\n\n\treturn {\n\t\ttokens: estimate.tokens + prefixTokens,\n\t\tusageTokens: estimate.usageTokens,\n\t\ttrailingTokens: estimate.trailingTokens + prefixTokens,\n\t\tlastUsageIndex: estimate.lastUsageIndex,\n\t};\n}\n"]}
|
|
@@ -10,6 +10,7 @@ export { anthropicOAuthProvider, loginAnthropic, refreshAnthropicToken } from ".
|
|
|
10
10
|
export * from "./device-code.ts";
|
|
11
11
|
export { getGitHubCopilotBaseUrl, githubCopilotOAuthProvider, loginGitHubCopilot, normalizeDomain, refreshGitHubCopilotToken, } from "./github-copilot.ts";
|
|
12
12
|
export { loginOpenAICodex, loginOpenAICodexDeviceCode, OPENAI_CODEX_BROWSER_LOGIN_METHOD, OPENAI_CODEX_DEVICE_CODE_LOGIN_METHOD, openaiCodexOAuthProvider, refreshOpenAICodexToken, } from "./openai-codex.ts";
|
|
13
|
+
export { createRadiusOAuthProvider, DEFAULT_RADIUS_GATEWAY, type RadiusGatewayConfig, type RadiusGatewayModel, type RadiusOAuthCredentials, type RadiusOAuthProviderOptions, } from "./radius.ts";
|
|
13
14
|
export * from "./types.ts";
|
|
14
15
|
import type { OAuthCredentials, OAuthProviderId, OAuthProviderInfo, OAuthProviderInterface } from "./types.ts";
|
|
15
16
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/oauth/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,sBAAsB,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAC/F,cAAc,kBAAkB,CAAC;AAEjC,OAAO,EACN,uBAAuB,EACvB,0BAA0B,EAC1B,kBAAkB,EAClB,eAAe,EACf,yBAAyB,GACzB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACN,gBAAgB,EAChB,0BAA0B,EAC1B,iCAAiC,EACjC,qCAAqC,EACrC,wBAAwB,EACxB,uBAAuB,GACvB,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/oauth/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,sBAAsB,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAC/F,cAAc,kBAAkB,CAAC;AAEjC,OAAO,EACN,uBAAuB,EACvB,0BAA0B,EAC1B,kBAAkB,EAClB,eAAe,EACf,yBAAyB,GACzB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACN,gBAAgB,EAChB,0BAA0B,EAC1B,iCAAiC,EACjC,qCAAqC,EACrC,wBAAwB,EACxB,uBAAuB,GACvB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACN,yBAAyB,EACzB,sBAAsB,EACtB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,sBAAsB,EAC3B,KAAK,0BAA0B,GAC/B,MAAM,aAAa,CAAC;AACrB,cAAc,YAAY,CAAC;AAW3B,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAiB/G;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,eAAe,GAAG,sBAAsB,GAAG,SAAS,CAExF;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,sBAAsB,GAAG,IAAI,CAE5E;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAOxD;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,IAAI,CAK1C;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,sBAAsB,EAAE,CAE5D;AAED;;GAEG;AACH,wBAAgB,wBAAwB,IAAI,iBAAiB,EAAE,CAM9D;AAMD;;;GAGG;AACH,wBAAsB,iBAAiB,CACtC,UAAU,EAAE,eAAe,EAC3B,WAAW,EAAE,gBAAgB,GAC3B,OAAO,CAAC,gBAAgB,CAAC,CAM3B;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CACnC,UAAU,EAAE,eAAe,EAC3B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAC3C,OAAO,CAAC;IAAE,cAAc,EAAE,gBAAgB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAsBtE","sourcesContent":["/**\n * OAuth credential management for AI providers.\n *\n * This module handles login, token refresh, and credential storage\n * for OAuth-based providers:\n * - Anthropic (Claude Pro/Max)\n * - GitHub Copilot\n */\n\n// Anthropic\nexport { anthropicOAuthProvider, loginAnthropic, refreshAnthropicToken } from \"./anthropic.ts\";\nexport * from \"./device-code.ts\";\n// GitHub Copilot\nexport {\n\tgetGitHubCopilotBaseUrl,\n\tgithubCopilotOAuthProvider,\n\tloginGitHubCopilot,\n\tnormalizeDomain,\n\trefreshGitHubCopilotToken,\n} from \"./github-copilot.ts\";\n// OpenAI Codex (ChatGPT OAuth)\nexport {\n\tloginOpenAICodex,\n\tloginOpenAICodexDeviceCode,\n\tOPENAI_CODEX_BROWSER_LOGIN_METHOD,\n\tOPENAI_CODEX_DEVICE_CODE_LOGIN_METHOD,\n\topenaiCodexOAuthProvider,\n\trefreshOpenAICodexToken,\n} from \"./openai-codex.ts\";\n\n// Radius (pi-messages gateway)\nexport {\n\tcreateRadiusOAuthProvider,\n\tDEFAULT_RADIUS_GATEWAY,\n\ttype RadiusGatewayConfig,\n\ttype RadiusGatewayModel,\n\ttype RadiusOAuthCredentials,\n\ttype RadiusOAuthProviderOptions,\n} from \"./radius.ts\";\nexport * from \"./types.ts\";\n\n// ============================================================================\n// Provider Registry\n// ============================================================================\n\nimport { getProviderEnvValue } from \"../provider-env.ts\";\nimport { anthropicOAuthProvider } from \"./anthropic.ts\";\nimport { githubCopilotOAuthProvider } from \"./github-copilot.ts\";\nimport { openaiCodexOAuthProvider } from \"./openai-codex.ts\";\nimport { createRadiusOAuthProvider, DEFAULT_RADIUS_GATEWAY } from \"./radius.ts\";\nimport type { OAuthCredentials, OAuthProviderId, OAuthProviderInfo, OAuthProviderInterface } from \"./types.ts\";\n\nconst BUILT_IN_OAUTH_PROVIDERS: OAuthProviderInterface[] = [\n\tanthropicOAuthProvider,\n\tgithubCopilotOAuthProvider,\n\topenaiCodexOAuthProvider,\n\tcreateRadiusOAuthProvider({\n\t\tid: \"radius\",\n\t\tname: \"Radius\",\n\t\tgateway: getProviderEnvValue(\"PI_GATEWAY\") || DEFAULT_RADIUS_GATEWAY,\n\t}),\n];\n\nconst oauthProviderRegistry = new Map<string, OAuthProviderInterface>(\n\tBUILT_IN_OAUTH_PROVIDERS.map((provider) => [provider.id, provider]),\n);\n\n/**\n * Get an OAuth provider by ID\n */\nexport function getOAuthProvider(id: OAuthProviderId): OAuthProviderInterface | undefined {\n\treturn oauthProviderRegistry.get(id);\n}\n\n/**\n * Register a custom OAuth provider\n */\nexport function registerOAuthProvider(provider: OAuthProviderInterface): void {\n\toauthProviderRegistry.set(provider.id, provider);\n}\n\n/**\n * Unregister an OAuth provider.\n *\n * If the provider is built-in, restores the built-in implementation.\n * Custom providers are removed completely.\n */\nexport function unregisterOAuthProvider(id: string): void {\n\tconst builtInProvider = BUILT_IN_OAUTH_PROVIDERS.find((provider) => provider.id === id);\n\tif (builtInProvider) {\n\t\toauthProviderRegistry.set(id, builtInProvider);\n\t\treturn;\n\t}\n\toauthProviderRegistry.delete(id);\n}\n\n/**\n * Reset OAuth providers to built-ins.\n */\nexport function resetOAuthProviders(): void {\n\toauthProviderRegistry.clear();\n\tfor (const provider of BUILT_IN_OAUTH_PROVIDERS) {\n\t\toauthProviderRegistry.set(provider.id, provider);\n\t}\n}\n\n/**\n * Get all registered OAuth providers\n */\nexport function getOAuthProviders(): OAuthProviderInterface[] {\n\treturn Array.from(oauthProviderRegistry.values());\n}\n\n/**\n * @deprecated Use getOAuthProviders() which returns OAuthProviderInterface[]\n */\nexport function getOAuthProviderInfoList(): OAuthProviderInfo[] {\n\treturn getOAuthProviders().map((p) => ({\n\t\tid: p.id,\n\t\tname: p.name,\n\t\tavailable: true,\n\t}));\n}\n\n// ============================================================================\n// High-level API (uses provider registry)\n// ============================================================================\n\n/**\n * Refresh token for any OAuth provider.\n * @deprecated Use getOAuthProvider(id).refreshToken() instead\n */\nexport async function refreshOAuthToken(\n\tproviderId: OAuthProviderId,\n\tcredentials: OAuthCredentials,\n): Promise<OAuthCredentials> {\n\tconst provider = getOAuthProvider(providerId);\n\tif (!provider) {\n\t\tthrow new Error(`Unknown OAuth provider: ${providerId}`);\n\t}\n\treturn provider.refreshToken(credentials);\n}\n\n/**\n * Get API key for a provider from OAuth credentials.\n * Automatically refreshes expired tokens.\n *\n * @returns API key string and updated credentials, or null if no credentials\n * @throws Error if refresh fails\n */\nexport async function getOAuthApiKey(\n\tproviderId: OAuthProviderId,\n\tcredentials: Record<string, OAuthCredentials>,\n): Promise<{ newCredentials: OAuthCredentials; apiKey: string } | null> {\n\tconst provider = getOAuthProvider(providerId);\n\tif (!provider) {\n\t\tthrow new Error(`Unknown OAuth provider: ${providerId}`);\n\t}\n\n\tlet creds = credentials[providerId];\n\tif (!creds) {\n\t\treturn null;\n\t}\n\n\t// Refresh if expired\n\tif (Date.now() >= creds.expires) {\n\t\ttry {\n\t\t\tcreds = await provider.refreshToken(creds);\n\t\t} catch (_error) {\n\t\t\tthrow new Error(`Failed to refresh OAuth token for ${providerId}`);\n\t\t}\n\t}\n\n\tconst apiKey = provider.getApiKey(creds);\n\treturn { newCredentials: creds, apiKey };\n}\n"]}
|
|
@@ -13,17 +13,26 @@ export * from "./device-code.js";
|
|
|
13
13
|
export { getGitHubCopilotBaseUrl, githubCopilotOAuthProvider, loginGitHubCopilot, normalizeDomain, refreshGitHubCopilotToken, } from "./github-copilot.js";
|
|
14
14
|
// OpenAI Codex (ChatGPT OAuth)
|
|
15
15
|
export { loginOpenAICodex, loginOpenAICodexDeviceCode, OPENAI_CODEX_BROWSER_LOGIN_METHOD, OPENAI_CODEX_DEVICE_CODE_LOGIN_METHOD, openaiCodexOAuthProvider, refreshOpenAICodexToken, } from "./openai-codex.js";
|
|
16
|
+
// Radius (pi-messages gateway)
|
|
17
|
+
export { createRadiusOAuthProvider, DEFAULT_RADIUS_GATEWAY, } from "./radius.js";
|
|
16
18
|
export * from "./types.js";
|
|
17
19
|
// ============================================================================
|
|
18
20
|
// Provider Registry
|
|
19
21
|
// ============================================================================
|
|
22
|
+
import { getProviderEnvValue } from "../provider-env.js";
|
|
20
23
|
import { anthropicOAuthProvider } from "./anthropic.js";
|
|
21
24
|
import { githubCopilotOAuthProvider } from "./github-copilot.js";
|
|
22
25
|
import { openaiCodexOAuthProvider } from "./openai-codex.js";
|
|
26
|
+
import { createRadiusOAuthProvider, DEFAULT_RADIUS_GATEWAY } from "./radius.js";
|
|
23
27
|
const BUILT_IN_OAUTH_PROVIDERS = [
|
|
24
28
|
anthropicOAuthProvider,
|
|
25
29
|
githubCopilotOAuthProvider,
|
|
26
30
|
openaiCodexOAuthProvider,
|
|
31
|
+
createRadiusOAuthProvider({
|
|
32
|
+
id: "radius",
|
|
33
|
+
name: "Radius",
|
|
34
|
+
gateway: getProviderEnvValue("PI_GATEWAY") || DEFAULT_RADIUS_GATEWAY,
|
|
35
|
+
}),
|
|
27
36
|
];
|
|
28
37
|
const oauthProviderRegistry = new Map(BUILT_IN_OAUTH_PROVIDERS.map((provider) => [provider.id, provider]));
|
|
29
38
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/utils/oauth/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,YAAY;AACZ,OAAO,EAAE,sBAAsB,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAC/F,cAAc,kBAAkB,CAAC;AACjC,iBAAiB;AACjB,OAAO,EACN,uBAAuB,EACvB,0BAA0B,EAC1B,kBAAkB,EAClB,eAAe,EACf,yBAAyB,GACzB,MAAM,qBAAqB,CAAC;AAC7B,+BAA+B;AAC/B,OAAO,EACN,gBAAgB,EAChB,0BAA0B,EAC1B,iCAAiC,EACjC,qCAAqC,EACrC,wBAAwB,EACxB,uBAAuB,GACvB,MAAM,mBAAmB,CAAC;AAE3B,cAAc,YAAY,CAAC;AAE3B,+EAA+E;AAC/E,oBAAoB;AACpB,+EAA+E;AAE/E,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/utils/oauth/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * OAuth credential management for AI providers.\n *\n * This module handles login, token refresh, and credential storage\n * for OAuth-based providers:\n * - Anthropic (Claude Pro/Max)\n * - GitHub Copilot\n */\n\n// Anthropic\nexport { anthropicOAuthProvider, loginAnthropic, refreshAnthropicToken } from \"./anthropic.ts\";\nexport * from \"./device-code.ts\";\n// GitHub Copilot\nexport {\n\tgetGitHubCopilotBaseUrl,\n\tgithubCopilotOAuthProvider,\n\tloginGitHubCopilot,\n\tnormalizeDomain,\n\trefreshGitHubCopilotToken,\n} from \"./github-copilot.ts\";\n// OpenAI Codex (ChatGPT OAuth)\nexport {\n\tloginOpenAICodex,\n\tloginOpenAICodexDeviceCode,\n\tOPENAI_CODEX_BROWSER_LOGIN_METHOD,\n\tOPENAI_CODEX_DEVICE_CODE_LOGIN_METHOD,\n\topenaiCodexOAuthProvider,\n\trefreshOpenAICodexToken,\n} from \"./openai-codex.ts\";\n\n// Radius (pi-messages gateway)\nexport {\n\tcreateRadiusOAuthProvider,\n\tDEFAULT_RADIUS_GATEWAY,\n\ttype RadiusGatewayConfig,\n\ttype RadiusGatewayModel,\n\ttype RadiusOAuthCredentials,\n\ttype RadiusOAuthProviderOptions,\n} from \"./radius.ts\";\nexport * from \"./types.ts\";\n\n// ============================================================================\n// Provider Registry\n// ============================================================================\n\nimport { getProviderEnvValue } from \"../provider-env.ts\";\nimport { anthropicOAuthProvider } from \"./anthropic.ts\";\nimport { githubCopilotOAuthProvider } from \"./github-copilot.ts\";\nimport { openaiCodexOAuthProvider } from \"./openai-codex.ts\";\nimport { createRadiusOAuthProvider, DEFAULT_RADIUS_GATEWAY } from \"./radius.ts\";\nimport type { OAuthCredentials, OAuthProviderId, OAuthProviderInfo, OAuthProviderInterface } from \"./types.ts\";\n\nconst BUILT_IN_OAUTH_PROVIDERS: OAuthProviderInterface[] = [\n\tanthropicOAuthProvider,\n\tgithubCopilotOAuthProvider,\n\topenaiCodexOAuthProvider,\n\tcreateRadiusOAuthProvider({\n\t\tid: \"radius\",\n\t\tname: \"Radius\",\n\t\tgateway: getProviderEnvValue(\"PI_GATEWAY\") || DEFAULT_RADIUS_GATEWAY,\n\t}),\n];\n\nconst oauthProviderRegistry = new Map<string, OAuthProviderInterface>(\n\tBUILT_IN_OAUTH_PROVIDERS.map((provider) => [provider.id, provider]),\n);\n\n/**\n * Get an OAuth provider by ID\n */\nexport function getOAuthProvider(id: OAuthProviderId): OAuthProviderInterface | undefined {\n\treturn oauthProviderRegistry.get(id);\n}\n\n/**\n * Register a custom OAuth provider\n */\nexport function registerOAuthProvider(provider: OAuthProviderInterface): void {\n\toauthProviderRegistry.set(provider.id, provider);\n}\n\n/**\n * Unregister an OAuth provider.\n *\n * If the provider is built-in, restores the built-in implementation.\n * Custom providers are removed completely.\n */\nexport function unregisterOAuthProvider(id: string): void {\n\tconst builtInProvider = BUILT_IN_OAUTH_PROVIDERS.find((provider) => provider.id === id);\n\tif (builtInProvider) {\n\t\toauthProviderRegistry.set(id, builtInProvider);\n\t\treturn;\n\t}\n\toauthProviderRegistry.delete(id);\n}\n\n/**\n * Reset OAuth providers to built-ins.\n */\nexport function resetOAuthProviders(): void {\n\toauthProviderRegistry.clear();\n\tfor (const provider of BUILT_IN_OAUTH_PROVIDERS) {\n\t\toauthProviderRegistry.set(provider.id, provider);\n\t}\n}\n\n/**\n * Get all registered OAuth providers\n */\nexport function getOAuthProviders(): OAuthProviderInterface[] {\n\treturn Array.from(oauthProviderRegistry.values());\n}\n\n/**\n * @deprecated Use getOAuthProviders() which returns OAuthProviderInterface[]\n */\nexport function getOAuthProviderInfoList(): OAuthProviderInfo[] {\n\treturn getOAuthProviders().map((p) => ({\n\t\tid: p.id,\n\t\tname: p.name,\n\t\tavailable: true,\n\t}));\n}\n\n// ============================================================================\n// High-level API (uses provider registry)\n// ============================================================================\n\n/**\n * Refresh token for any OAuth provider.\n * @deprecated Use getOAuthProvider(id).refreshToken() instead\n */\nexport async function refreshOAuthToken(\n\tproviderId: OAuthProviderId,\n\tcredentials: OAuthCredentials,\n): Promise<OAuthCredentials> {\n\tconst provider = getOAuthProvider(providerId);\n\tif (!provider) {\n\t\tthrow new Error(`Unknown OAuth provider: ${providerId}`);\n\t}\n\treturn provider.refreshToken(credentials);\n}\n\n/**\n * Get API key for a provider from OAuth credentials.\n * Automatically refreshes expired tokens.\n *\n * @returns API key string and updated credentials, or null if no credentials\n * @throws Error if refresh fails\n */\nexport async function getOAuthApiKey(\n\tproviderId: OAuthProviderId,\n\tcredentials: Record<string, OAuthCredentials>,\n): Promise<{ newCredentials: OAuthCredentials; apiKey: string } | null> {\n\tconst provider = getOAuthProvider(providerId);\n\tif (!provider) {\n\t\tthrow new Error(`Unknown OAuth provider: ${providerId}`);\n\t}\n\n\tlet creds = credentials[providerId];\n\tif (!creds) {\n\t\treturn null;\n\t}\n\n\t// Refresh if expired\n\tif (Date.now() >= creds.expires) {\n\t\ttry {\n\t\t\tcreds = await provider.refreshToken(creds);\n\t\t} catch (_error) {\n\t\t\tthrow new Error(`Failed to refresh OAuth token for ${providerId}`);\n\t\t}\n\t}\n\n\tconst apiKey = provider.getApiKey(creds);\n\treturn { newCredentials: creds, apiKey };\n}\n"]}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Radius gateway OAuth flow and model catalog loading.
|
|
3
|
+
*
|
|
4
|
+
* Radius is a pi-messages gateway. OAuth endpoints are discovered from the
|
|
5
|
+
* gateway (`/v1/oauth`); the model catalog comes from `/v1/config` and is
|
|
6
|
+
* cached on the stored credential (`gatewayConfig`) so models are available
|
|
7
|
+
* at startup and refreshed whenever the token refreshes.
|
|
8
|
+
*
|
|
9
|
+
* NOTE: This module uses node:http for the OAuth callback server.
|
|
10
|
+
* It is only intended for CLI use, not browser environments.
|
|
11
|
+
*/
|
|
12
|
+
import type { Api, Model, ThinkingLevelMap } from "../../types.ts";
|
|
13
|
+
import type { OAuthCredentials, OAuthProviderInterface } from "./types.ts";
|
|
14
|
+
export declare const DEFAULT_RADIUS_GATEWAY = "https://radius.pi.dev";
|
|
15
|
+
/** Model metadata served by the gateway config endpoint. */
|
|
16
|
+
export type RadiusGatewayModel = {
|
|
17
|
+
id: string;
|
|
18
|
+
name: string;
|
|
19
|
+
reasoning: boolean;
|
|
20
|
+
thinkingLevelMap?: ThinkingLevelMap;
|
|
21
|
+
input: ("text" | "image")[];
|
|
22
|
+
cost: Model<Api>["cost"];
|
|
23
|
+
contextWindow: number;
|
|
24
|
+
maxTokens: number;
|
|
25
|
+
};
|
|
26
|
+
export type RadiusGatewayConfig = {
|
|
27
|
+
baseUrl: string;
|
|
28
|
+
models: RadiusGatewayModel[];
|
|
29
|
+
};
|
|
30
|
+
export type RadiusOAuthCredentials = OAuthCredentials & {
|
|
31
|
+
gatewayConfig?: RadiusGatewayConfig;
|
|
32
|
+
};
|
|
33
|
+
export interface RadiusOAuthProviderOptions {
|
|
34
|
+
id: string;
|
|
35
|
+
name: string;
|
|
36
|
+
gateway: string;
|
|
37
|
+
}
|
|
38
|
+
export declare function createRadiusOAuthProvider(options: RadiusOAuthProviderOptions): OAuthProviderInterface;
|
|
39
|
+
//# sourceMappingURL=radius.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"radius.d.ts","sourceRoot":"","sources":["../../../src/utils/oauth/radius.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAUH,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAInE,OAAO,KAAK,EAAE,gBAAgB,EAAuB,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEhG,eAAO,MAAM,sBAAsB,0BAA0B,CAAC;AAU9D,4DAA4D;AAC5D,MAAM,MAAM,kBAAkB,GAAG;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;IACnB,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,KAAK,EAAE,CAAC,MAAM,GAAG,OAAO,CAAC,EAAE,CAAC;IAC5B,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,kBAAkB,EAAE,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG,gBAAgB,GAAG;IACvD,aAAa,CAAC,EAAE,mBAAmB,CAAC;CACpC,CAAC;AAmaF,MAAM,WAAW,0BAA0B;IAC1C,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,0BAA0B,GAAG,sBAAsB,CA4ErG","sourcesContent":["/**\n * Radius gateway OAuth flow and model catalog loading.\n *\n * Radius is a pi-messages gateway. OAuth endpoints are discovered from the\n * gateway (`/v1/oauth`); the model catalog comes from `/v1/config` and is\n * cached on the stored credential (`gatewayConfig`) so models are available\n * at startup and refreshed whenever the token refreshes.\n *\n * NOTE: This module uses node:http for the OAuth callback server.\n * It is only intended for CLI use, not browser environments.\n */\n\n// NEVER convert to top-level imports - breaks browser/Vite builds\nlet _http: typeof import(\"node:http\") | null = null;\nif (typeof process !== \"undefined\" && (process.versions?.node || process.versions?.bun)) {\n\timport(\"node:http\").then((m) => {\n\t\t_http = m;\n\t});\n}\n\nimport type { Api, Model, ThinkingLevelMap } from \"../../types.ts\";\nimport { pollOAuthDeviceCodeFlow } from \"./device-code.ts\";\nimport { oauthErrorHtml, oauthSuccessHtml } from \"./oauth-page.ts\";\nimport { generatePKCE } from \"./pkce.ts\";\nimport type { OAuthCredentials, OAuthLoginCallbacks, OAuthProviderInterface } from \"./types.ts\";\n\nexport const DEFAULT_RADIUS_GATEWAY = \"https://radius.pi.dev\";\n\nconst CALLBACK_HOST = \"127.0.0.1\";\nconst CALLBACK_PORT = 1456;\nconst CALLBACK_PATH = \"/oauth/callback\";\nconst REDIRECT_URI = `http://${CALLBACK_HOST}:${CALLBACK_PORT}${CALLBACK_PATH}`;\nconst TOKEN_EXPIRY_SKEW_MS = 60_000;\nconst LOGIN_METHOD_BROWSER = \"browser\";\nconst LOGIN_METHOD_DEVICE_CODE = \"device-code\";\n\n/** Model metadata served by the gateway config endpoint. */\nexport type RadiusGatewayModel = {\n\tid: string;\n\tname: string;\n\treasoning: boolean;\n\tthinkingLevelMap?: ThinkingLevelMap;\n\tinput: (\"text\" | \"image\")[];\n\tcost: Model<Api>[\"cost\"];\n\tcontextWindow: number;\n\tmaxTokens: number;\n};\n\nexport type RadiusGatewayConfig = {\n\tbaseUrl: string;\n\tmodels: RadiusGatewayModel[];\n};\n\nexport type RadiusOAuthCredentials = OAuthCredentials & {\n\tgatewayConfig?: RadiusGatewayConfig;\n};\n\ntype RadiusOAuthConfig = {\n\tissuer: string;\n\tauthorizationEndpoint: string;\n\ttokenEndpoint: string;\n\tdeviceAuthorizationEndpoint: string;\n\tdeviceAuthorizationEventsEndpoint: string;\n\tverificationEndpoint: string;\n\tclientId: string;\n\tscope: string;\n\tdeviceCodeGrantType: string;\n};\n\ntype DeviceAuthorizationResponse = {\n\tdevice_code: string;\n\tuser_code: string;\n\tverification_uri?: string;\n\tverification_uri_complete?: string;\n\texpires_in: number;\n\tinterval?: number;\n};\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction normalizeRadiusGatewayUrl(value: string): string {\n\tconst withScheme = /^https?:\\/\\//iu.test(value) ? value : `https://${value}`;\n\treturn withScheme.replace(/\\/+$/u, \"\");\n}\n\n// The gateway is a trusted first-party service. The shape checks below only\n// guard against version skew and stale credential caches: malformed entries\n// are dropped rather than failing the whole catalog, and nested fields (e.g.\n// `input` members, `cost` rates) are intentionally not validated in depth.\n// Do not turn this into strict validation.\nfunction isRadiusGatewayModel(value: unknown): value is RadiusGatewayModel {\n\tif (!isRecord(value)) {\n\t\treturn false;\n\t}\n\treturn (\n\t\ttypeof value.id === \"string\" &&\n\t\ttypeof value.name === \"string\" &&\n\t\ttypeof value.reasoning === \"boolean\" &&\n\t\tArray.isArray(value.input) &&\n\t\tisRecord(value.cost) &&\n\t\ttypeof value.contextWindow === \"number\" &&\n\t\ttypeof value.maxTokens === \"number\"\n\t);\n}\n\nfunction sanitizeRadiusGatewayConfig(config: unknown): RadiusGatewayConfig | undefined {\n\tif (!isRecord(config)) {\n\t\treturn undefined;\n\t}\n\tconst baseUrl = config.baseUrl;\n\tconst models = config.models;\n\tif (typeof baseUrl !== \"string\" || !Array.isArray(models)) {\n\t\treturn undefined;\n\t}\n\n\treturn {\n\t\tbaseUrl,\n\t\tmodels: models.filter(isRadiusGatewayModel).map((model) => ({ ...model })),\n\t};\n}\n\nfunction getRadiusCredentialConfig(credentials: OAuthCredentials | undefined): RadiusGatewayConfig | undefined {\n\treturn sanitizeRadiusGatewayConfig((credentials as RadiusOAuthCredentials | undefined)?.gatewayConfig);\n}\n\nfunction truncateHttpBody(body: string): string {\n\tconst trimmed = body.trim();\n\treturn trimmed.length > 512 ? `${trimmed.slice(0, 512)}…` : trimmed;\n}\n\nasync function loadRadiusGatewayConfig(gateway: string, apiKey?: string): Promise<RadiusGatewayConfig> {\n\tconst headers: Record<string, string> = { accept: \"application/json\" };\n\tif (apiKey) {\n\t\theaders.authorization = `Bearer ${apiKey}`;\n\t}\n\n\tconst response = await fetch(new URL(\"/v1/config\", gateway), { headers });\n\tif (!response.ok) {\n\t\tthrow new Error(\n\t\t\t`Could not load Radius config from ${gateway}: ${response.status}: ${truncateHttpBody(await response.text())}`,\n\t\t);\n\t}\n\n\tconst config = sanitizeRadiusGatewayConfig(await response.json());\n\tif (!config) {\n\t\tthrow new Error(`Invalid Radius config from ${gateway}`);\n\t}\n\treturn config;\n}\n\nasync function loadRadiusOAuthConfig(gateway: string): Promise<RadiusOAuthConfig> {\n\tconst response = await fetch(new URL(\"/v1/oauth\", gateway), {\n\t\theaders: { accept: \"application/json\" },\n\t});\n\n\tif (!response.ok) {\n\t\tthrow new Error(\n\t\t\t`Could not load Radius OAuth config from ${gateway}: ${response.status} ${await response.text()}`,\n\t\t);\n\t}\n\n\treturn (await response.json()) as RadiusOAuthConfig;\n}\n\nclass OAuthResponseError extends Error {\n\treadonly status: number;\n\treadonly oauthError?: string;\n\n\tconstructor(status: number, oauthError: string | undefined, description: string | undefined, message: string) {\n\t\tconst detail = oauthError\n\t\t\t? description\n\t\t\t\t? `${oauthError}: ${description}`\n\t\t\t\t: oauthError\n\t\t\t: description || String(status);\n\t\tsuper(`${message}: ${detail}`);\n\t\tthis.status = status;\n\t\tthis.oauthError = oauthError;\n\t}\n}\n\nasync function readOAuthResponseError(response: Response, message: string): Promise<OAuthResponseError> {\n\tconst text = await response.text().catch(() => \"\");\n\tlet oauthError: string | undefined;\n\tlet description: string | undefined;\n\n\tif (text) {\n\t\ttry {\n\t\t\tconst data = JSON.parse(text) as { error?: unknown; error_description?: unknown };\n\t\t\toauthError = typeof data.error === \"string\" ? data.error : undefined;\n\t\t\tdescription = typeof data.error_description === \"string\" ? data.error_description : undefined;\n\t\t} catch {\n\t\t\tdescription = text;\n\t\t}\n\t}\n\n\treturn new OAuthResponseError(response.status, oauthError, description, message);\n}\n\nasync function requestOAuthToken(\n\toauth: RadiusOAuthConfig,\n\tbody: URLSearchParams,\n\tsignal?: AbortSignal,\n): Promise<OAuthCredentials> {\n\tlet response: Response;\n\ttry {\n\t\tresponse = await fetch(oauth.tokenEndpoint, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { accept: \"application/json\", \"content-type\": \"application/x-www-form-urlencoded\" },\n\t\t\tbody,\n\t\t\tsignal,\n\t\t});\n\t} catch (error) {\n\t\tif (signal?.aborted) {\n\t\t\tthrow new Error(\"Login cancelled\");\n\t\t}\n\t\tthrow error;\n\t}\n\n\tif (!response.ok) {\n\t\tthrow await readOAuthResponseError(response, \"Radius OAuth token request failed\");\n\t}\n\n\tconst data = (await response.json()) as {\n\t\taccess_token: string;\n\t\trefresh_token: string;\n\t\texpires_in: number;\n\t\tscope?: string;\n\t};\n\n\treturn {\n\t\taccess: data.access_token,\n\t\trefresh: data.refresh_token,\n\t\texpires: Date.now() + data.expires_in * 1000 - TOKEN_EXPIRY_SKEW_MS,\n\t\tscope: data.scope,\n\t};\n}\n\ntype OAuthCallbackServer = {\n\twaitForCode(): Promise<string | null>;\n\tclose(): void;\n};\n\nfunction startOAuthCallbackServer(\n\texpectedState: string,\n\tsignal: AbortSignal | undefined,\n): Promise<OAuthCallbackServer> {\n\tif (!_http) {\n\t\tthrow new Error(\"Radius OAuth is only available in Node.js environments\");\n\t}\n\n\tlet settle: (code: string | null) => void = () => {};\n\tlet settled = false;\n\tconst wait = new Promise<string | null>((resolve) => {\n\t\tsettle = resolve;\n\t});\n\tconst finish = (code: string | null) => {\n\t\tif (settled) {\n\t\t\treturn;\n\t\t}\n\t\tsettled = true;\n\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\tsettle(code);\n\t};\n\tconst onAbort = () => finish(null);\n\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\tconst sendPage = (response: import(\"node:http\").ServerResponse, status: number, html: string) => {\n\t\tresponse.statusCode = status;\n\t\tresponse.setHeader(\"content-type\", \"text/html; charset=utf-8\");\n\t\tresponse.end(html);\n\t};\n\n\tconst server = _http.createServer((request, response) => {\n\t\tconst url = new URL(request.url ?? \"/\", REDIRECT_URI);\n\t\tif (url.pathname !== CALLBACK_PATH) {\n\t\t\tsendPage(response, 404, oauthErrorHtml(\"Callback route not found.\"));\n\t\t\treturn;\n\t\t}\n\t\tif (url.searchParams.get(\"state\") !== expectedState) {\n\t\t\tsendPage(response, 400, oauthErrorHtml(\"OAuth state mismatch.\"));\n\t\t\treturn;\n\t\t}\n\n\t\tconst error = url.searchParams.get(\"error\");\n\t\tif (error) {\n\t\t\tsendPage(response, 400, oauthErrorHtml(url.searchParams.get(\"error_description\") ?? error));\n\t\t\tfinish(null);\n\t\t\treturn;\n\t\t}\n\n\t\tconst code = url.searchParams.get(\"code\");\n\t\tif (!code) {\n\t\t\tsendPage(response, 400, oauthErrorHtml(\"Missing authorization code.\"));\n\t\t\treturn;\n\t\t}\n\n\t\tsendPage(response, 200, oauthSuccessHtml(\"Signed in to Radius. You may now close this page.\"));\n\t\tfinish(code);\n\t});\n\n\treturn new Promise((resolve) => {\n\t\tserver\n\t\t\t.listen(CALLBACK_PORT, CALLBACK_HOST, () => {\n\t\t\t\tresolve({\n\t\t\t\t\twaitForCode: () => wait,\n\t\t\t\t\tclose: () => {\n\t\t\t\t\t\tfinish(null);\n\t\t\t\t\t\tserver.close();\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t})\n\t\t\t.once(\"error\", () => {\n\t\t\t\tfinish(null);\n\t\t\t\tresolve({ waitForCode: async () => null, close: () => {} });\n\t\t\t});\n\t});\n}\n\nasync function loginWithBrowser(oauth: RadiusOAuthConfig, callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n\tconst { verifier, challenge } = await generatePKCE();\n\tconst state = crypto.randomUUID();\n\tconst authorizeUrl = new URL(oauth.authorizationEndpoint);\n\tauthorizeUrl.search = new URLSearchParams({\n\t\tresponse_type: \"code\",\n\t\tclient_id: oauth.clientId,\n\t\tredirect_uri: REDIRECT_URI,\n\t\tscope: oauth.scope,\n\t\tcode_challenge: challenge,\n\t\tcode_challenge_method: \"S256\",\n\t\thandoff: \"url\",\n\t\tstate,\n\t}).toString();\n\n\tconst callbackServer = await startOAuthCallbackServer(state, callbacks.signal);\n\tcallbacks.onProgress?.(`Listening for OAuth callback on ${REDIRECT_URI}`);\n\tcallbacks.onAuth({\n\t\turl: authorizeUrl.toString(),\n\t\tinstructions: \"Continue in your browser.\",\n\t});\n\n\ttry {\n\t\tconst code = await callbackServer.waitForCode();\n\t\tif (!code) {\n\t\t\tif (callbacks.signal?.aborted) {\n\t\t\t\tthrow new Error(\"Login cancelled\");\n\t\t\t}\n\t\t\tthrow new Error(\"OAuth callback did not complete.\");\n\t\t}\n\t\treturn await requestOAuthToken(\n\t\t\toauth,\n\t\t\tnew URLSearchParams({\n\t\t\t\tgrant_type: \"authorization_code\",\n\t\t\t\tclient_id: oauth.clientId,\n\t\t\t\tredirect_uri: REDIRECT_URI,\n\t\t\t\tcode,\n\t\t\t\tcode_verifier: verifier,\n\t\t\t}),\n\t\t\tcallbacks.signal,\n\t\t);\n\t} finally {\n\t\tcallbackServer.close();\n\t}\n}\n\nasync function requestDeviceAuthorization(\n\toauth: RadiusOAuthConfig,\n\tsignal: AbortSignal | undefined,\n): Promise<DeviceAuthorizationResponse> {\n\tlet response: Response;\n\ttry {\n\t\tresponse = await fetch(oauth.deviceAuthorizationEndpoint, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { accept: \"application/json\", \"content-type\": \"application/x-www-form-urlencoded\" },\n\t\t\tbody: new URLSearchParams({ client_id: oauth.clientId, scope: oauth.scope }),\n\t\t\tsignal,\n\t\t});\n\t} catch (error) {\n\t\tif (signal?.aborted) {\n\t\t\tthrow new Error(\"Login cancelled\");\n\t\t}\n\t\tthrow error;\n\t}\n\n\tif (!response.ok) {\n\t\tthrow await readOAuthResponseError(response, \"Radius OAuth device authorization failed\");\n\t}\n\n\tconst data = (await response.json()) as Partial<DeviceAuthorizationResponse>;\n\tif (!data.device_code || !data.user_code || !data.expires_in) {\n\t\tthrow new Error(\"Radius OAuth device authorization response is missing required fields\");\n\t}\n\n\treturn {\n\t\tdevice_code: data.device_code,\n\t\tuser_code: data.user_code,\n\t\tverification_uri: data.verification_uri,\n\t\tverification_uri_complete: data.verification_uri_complete,\n\t\texpires_in: data.expires_in,\n\t\tinterval: data.interval,\n\t};\n}\n\nasync function loginWithDeviceCode(\n\toauth: RadiusOAuthConfig,\n\tcallbacks: OAuthLoginCallbacks,\n): Promise<OAuthCredentials> {\n\tconst device = await requestDeviceAuthorization(oauth, callbacks.signal);\n\tcallbacks.onDeviceCode({\n\t\tuserCode: device.user_code,\n\t\tverificationUri: device.verification_uri || oauth.verificationEndpoint,\n\t\tintervalSeconds: device.interval,\n\t\texpiresInSeconds: device.expires_in,\n\t});\n\n\treturn pollOAuthDeviceCodeFlow<OAuthCredentials>({\n\t\tintervalSeconds: device.interval,\n\t\texpiresInSeconds: device.expires_in,\n\t\tsignal: callbacks.signal,\n\t\tpoll: async () => {\n\t\t\ttry {\n\t\t\t\tconst credentials = await requestOAuthToken(\n\t\t\t\t\toauth,\n\t\t\t\t\tnew URLSearchParams({\n\t\t\t\t\t\tgrant_type: oauth.deviceCodeGrantType,\n\t\t\t\t\t\tclient_id: oauth.clientId,\n\t\t\t\t\t\tdevice_code: device.device_code,\n\t\t\t\t\t}),\n\t\t\t\t\tcallbacks.signal,\n\t\t\t\t);\n\t\t\t\treturn { status: \"complete\", value: credentials };\n\t\t\t} catch (error) {\n\t\t\t\tif (!(error instanceof OAuthResponseError)) {\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t\tswitch (error.oauthError) {\n\t\t\t\t\tcase \"authorization_pending\":\n\t\t\t\t\t\treturn { status: \"pending\" };\n\t\t\t\t\tcase \"slow_down\":\n\t\t\t\t\t\treturn { status: \"slow_down\" };\n\t\t\t\t\tcase \"expired_token\":\n\t\t\t\t\t\treturn { status: \"failed\", message: \"Device authorization expired.\" };\n\t\t\t\t\tcase \"access_denied\":\n\t\t\t\t\t\treturn { status: \"failed\", message: \"Device authorization was denied.\" };\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t});\n}\n\nasync function attachGatewayConfig(\n\tgateway: string,\n\tcredentials: OAuthCredentials,\n\tprevious?: OAuthCredentials,\n): Promise<RadiusOAuthCredentials> {\n\ttry {\n\t\tconst config = await loadRadiusGatewayConfig(gateway, credentials.access);\n\t\treturn { ...credentials, gatewayConfig: config };\n\t} catch (error) {\n\t\t// Keep the previous catalog so models do not vanish on transient\n\t\t// config failures; the next token refresh retries.\n\t\tconst previousConfig = getRadiusCredentialConfig(previous);\n\t\tif (previousConfig) {\n\t\t\treturn { ...credentials, gatewayConfig: previousConfig };\n\t\t}\n\t\t// No catalog to retain (e.g. initial login): fail loudly instead of\n\t\t// completing a sign-in that would register no models.\n\t\tthrow error;\n\t}\n}\n\nexport interface RadiusOAuthProviderOptions {\n\tid: string;\n\tname: string;\n\tgateway: string;\n}\n\nexport function createRadiusOAuthProvider(options: RadiusOAuthProviderOptions): OAuthProviderInterface {\n\tconst gateway = normalizeRadiusGatewayUrl(options.gateway);\n\n\treturn {\n\t\tid: options.id,\n\t\tname: options.name,\n\n\t\tasync login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n\t\t\tconst oauth = await loadRadiusOAuthConfig(gateway);\n\t\t\tconst loginMethod = await callbacks.onSelect({\n\t\t\t\tmessage: `Sign in to ${options.name}:`,\n\t\t\t\toptions: [\n\t\t\t\t\t{ id: LOGIN_METHOD_BROWSER, label: \"Sign in with browser (recommended)\" },\n\t\t\t\t\t{\n\t\t\t\t\t\tid: LOGIN_METHOD_DEVICE_CODE,\n\t\t\t\t\t\tlabel: \"Sign in with device code (when signing in from another device)\",\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t});\n\t\t\tif (!loginMethod) {\n\t\t\t\tthrow new Error(\"Login cancelled\");\n\t\t\t}\n\n\t\t\tlet credentials: OAuthCredentials;\n\t\t\tif (loginMethod === LOGIN_METHOD_DEVICE_CODE) {\n\t\t\t\tcredentials = await loginWithDeviceCode(oauth, callbacks);\n\t\t\t} else if (loginMethod === LOGIN_METHOD_BROWSER) {\n\t\t\t\tcredentials = await loginWithBrowser(oauth, callbacks);\n\t\t\t} else {\n\t\t\t\tthrow new Error(`Unknown ${options.name} sign-in method: ${loginMethod}`);\n\t\t\t}\n\n\t\t\treturn attachGatewayConfig(gateway, credentials);\n\t\t},\n\n\t\tasync refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {\n\t\t\tconst oauth = await loadRadiusOAuthConfig(gateway);\n\t\t\tconst refreshed = await requestOAuthToken(\n\t\t\t\toauth,\n\t\t\t\tnew URLSearchParams({\n\t\t\t\t\tgrant_type: \"refresh_token\",\n\t\t\t\t\tclient_id: oauth.clientId,\n\t\t\t\t\trefresh_token: credentials.refresh,\n\t\t\t\t}),\n\t\t\t);\n\t\t\treturn attachGatewayConfig(gateway, refreshed, credentials);\n\t\t},\n\n\t\tgetApiKey(credentials: OAuthCredentials): string {\n\t\t\treturn credentials.access;\n\t\t},\n\n\t\tmodifyModels(models: Model<Api>[], credentials: OAuthCredentials): Model<Api>[] {\n\t\t\tconst config = getRadiusCredentialConfig(credentials);\n\t\t\tif (!config) {\n\t\t\t\treturn models;\n\t\t\t}\n\n\t\t\t// Keep models already registered for this provider (e.g. models.json\n\t\t\t// custom entries) and add catalog models that are not present.\n\t\t\tconst existingIds = new Set(models.filter((model) => model.provider === options.id).map((model) => model.id));\n\t\t\tconst added = config.models\n\t\t\t\t.filter((model) => !existingIds.has(model.id))\n\t\t\t\t.map(\n\t\t\t\t\t(model) =>\n\t\t\t\t\t\t({\n\t\t\t\t\t\t\t...model,\n\t\t\t\t\t\t\tapi: \"pi-messages\",\n\t\t\t\t\t\t\tprovider: options.id,\n\t\t\t\t\t\t\tbaseUrl: config.baseUrl,\n\t\t\t\t\t\t}) as Model<Api>,\n\t\t\t\t);\n\n\t\t\treturn [...models, ...added];\n\t\t},\n\t};\n}\n"]}
|