diff --git a/examples/chat-ui/src/App.tsx b/examples/chat-ui/src/App.tsx index f390de7..59c2878 100644 --- a/examples/chat-ui/src/App.tsx +++ b/examples/chat-ui/src/App.tsx @@ -1,13 +1,15 @@ import { BrowserRouter as Router, Routes, Route } from 'react-router-dom' import ChatApp from './components/ChatApp' -import OAuthCallback from './components/OAuthCallback' +import PkceCallback from './components/PkceCallback.tsx' +import { OAuthCallback } from './components/OAuthCallback.tsx' function App() { return ( - } /> - } /> + } /> + } /> + } /> } /> diff --git a/examples/chat-ui/src/components/ChatApp.tsx b/examples/chat-ui/src/components/ChatApp.tsx index 6db7a43..847ac3e 100644 --- a/examples/chat-ui/src/components/ChatApp.tsx +++ b/examples/chat-ui/src/components/ChatApp.tsx @@ -33,7 +33,9 @@ const ChatApp: React.FC = () => { // Handle OAuth success messages from popups useEffect(() => { const handleMessage = (event: MessageEvent) => { + console.log('DEBUG: Received message in parent window:', event.data) if (event.data.type === 'oauth_success') { + console.log('DEBUG: OAuth success message received, triggering API key update') handleApiKeyUpdate() } } @@ -42,6 +44,96 @@ const ChatApp: React.FC = () => { return () => window.removeEventListener('message', handleMessage) }, []) + // Poll for OAuth token changes (fallback for when popup messaging doesn't work) + useEffect(() => { + const oauthProviders = ['groq', 'openrouter'] as const + let initialTokens: Record = {} + + // Capture initial token state + const captureInitialTokens = () => { + for (const providerId of oauthProviders) { + const tokenKey = `aiChatTemplate_token_${providerId}` + initialTokens[providerId] = localStorage.getItem(tokenKey) + } + } + + const checkForNewTokens = () => { + for (const providerId of oauthProviders) { + const tokenKey = `aiChatTemplate_token_${providerId}` + const currentToken = localStorage.getItem(tokenKey) + + // Check if token was added or changed + if (currentToken !== initialTokens[providerId]) { + try { + const parsedToken = JSON.parse(currentToken || '{}') + if (parsedToken.access_token) { + console.log('DEBUG: New OAuth token detected for', providerId, 'via polling') + handleApiKeyUpdate() + return true // Stop polling once we find a new token + } + } catch (e) { + // Invalid token format, continue checking + } + } + } + return false + } + + let pollInterval: number | null = null + + const startPolling = () => { + // Capture initial state + captureInitialTokens() + + // Check immediately for existing valid tokens (in case we just redirected from OAuth) + console.log('DEBUG: Checking for existing OAuth tokens on startup') + for (const providerId of oauthProviders) { + const tokenKey = `aiChatTemplate_token_${providerId}` + const currentToken = localStorage.getItem(tokenKey) + + console.log(`DEBUG: Checking ${providerId} token:`, currentToken ? 'exists' : 'not found') + + if (currentToken) { + try { + const parsedToken = JSON.parse(currentToken) + console.log(`DEBUG: Parsed ${providerId} token:`, parsedToken) + if (parsedToken.access_token) { + console.log('DEBUG: Found existing OAuth token for', providerId, 'on startup') + handleApiKeyUpdate() + return // Don't start polling if we found a valid token + } + } catch (e) { + console.log(`DEBUG: Failed to parse ${providerId} token:`, e) + } + } + } + + // Start polling every 500ms for new tokens + pollInterval = setInterval(() => { + if (checkForNewTokens()) { + if (pollInterval) { + clearInterval(pollInterval) + pollInterval = null + console.log('DEBUG: Stopped polling for OAuth tokens') + } + } + }, 500) + + console.log('DEBUG: Started polling for OAuth token changes') + } + + // Start polling after a short delay to allow for popup messages first + const delayedStart = setTimeout(startPolling, 100) + + return () => { + if (delayedStart) clearTimeout(delayedStart) + if (pollInterval) { + clearInterval(pollInterval) + console.log('DEBUG: Cleaned up OAuth token polling') + } + } + }, []) + const handleModelChange = (model: Model) => { setSelectedModel(model) saveSelectedModel(model) diff --git a/examples/chat-ui/src/components/ModelSelector.tsx b/examples/chat-ui/src/components/ModelSelector.tsx index 973e6b1..b87592c 100644 --- a/examples/chat-ui/src/components/ModelSelector.tsx +++ b/examples/chat-ui/src/components/ModelSelector.tsx @@ -39,7 +39,9 @@ const ModelSelector: React.FC = ({ selectedModel, onModelCha // Handle OAuth success - show provider models when OAuth completes useEffect(() => { const handleMessage = (event: MessageEvent) => { + console.log('DEBUG: ModelSelector received message:', event.data) if (event.data.type === 'oauth_success' && event.data.provider) { + console.log('DEBUG: ModelSelector opening provider models modal for:', event.data.provider) const provider = providers[event.data.provider as keyof typeof providers] if (provider) { setProviderModelsModal({ isOpen: true, provider }) diff --git a/examples/chat-ui/src/components/OAuthCallback.tsx b/examples/chat-ui/src/components/OAuthCallback.tsx index 01beae1..d6f57fa 100644 --- a/examples/chat-ui/src/components/OAuthCallback.tsx +++ b/examples/chat-ui/src/components/OAuthCallback.tsx @@ -1,124 +1,22 @@ -import React, { useEffect, useState, useRef } from 'react' -import { useSearchParams } from 'react-router-dom' -import { completeOAuthFlow } from '../utils/auth' -import { SupportedProvider } from '../types/models' - -interface OAuthCallbackProps { - provider: SupportedProvider -} - -const OAuthCallback: React.FC = ({ provider }) => { - const [searchParams] = useSearchParams() - const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading') - const [error, setError] = useState(null) - const executedRef = useRef(false) +import { useEffect } from 'react' +import { onMcpAuthorization } from 'use-mcp' +export function OAuthCallback() { useEffect(() => { - const handleCallback = async () => { - if (executedRef.current) { - console.log('DEBUG: Skipping duplicate OAuth callback execution') - return - } - executedRef.current = true - - try { - const code = searchParams.get('code') - const state = searchParams.get('state') - const error = searchParams.get('error') - - if (error) { - throw new Error(`OAuth error: ${error}`) - } - - if (!code) { - throw new Error('Missing authorization code') - } - - // OpenRouter doesn't use state parameter, but other providers might - const stateToUse = state || 'no-state' - await completeOAuthFlow(provider, code, stateToUse) - setStatus('success') - - // Close popup after successful authentication - // Give extra time for debugging in development - setTimeout(() => { - if (window.opener) { - window.opener.postMessage({ type: 'oauth_success', provider }, '*') - window.close() - } else { - // Redirect to main page if not in popup - window.location.href = '/' - } - }, 3000) - } catch (err) { - console.error('OAuth callback error:', err) - setError(err instanceof Error ? err.message : 'Unknown error') - setStatus('error') - } - } - - handleCallback() - }, [searchParams, provider]) + onMcpAuthorization() + }, []) return ( -
-
-
- {status === 'loading' && ( - <> -
-

Completing Authentication

-

Connecting to {provider}...

- - )} - - {status === 'success' && ( - <> -
- - - -
-

Authentication Successful!

-

Successfully connected to {provider}. You can now close this window.

- - - )} - - {status === 'error' && ( - <> -
- - - -
-

Authentication Failed

-

{error || 'An error occurred during authentication'}

- - - )} +
+
+

Authenticating...

+

Please wait while we complete your authentication.

+

This window should close automatically.

+ +
+
) } - -export default OAuthCallback diff --git a/examples/chat-ui/src/components/PkceCallback.tsx b/examples/chat-ui/src/components/PkceCallback.tsx new file mode 100644 index 0000000..0502ef3 --- /dev/null +++ b/examples/chat-ui/src/components/PkceCallback.tsx @@ -0,0 +1,153 @@ +import React, { useEffect, useState, useRef } from 'react' +import { useSearchParams } from 'react-router-dom' +import { completeOAuthFlow } from '../utils/auth' +import { SupportedProvider } from '../types/models' + +interface OAuthCallbackProps { + provider: SupportedProvider +} + +const PkceCallback: React.FC = ({ provider }) => { + const [searchParams] = useSearchParams() + const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading') + const [error, setError] = useState(null) + const executedRef = useRef(false) + + useEffect(() => { + const handleCallback = async () => { + if (executedRef.current) { + console.log('DEBUG: Skipping duplicate OAuth callback execution') + return + } + executedRef.current = true + + try { + const code = searchParams.get('code') + const error = searchParams.get('error') + + if (error) { + throw new Error(`OAuth error: ${error}`) + } + + if (!code) { + throw new Error('Missing authorization code') + } + + // TODO: Add state parameter handling back if needed later + // const stateToUse = state || 'no-state' + await completeOAuthFlow(provider, code) + setStatus('success') + + console.log('DEBUG: OAuth flow completed successfully') + console.log('DEBUG: window.opener exists:', !!window.opener) + console.log('DEBUG: window.opener closed:', window.opener?.closed) + console.log('DEBUG: window.parent exists:', !!window.parent) + console.log('DEBUG: window.parent === window:', window.parent === window) + + // Try to send success message to parent if possible + const sendSuccessMessage = () => { + const message = { type: 'oauth_success', provider } + + // Try window.opener first + if (window.opener && !window.opener.closed) { + console.log('DEBUG: Sending message via window.opener') + window.opener.postMessage(message, '*') + return true + } + + // Also try window.parent as fallback + if (window.parent && window.parent !== window) { + console.log('DEBUG: Sending message via window.parent') + window.parent.postMessage(message, '*') + return true + } + + return false + } + + // Try to send success message + const messageSent = sendSuccessMessage() + console.log({ messageSent }) + + console.log('DEBUG: Closing popup in 100ms') + setTimeout(() => { + console.log('DEBUG: Attempting to close popup') + window.close() + }, 100) + } catch (err) { + console.error('OAuth callback error:', err) + setError(err instanceof Error ? err.message : 'Unknown error') + setStatus('error') + } + } + + handleCallback() + }, [searchParams, provider]) + + return ( +
+
+
+ {status === 'loading' && ( + <> +
+

Completing Authentication

+

Connecting to {provider}...

+ + )} + + {status === 'success' && ( + <> +
+ + + +
+

Authentication Successful!

+

Successfully connected to {provider}.

+
+ +
+ + )} + + {status === 'error' && ( + <> +
+ + + +
+

Authentication Failed

+

{error || 'An error occurred during authentication'}

+ + + )} +
+
+
+ ) +} + +export default PkceCallback diff --git a/examples/chat-ui/src/data/models.json b/examples/chat-ui/src/data/models.json index d40234c..40397d9 100644 --- a/examples/chat-ui/src/data/models.json +++ b/examples/chat-ui/src/data/models.json @@ -1,86 +1,86 @@ { "anthropic": { - "claude-3-haiku-20240307": { - "id": "claude-3-haiku-20240307", - "name": "Claude Haiku 3", + "claude-3-5-haiku-20241022": { + "id": "claude-3-5-haiku-20241022", + "name": "Claude Haiku 3.5", "attachment": true, "reasoning": false, "temperature": true, "tool_call": true, - "knowledge": "2023-08-31", - "release_date": "2024-03-13", - "last_updated": "2024-03-13", + "knowledge": "2024-07-31", + "release_date": "2024-10-22", + "last_updated": "2024-10-22", "modalities": { "input": ["text", "image"], "output": ["text"] }, "open_weights": false, "cost": { - "input": 0.25, - "output": 1.25, - "cache_read": 0.03, - "cache_write": 0.3 + "input": 0.8, + "output": 4, + "cache_read": 0.08, + "cache_write": 1 }, "limit": { "context": 200000, - "output": 4096 + "output": 8192 } }, - "claude-3-opus-20240229": { - "id": "claude-3-opus-20240229", - "name": "Claude Opus 3", + "claude-3-5-sonnet-20241022": { + "id": "claude-3-5-sonnet-20241022", + "name": "Claude Sonnet 3.5 v2", "attachment": true, "reasoning": false, "temperature": true, "tool_call": true, - "knowledge": "2023-08-31", - "release_date": "2024-02-29", - "last_updated": "2024-02-29", + "knowledge": "2024-04-30", + "release_date": "2024-10-22", + "last_updated": "2024-10-22", "modalities": { "input": ["text", "image"], "output": ["text"] }, "open_weights": false, "cost": { - "input": 15, - "output": 75, - "cache_read": 1.5, - "cache_write": 18.75 + "input": 3, + "output": 15, + "cache_read": 0.3, + "cache_write": 3.75 }, "limit": { "context": 200000, - "output": 4096 + "output": 8192 } }, - "claude-3-5-haiku-20241022": { - "id": "claude-3-5-haiku-20241022", - "name": "Claude Haiku 3.5", + "claude-sonnet-4-20250514": { + "id": "claude-sonnet-4-20250514", + "name": "Claude Sonnet 4", "attachment": true, - "reasoning": false, + "reasoning": true, "temperature": true, "tool_call": true, - "knowledge": "2024-07-31", - "release_date": "2024-10-22", - "last_updated": "2024-10-22", + "knowledge": "2025-03-31", + "release_date": "2025-05-22", + "last_updated": "2025-05-22", "modalities": { "input": ["text", "image"], "output": ["text"] }, "open_weights": false, "cost": { - "input": 0.8, - "output": 4, - "cache_read": 0.08, - "cache_write": 1 + "input": 3, + "output": 15, + "cache_read": 0.3, + "cache_write": 3.75 }, "limit": { "context": 200000, - "output": 8192 + "output": 64000 } }, - "claude-sonnet-4-20250514": { - "id": "claude-sonnet-4-20250514", - "name": "Claude Sonnet 4", + "claude-opus-4-20250514": { + "id": "claude-opus-4-20250514", + "name": "Claude Opus 4", "attachment": true, "reasoning": true, "temperature": true, @@ -94,36 +94,36 @@ }, "open_weights": false, "cost": { - "input": 3, - "output": 15, - "cache_read": 0.3, - "cache_write": 3.75 + "input": 15, + "output": 75, + "cache_read": 1.5, + "cache_write": 18.75 }, "limit": { "context": 200000, - "output": 64000 + "output": 32000 } }, - "claude-3-sonnet-20240229": { - "id": "claude-3-sonnet-20240229", - "name": "Claude Sonnet 3", + "claude-3-opus-20240229": { + "id": "claude-3-opus-20240229", + "name": "Claude Opus 3", "attachment": true, "reasoning": false, "temperature": true, "tool_call": true, "knowledge": "2023-08-31", - "release_date": "2024-03-04", - "last_updated": "2024-03-04", + "release_date": "2024-02-29", + "last_updated": "2024-02-29", "modalities": { "input": ["text", "image"], "output": ["text"] }, "open_weights": false, "cost": { - "input": 3, - "output": 15, - "cache_read": 0.3, - "cache_write": 0.3 + "input": 15, + "output": 75, + "cache_read": 1.5, + "cache_write": 18.75 }, "limit": { "context": 200000, @@ -156,42 +156,42 @@ "output": 8192 } }, - "claude-opus-4-20250514": { - "id": "claude-opus-4-20250514", - "name": "Claude Opus 4", + "claude-3-haiku-20240307": { + "id": "claude-3-haiku-20240307", + "name": "Claude Haiku 3", "attachment": true, - "reasoning": true, + "reasoning": false, "temperature": true, "tool_call": true, - "knowledge": "2025-03-31", - "release_date": "2025-05-22", - "last_updated": "2025-05-22", + "knowledge": "2023-08-31", + "release_date": "2024-03-13", + "last_updated": "2024-03-13", "modalities": { "input": ["text", "image"], "output": ["text"] }, "open_weights": false, "cost": { - "input": 15, - "output": 75, - "cache_read": 1.5, - "cache_write": 18.75 + "input": 0.25, + "output": 1.25, + "cache_read": 0.03, + "cache_write": 0.3 }, "limit": { "context": 200000, - "output": 32000 + "output": 4096 } }, - "claude-3-5-sonnet-20241022": { - "id": "claude-3-5-sonnet-20241022", - "name": "Claude Sonnet 3.5 v2", + "claude-3-7-sonnet-20250219": { + "id": "claude-3-7-sonnet-20250219", + "name": "Claude Sonnet 3.7", "attachment": true, - "reasoning": false, + "reasoning": true, "temperature": true, "tool_call": true, - "knowledge": "2024-04-30", - "release_date": "2024-10-22", - "last_updated": "2024-10-22", + "knowledge": "2024-10-31", + "release_date": "2025-02-19", + "last_updated": "2025-02-19", "modalities": { "input": ["text", "image"], "output": ["text"] @@ -205,19 +205,19 @@ }, "limit": { "context": 200000, - "output": 8192 + "output": 64000 } }, - "claude-3-7-sonnet-20250219": { - "id": "claude-3-7-sonnet-20250219", - "name": "Claude Sonnet 3.7", + "claude-3-sonnet-20240229": { + "id": "claude-3-sonnet-20240229", + "name": "Claude Sonnet 3", "attachment": true, - "reasoning": true, + "reasoning": false, "temperature": true, "tool_call": true, - "knowledge": "2024-10-31", - "release_date": "2025-02-19", - "last_updated": "2025-02-19", + "knowledge": "2023-08-31", + "release_date": "2024-03-04", + "last_updated": "2024-03-04", "modalities": { "input": ["text", "image"], "output": ["text"] @@ -227,757 +227,1795 @@ "input": 3, "output": 15, "cache_read": 0.3, - "cache_write": 3.75 + "cache_write": 0.3 }, "limit": { "context": 200000, - "output": 64000 + "output": 4096 } } }, "groq": { - "llama-3.3-70b-versatile": { - "id": "llama-3.3-70b-versatile", - "name": "Llama 3.3 70B Versatile", + "mistral-saba-24b": { + "id": "mistral-saba-24b", + "name": "Mistral Saba 24B", "attachment": false, "reasoning": false, "temperature": true, "tool_call": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "knowledge": "2024-08", + "release_date": "2025-02-06", + "last_updated": "2025-02-06", "modalities": { "input": ["text"], "output": ["text"] }, - "open_weights": true, + "open_weights": false, "cost": { - "input": 0.59, + "input": 0.79, "output": 0.79 }, "limit": { - "context": 131072, + "context": 32768, "output": 32768 } }, - "llama-3.1-8b-instant": { - "id": "llama-3.1-8b-instant", - "name": "Llama 3.1 8B Instant", + "deepseek-r1-distill-llama-70b": { + "id": "deepseek-r1-distill-llama-70b", + "name": "DeepSeek R1 Distill Llama 70B", "attachment": false, - "reasoning": false, + "reasoning": true, "temperature": true, "tool_call": true, - "knowledge": "2023-12", - "release_date": "2024-07-23", - "last_updated": "2024-07-23", + "knowledge": "2024-07", + "release_date": "2025-01-20", + "last_updated": "2025-01-20", "modalities": { "input": ["text"], "output": ["text"] }, "open_weights": true, "cost": { - "input": 0.05, - "output": 0.08 + "input": 0.75, + "output": 0.99 }, "limit": { "context": 131072, "output": 8192 } }, - "llama-guard-3-8b": { - "id": "llama-guard-3-8b", - "name": "Llama Guard 3 8B", + "llama-3.3-70b-versatile": { + "id": "llama-3.3-70b-versatile", + "name": "Llama 3.3 70B Versatile", "attachment": false, "reasoning": false, "temperature": true, - "tool_call": false, - "release_date": "2024-07-23", - "last_updated": "2024-07-23", + "tool_call": true, + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", "modalities": { "input": ["text"], "output": ["text"] }, "open_weights": true, "cost": { - "input": 0.2, - "output": 0.2 + "input": 0.59, + "output": 0.79 }, "limit": { - "context": 8192, - "output": 8192 + "context": 131072, + "output": 32768 } }, - "gemma2-9b-it": { - "id": "gemma2-9b-it", - "name": "Gemma 2 9B", + "moonshotai/kimi-k2-instruct": { + "id": "moonshotai/kimi-k2-instruct", + "name": "Kimi K2 Instruct", "attachment": false, "reasoning": false, "temperature": true, "tool_call": true, - "knowledge": "2024-06", - "release_date": "2024-06-27", - "last_updated": "2024-06-27", + "knowledge": "2024-10", + "release_date": "2025-07-14", + "last_updated": "2025-07-14", "modalities": { "input": ["text"], "output": ["text"] }, "open_weights": true, "cost": { - "input": 0.2, - "output": 0.2 + "input": 1, + "output": 3 }, "limit": { - "context": 8192, - "output": 8192 + "context": 131072, + "output": 16384 } }, - "llama3-8b-8192": { - "id": "llama3-8b-8192", - "name": "Llama 3 8B", + "meta-llama/llama-4-maverick-17b-128e-instruct": { + "id": "meta-llama/llama-4-maverick-17b-128e-instruct", + "name": "Llama 4 Maverick 17B", "attachment": false, "reasoning": false, "temperature": true, "tool_call": true, - "knowledge": "2023-03", - "release_date": "2024-04-18", - "last_updated": "2024-04-18", + "knowledge": "2024-08", + "release_date": "2025-04-05", + "last_updated": "2025-04-05", "modalities": { - "input": ["text"], + "input": ["text", "image"], "output": ["text"] }, "open_weights": true, "cost": { - "input": 0.05, - "output": 0.08 + "input": 0.2, + "output": 0.6 }, "limit": { - "context": 8192, + "context": 131072, "output": 8192 } }, - "llama3-70b-8192": { - "id": "llama3-70b-8192", - "name": "Llama 3 70B", + "meta-llama/llama-4-scout-17b-16e-instruct": { + "id": "meta-llama/llama-4-scout-17b-16e-instruct", + "name": "Llama 4 Scout 17B", "attachment": false, "reasoning": false, "temperature": true, "tool_call": true, - "knowledge": "2023-03", - "release_date": "2024-04-18", - "last_updated": "2024-04-18", + "knowledge": "2024-08", + "release_date": "2025-04-05", + "last_updated": "2025-04-05", "modalities": { - "input": ["text"], + "input": ["text", "image"], "output": ["text"] }, "open_weights": true, "cost": { - "input": 0.59, - "output": 0.79 + "input": 0.11, + "output": 0.34 }, "limit": { - "context": 8192, + "context": 131072, "output": 8192 } }, - "deepseek-r1-distill-llama-70b": { - "id": "deepseek-r1-distill-llama-70b", - "name": "DeepSeek R1 Distill Llama 70B", + "qwen/qwen3-32b": { + "id": "qwen/qwen3-32b", + "name": "Qwen3 32B", "attachment": false, "reasoning": true, "temperature": true, "tool_call": true, - "knowledge": "2024-07", - "release_date": "2025-01-20", - "last_updated": "2025-01-20", + "knowledge": "2024-11-08", + "release_date": "2024-12-23", + "last_updated": "2024-12-23", "modalities": { "input": ["text"], "output": ["text"] }, "open_weights": true, "cost": { - "input": 0.75, - "output": 0.99 + "input": 0.29, + "output": 0.59 }, "limit": { "context": 131072, - "output": 8192 + "output": 16384 } - }, - "qwen-qwq-32b": { - "id": "qwen-qwq-32b", - "name": "Qwen QwQ 32B", + } + }, + "openrouter": { + "openai/gpt-4.1-mini": { + "id": "openai/gpt-4.1-mini", + "name": "GPT-4.1 Mini", + "attachment": true, + "reasoning": false, + "temperature": true, + "tool_call": true, + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", + "modalities": { + "input": ["text", "image"], + "output": ["text"] + }, + "open_weights": false, + "cost": { + "input": 0.4, + "output": 1.6, + "cache_read": 0.1 + }, + "limit": { + "context": 1047576, + "output": 32768 + } + }, + "openai/o4-mini": { + "id": "openai/o4-mini", + "name": "o4 Mini", + "attachment": true, + "reasoning": true, + "temperature": true, + "tool_call": true, + "knowledge": "2024-06", + "release_date": "2025-04-16", + "last_updated": "2025-04-16", + "modalities": { + "input": ["text", "image"], + "output": ["text"] + }, + "open_weights": false, + "cost": { + "input": 1.1, + "output": 4.4, + "cache_read": 0.28 + }, + "limit": { + "context": 200000, + "output": 100000 + } + }, + "openai/gpt-4o-mini": { + "id": "openai/gpt-4o-mini", + "name": "GPT-4o-mini", + "attachment": true, + "reasoning": false, + "temperature": true, + "tool_call": true, + "knowledge": "2024-10", + "release_date": "2024-07-18", + "last_updated": "2024-07-18", + "modalities": { + "input": ["text", "image"], + "output": ["text"] + }, + "open_weights": false, + "cost": { + "input": 0.15, + "output": 0.6, + "cache_read": 0.08 + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "openai/gpt-4.1": { + "id": "openai/gpt-4.1", + "name": "GPT-4.1", + "attachment": true, + "reasoning": false, + "temperature": true, + "tool_call": true, + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", + "modalities": { + "input": ["text", "image"], + "output": ["text"] + }, + "open_weights": false, + "cost": { + "input": 2, + "output": 8, + "cache_read": 0.5 + }, + "limit": { + "context": 1047576, + "output": 32768 + } + }, + "sarvamai/sarvam-m:free": { + "id": "sarvamai/sarvam-m:free", + "name": "Sarvam-M (free)", + "attachment": false, + "reasoning": true, + "temperature": true, + "tool_call": true, + "knowledge": "2025-05", + "release_date": "2025-05-25", + "last_updated": "2025-05-25", + "modalities": { + "input": ["text"], + "output": ["text"] + }, + "open_weights": true, + "cost": { + "input": 0, + "output": 0 + }, + "limit": { + "context": 32768, + "output": 32768 + } + }, + "deepseek/deepseek-r1-distill-qwen-14b": { + "id": "deepseek/deepseek-r1-distill-qwen-14b", + "name": "DeepSeek R1 Distill Qwen 14B", + "attachment": false, + "reasoning": true, + "temperature": true, + "tool_call": false, + "knowledge": "2024-10", + "release_date": "2025-01-29", + "last_updated": "2025-01-29", + "modalities": { + "input": ["text"], + "output": ["text"] + }, + "open_weights": true, + "cost": { + "input": 0, + "output": 0 + }, + "limit": { + "context": 64000, + "output": 8192 + } + }, + "deepseek/deepseek-r1:free": { + "id": "deepseek/deepseek-r1:free", + "name": "R1 (free)", + "attachment": false, + "reasoning": true, + "temperature": true, + "tool_call": true, + "knowledge": "2025-01", + "release_date": "2025-01-20", + "last_updated": "2025-01-20", + "modalities": { + "input": ["text"], + "output": ["text"] + }, + "open_weights": true, + "cost": { + "input": 0, + "output": 0 + }, + "limit": { + "context": 163840, + "output": 163840 + } + }, + "deepseek/deepseek-r1-distill-llama-70b": { + "id": "deepseek/deepseek-r1-distill-llama-70b", + "name": "DeepSeek R1 Distill Llama 70B", + "attachment": false, + "reasoning": true, + "temperature": true, + "tool_call": false, + "knowledge": "2024-10", + "release_date": "2025-01-23", + "last_updated": "2025-01-23", + "modalities": { + "input": ["text"], + "output": ["text"] + }, + "open_weights": true, + "cost": { + "input": 0, + "output": 0 + }, + "limit": { + "context": 8192, + "output": 8192 + } + }, + "deepseek/deepseek-r1-0528:free": { + "id": "deepseek/deepseek-r1-0528:free", + "name": "R1 0528 (free)", + "attachment": false, + "reasoning": true, + "temperature": true, + "tool_call": true, + "knowledge": "2025-05", + "release_date": "2025-05-28", + "last_updated": "2025-05-28", + "modalities": { + "input": ["text"], + "output": ["text"] + }, + "open_weights": true, + "cost": { + "input": 0, + "output": 0 + }, + "limit": { + "context": 163840, + "output": 163840 + } + }, + "deepseek/deepseek-v3-base:free": { + "id": "deepseek/deepseek-v3-base:free", + "name": "DeepSeek V3 Base (free)", + "attachment": false, + "reasoning": false, + "temperature": true, + "tool_call": false, + "knowledge": "2025-03", + "release_date": "2025-03-29", + "last_updated": "2025-03-29", + "modalities": { + "input": ["text"], + "output": ["text"] + }, + "open_weights": true, + "cost": { + "input": 0, + "output": 0 + }, + "limit": { + "context": 163840, + "output": 163840 + } + }, + "deepseek/deepseek-chat-v3-0324": { + "id": "deepseek/deepseek-chat-v3-0324", + "name": "DeepSeek V3 0324", + "attachment": false, + "reasoning": false, + "temperature": true, + "tool_call": false, + "knowledge": "2024-10", + "release_date": "2025-03-24", + "last_updated": "2025-03-24", + "modalities": { + "input": ["text"], + "output": ["text"] + }, + "open_weights": true, + "cost": { + "input": 0, + "output": 0 + }, + "limit": { + "context": 16384, + "output": 8192 + } + }, + "deepseek/deepseek-r1-0528-qwen3-8b:free": { + "id": "deepseek/deepseek-r1-0528-qwen3-8b:free", + "name": "Deepseek R1 0528 Qwen3 8B (free)", + "attachment": false, + "reasoning": true, + "temperature": true, + "tool_call": true, + "knowledge": "2025-05", + "release_date": "2025-05-29", + "last_updated": "2025-05-29", + "modalities": { + "input": ["text"], + "output": ["text"] + }, + "open_weights": true, + "cost": { + "input": 0, + "output": 0 + }, + "limit": { + "context": 131072, + "output": 131072 + } + }, + "cognitivecomputations/dolphin3.0-mistral-24b": { + "id": "cognitivecomputations/dolphin3.0-mistral-24b", + "name": "Dolphin3.0 Mistral 24B", + "attachment": false, + "reasoning": false, + "temperature": true, + "tool_call": true, + "knowledge": "2024-10", + "release_date": "2025-02-13", + "last_updated": "2025-02-13", + "modalities": { + "input": ["text"], + "output": ["text"] + }, + "open_weights": true, + "cost": { + "input": 0, + "output": 0 + }, + "limit": { + "context": 32768, + "output": 8192 + } + }, + "cognitivecomputations/dolphin3.0-r1-mistral-24b": { + "id": "cognitivecomputations/dolphin3.0-r1-mistral-24b", + "name": "Dolphin3.0 R1 Mistral 24B", + "attachment": false, + "reasoning": true, + "temperature": true, + "tool_call": true, + "knowledge": "2024-10", + "release_date": "2025-02-13", + "last_updated": "2025-02-13", + "modalities": { + "input": ["text"], + "output": ["text"] + }, + "open_weights": true, + "cost": { + "input": 0, + "output": 0 + }, + "limit": { + "context": 32768, + "output": 8192 + } + }, + "anthropic/claude-4-sonnet-20250522": { + "id": "anthropic/claude-4-sonnet-20250522", + "name": "Claude Sonnet 4", + "attachment": true, + "reasoning": true, + "temperature": true, + "tool_call": true, + "knowledge": "2025-03-31", + "release_date": "2025-05-22", + "last_updated": "2025-05-22", + "modalities": { + "input": ["text", "image"], + "output": ["text"] + }, + "open_weights": false, + "cost": { + "input": 3, + "output": 15, + "cache_read": 0.3, + "cache_write": 3.75 + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "anthropic/claude-3.5-haiku": { + "id": "anthropic/claude-3.5-haiku", + "name": "Claude Haiku 3.5", + "attachment": true, + "reasoning": false, + "temperature": true, + "tool_call": true, + "knowledge": "2024-07-31", + "release_date": "2024-10-22", + "last_updated": "2024-10-22", + "modalities": { + "input": ["text", "image"], + "output": ["text"] + }, + "open_weights": false, + "cost": { + "input": 0.8, + "output": 4, + "cache_read": 0.08, + "cache_write": 1 + }, + "limit": { + "context": 200000, + "output": 8192 + } + }, + "anthropic/claude-3.7-sonnet": { + "id": "anthropic/claude-3.7-sonnet", + "name": "Claude Sonnet 3.7", + "attachment": true, + "reasoning": true, + "temperature": true, + "tool_call": true, + "knowledge": "2024-01", + "release_date": "2025-02-19", + "last_updated": "2025-02-19", + "modalities": { + "input": ["text", "image"], + "output": ["text"] + }, + "open_weights": false, + "cost": { + "input": 15, + "output": 75, + "cache_read": 1.5, + "cache_write": 18.75 + }, + "limit": { + "context": 200000, + "output": 128000 + } + }, + "anthropic/claude-opus-4": { + "id": "anthropic/claude-opus-4", + "name": "Claude Opus 4", + "attachment": true, + "reasoning": true, + "temperature": true, + "tool_call": true, + "knowledge": "2025-03-31", + "release_date": "2025-05-22", + "last_updated": "2025-05-22", + "modalities": { + "input": ["text", "image"], + "output": ["text"] + }, + "open_weights": false, + "cost": { + "input": 15, + "output": 75, + "cache_read": 1.5, + "cache_write": 18.75 + }, + "limit": { + "context": 200000, + "output": 32000 + } + }, + "featherless/qwerky-72b": { + "id": "featherless/qwerky-72b", + "name": "Qwerky 72B", + "attachment": false, + "reasoning": false, + "temperature": true, + "tool_call": false, + "knowledge": "2024-10", + "release_date": "2025-03-20", + "last_updated": "2025-03-20", + "modalities": { + "input": ["text"], + "output": ["text"] + }, + "open_weights": true, + "cost": { + "input": 0, + "output": 0 + }, + "limit": { + "context": 32768, + "output": 8192 + } + }, + "rekaai/reka-flash-3": { + "id": "rekaai/reka-flash-3", + "name": "Reka Flash 3", + "attachment": false, + "reasoning": true, + "temperature": true, + "tool_call": true, + "knowledge": "2024-10", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", + "modalities": { + "input": ["text"], + "output": ["text"] + }, + "open_weights": true, + "cost": { + "input": 0, + "output": 0 + }, + "limit": { + "context": 32768, + "output": 8192 + } + }, + "moonshotai/kimi-dev-72b:free": { + "id": "moonshotai/kimi-dev-72b:free", + "name": "Kimi Dev 72b (free)", + "attachment": false, + "reasoning": false, + "temperature": true, + "tool_call": true, + "knowledge": "2025-06", + "release_date": "2025-06-16", + "last_updated": "2025-06-16", + "modalities": { + "input": ["text"], + "output": ["text"] + }, + "open_weights": true, + "cost": { + "input": 0, + "output": 0 + }, + "limit": { + "context": 131072, + "output": 131072 + } + }, + "moonshotai/kimi-k2": { + "id": "moonshotai/kimi-k2", + "name": "Kimi K2", + "attachment": false, + "reasoning": false, + "temperature": true, + "tool_call": true, + "knowledge": "2024-10", + "release_date": "2025-07-11", + "last_updated": "2025-07-11", + "modalities": { + "input": ["text"], + "output": ["text"] + }, + "open_weights": false, + "cost": { + "input": 0.57, + "output": 2.3 + }, + "limit": { + "context": 131072, + "output": 32768 + } + }, + "x-ai/grok-3-mini": { + "id": "x-ai/grok-3-mini", + "name": "Grok 3 Mini", + "attachment": false, + "reasoning": true, + "temperature": true, + "tool_call": true, + "knowledge": "2024-11", + "release_date": "2025-02-17", + "last_updated": "2025-02-17", + "modalities": { + "input": ["text"], + "output": ["text"] + }, + "open_weights": false, + "cost": { + "input": 0.3, + "output": 0.5, + "cache_read": 0.075, + "cache_write": 0.5 + }, + "limit": { + "context": 131072, + "output": 8192 + } + }, + "x-ai/grok-3": { + "id": "x-ai/grok-3", + "name": "Grok 3", + "attachment": false, + "reasoning": false, + "temperature": true, + "tool_call": true, + "knowledge": "2024-11", + "release_date": "2025-02-17", + "last_updated": "2025-02-17", + "modalities": { + "input": ["text"], + "output": ["text"] + }, + "open_weights": false, + "cost": { + "input": 3, + "output": 15, + "cache_read": 0.75, + "cache_write": 15 + }, + "limit": { + "context": 131072, + "output": 8192 + } + }, + "x-ai/grok-3-beta": { + "id": "x-ai/grok-3-beta", + "name": "Grok 3 Beta", + "attachment": false, + "reasoning": false, + "temperature": true, + "tool_call": true, + "knowledge": "2024-11", + "release_date": "2025-02-17", + "last_updated": "2025-02-17", + "modalities": { + "input": ["text"], + "output": ["text"] + }, + "open_weights": false, + "cost": { + "input": 3, + "output": 15, + "cache_read": 0.75, + "cache_write": 15 + }, + "limit": { + "context": 131072, + "output": 8192 + } + }, + "x-ai/grok-4": { + "id": "x-ai/grok-4", + "name": "Grok 4", + "attachment": false, + "reasoning": true, + "temperature": true, + "tool_call": true, + "knowledge": "2025-07", + "release_date": "2025-07-09", + "last_updated": "2025-07-09", + "modalities": { + "input": ["text"], + "output": ["text"] + }, + "open_weights": false, + "cost": { + "input": 3, + "output": 15, + "cache_read": 0.75, + "cache_write": 15 + }, + "limit": { + "context": 256000, + "output": 64000 + } + }, + "x-ai/grok-3-mini-beta": { + "id": "x-ai/grok-3-mini-beta", + "name": "Grok 3 Mini Beta", + "attachment": false, + "reasoning": true, + "temperature": true, + "tool_call": true, + "knowledge": "2024-11", + "release_date": "2025-02-17", + "last_updated": "2025-02-17", + "modalities": { + "input": ["text"], + "output": ["text"] + }, + "open_weights": false, + "cost": { + "input": 0.3, + "output": 0.5, + "cache_read": 0.075, + "cache_write": 0.5 + }, + "limit": { + "context": 131072, + "output": 8192 + } + }, + "microsoft/mai-ds-r1:free": { + "id": "microsoft/mai-ds-r1:free", + "name": "MAI DS R1 (free)", + "attachment": false, + "reasoning": true, + "temperature": true, + "tool_call": true, + "knowledge": "2025-04", + "release_date": "2025-04-21", + "last_updated": "2025-04-21", + "modalities": { + "input": ["text"], + "output": ["text"] + }, + "open_weights": true, + "cost": { + "input": 0, + "output": 0 + }, + "limit": { + "context": 163840, + "output": 163840 + } + }, + "openrouter/cypher-alpha:free": { + "id": "openrouter/cypher-alpha:free", + "name": "Cypher Alpha (free)", + "attachment": false, + "reasoning": false, + "temperature": true, + "tool_call": true, + "knowledge": "2025-07", + "release_date": "2025-07-01", + "last_updated": "2025-07-01", + "modalities": { + "input": ["text"], + "output": ["text"] + }, + "open_weights": false, + "cost": { + "input": 0, + "output": 0 + }, + "limit": { + "context": 1000000, + "output": 1000000 + } + }, + "meta-llama/llama-3.3-70b-instruct:free": { + "id": "meta-llama/llama-3.3-70b-instruct:free", + "name": "Llama 3.3 70B Instruct (free)", + "attachment": false, + "reasoning": false, + "temperature": true, + "tool_call": true, + "knowledge": "2024-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", + "modalities": { + "input": ["text"], + "output": ["text"] + }, + "open_weights": true, + "cost": { + "input": 0, + "output": 0 + }, + "limit": { + "context": 65536, + "output": 65536 + } + }, + "meta-llama/llama-3.2-11b-vision-instruct": { + "id": "meta-llama/llama-3.2-11b-vision-instruct", + "name": "Llama 3.2 11B Vision Instruct", + "attachment": true, + "reasoning": false, + "temperature": true, + "tool_call": false, + "knowledge": "2023-12", + "release_date": "2024-09-25", + "last_updated": "2024-09-25", + "modalities": { + "input": ["text", "image"], + "output": ["text"] + }, + "open_weights": true, + "cost": { + "input": 0, + "output": 0 + }, + "limit": { + "context": 131072, + "output": 8192 + } + }, + "meta-llama/llama-4-scout:free": { + "id": "meta-llama/llama-4-scout:free", + "name": "Llama 4 Scout (free)", + "attachment": true, + "reasoning": false, + "temperature": true, + "tool_call": true, + "knowledge": "2024-08", + "release_date": "2025-04-05", + "last_updated": "2025-04-05", + "modalities": { + "input": ["text", "image"], + "output": ["text"] + }, + "open_weights": true, + "cost": { + "input": 0, + "output": 0 + }, + "limit": { + "context": 64000, + "output": 64000 + } + }, + "google/gemma-3n-e4b-it:free": { + "id": "google/gemma-3n-e4b-it:free", + "name": "Gemma 3n 4B (free)", + "attachment": true, + "reasoning": false, + "temperature": true, + "tool_call": true, + "knowledge": "2025-05", + "release_date": "2025-05-20", + "last_updated": "2025-05-20", + "modalities": { + "input": ["text", "image", "audio"], + "output": ["text"] + }, + "open_weights": true, + "cost": { + "input": 0, + "output": 0 + }, + "limit": { + "context": 8192, + "output": 8192 + } + }, + "google/gemini-2.5-flash-preview-05-20": { + "id": "google/gemini-2.5-flash-preview-05-20", + "name": "Gemini 2.5 Flash Preview 05-20", + "attachment": true, + "reasoning": true, + "temperature": true, + "tool_call": true, + "knowledge": "2025-01", + "release_date": "2025-05-20", + "last_updated": "2025-05-20", + "modalities": { + "input": ["text", "image", "audio", "video", "pdf"], + "output": ["text"] + }, + "open_weights": false, + "cost": { + "input": 0.15, + "output": 0.6, + "cache_read": 0.0375 + }, + "limit": { + "context": 1048576, + "output": 65536 + } + }, + "google/gemma-3-27b-it": { + "id": "google/gemma-3-27b-it", + "name": "Gemma 3 27B IT", + "attachment": true, + "reasoning": false, + "temperature": true, + "tool_call": true, + "knowledge": "2024-10", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", + "modalities": { + "input": ["text", "image"], + "output": ["text"] + }, + "open_weights": true, + "cost": { + "input": 0, + "output": 0 + }, + "limit": { + "context": 96000, + "output": 8192 + } + }, + "google/gemini-2.5-pro-preview-06-05": { + "id": "google/gemini-2.5-pro-preview-06-05", + "name": "Gemini 2.5 Pro Preview 06-05", + "attachment": true, + "reasoning": true, + "temperature": true, + "tool_call": true, + "knowledge": "2025-01", + "release_date": "2025-06-05", + "last_updated": "2025-06-05", + "modalities": { + "input": ["text", "image", "audio", "video", "pdf"], + "output": ["text"] + }, + "open_weights": false, + "cost": { + "input": 1.25, + "output": 10, + "cache_read": 0.31 + }, + "limit": { + "context": 1048576, + "output": 65536 + } + }, + "google/gemini-2.5-flash-preview-04-17": { + "id": "google/gemini-2.5-flash-preview-04-17", + "name": "Gemini 2.5 Flash Preview 04-17", + "attachment": true, + "reasoning": true, + "temperature": true, + "tool_call": true, + "knowledge": "2025-01", + "release_date": "2025-04-17", + "last_updated": "2025-04-17", + "modalities": { + "input": ["text", "image", "audio", "video", "pdf"], + "output": ["text"] + }, + "open_weights": false, + "cost": { + "input": 0.15, + "output": 0.6, + "cache_read": 0.0375 + }, + "limit": { + "context": 1048576, + "output": 65536 + } + }, + "google/gemini-2.0-flash-exp:free": { + "id": "google/gemini-2.0-flash-exp:free", + "name": "Gemini 2.0 Flash Experimental (free)", + "attachment": true, + "reasoning": false, + "temperature": true, + "tool_call": true, + "knowledge": "2024-12", + "release_date": "2024-12-11", + "last_updated": "2024-12-11", + "modalities": { + "input": ["text", "image"], + "output": ["text"] + }, + "open_weights": false, + "cost": { + "input": 0, + "output": 0 + }, + "limit": { + "context": 1048576, + "output": 1048576 + } + }, + "google/gemma-2-9b-it:free": { + "id": "google/gemma-2-9b-it:free", + "name": "Gemma 2 9B (free)", "attachment": false, - "reasoning": true, + "reasoning": false, "temperature": true, "tool_call": true, - "knowledge": "2024-09", - "release_date": "2024-11-27", - "last_updated": "2024-11-27", + "knowledge": "2024-06", + "release_date": "2024-06-28", + "last_updated": "2024-06-28", "modalities": { "input": ["text"], "output": ["text"] }, "open_weights": true, "cost": { - "input": 0.29, - "output": 0.39 + "input": 0, + "output": 0 }, "limit": { - "context": 131072, - "output": 16384 + "context": 8192, + "output": 8192 } }, - "mistral-saba-24b": { - "id": "mistral-saba-24b", - "name": "Mistral Saba 24B", - "attachment": false, + "google/gemma-3-12b-it": { + "id": "google/gemma-3-12b-it", + "name": "Gemma 3 12B IT", + "attachment": true, "reasoning": false, "temperature": true, "tool_call": true, - "knowledge": "2024-08", - "release_date": "2025-02-06", - "last_updated": "2025-02-06", + "knowledge": "2024-10", + "release_date": "2025-03-13", + "last_updated": "2025-03-13", "modalities": { - "input": ["text"], + "input": ["text", "image"], + "output": ["text"] + }, + "open_weights": true, + "cost": { + "input": 0, + "output": 0 + }, + "limit": { + "context": 96000, + "output": 8192 + } + }, + "google/gemini-2.5-pro-preview-05-06": { + "id": "google/gemini-2.5-pro-preview-05-06", + "name": "Gemini 2.5 Pro Preview 05-06", + "attachment": true, + "reasoning": true, + "temperature": true, + "tool_call": true, + "knowledge": "2025-01", + "release_date": "2025-05-06", + "last_updated": "2025-05-06", + "modalities": { + "input": ["text", "image", "audio", "video", "pdf"], "output": ["text"] }, "open_weights": false, "cost": { - "input": 0.79, - "output": 0.79 + "input": 1.25, + "output": 10, + "cache_read": 0.31 }, "limit": { - "context": 32768, - "output": 32768 + "context": 1048576, + "output": 65536 } }, - "qwen/qwen3-32b": { - "id": "qwen/qwen3-32b", - "name": "Qwen3 32B", + "google/gemini-2.0-flash-001": { + "id": "google/gemini-2.0-flash-001", + "name": "Gemini 2.0 Flash", + "attachment": true, + "reasoning": false, + "temperature": true, + "tool_call": true, + "knowledge": "2024-06", + "release_date": "2024-12-11", + "last_updated": "2024-12-11", + "modalities": { + "input": ["text", "image", "audio", "video", "pdf"], + "output": ["text"] + }, + "open_weights": false, + "cost": { + "input": 0.1, + "output": 0.4, + "cache_read": 0.025 + }, + "limit": { + "context": 1048576, + "output": 8192 + } + }, + "google/gemma-3n-e4b-it": { + "id": "google/gemma-3n-e4b-it", + "name": "Gemma 3n E4B IT", + "attachment": true, + "reasoning": false, + "temperature": true, + "tool_call": false, + "knowledge": "2024-10", + "release_date": "2025-05-20", + "last_updated": "2025-05-20", + "modalities": { + "input": ["text", "image", "audio"], + "output": ["text"] + }, + "open_weights": true, + "cost": { + "input": 0, + "output": 0 + }, + "limit": { + "context": 8192, + "output": 8192 + } + }, + "google/gemini-2.5-pro": { + "id": "google/gemini-2.5-pro", + "name": "Gemini 2.5 Pro", + "attachment": true, + "reasoning": true, + "temperature": true, + "tool_call": true, + "knowledge": "2025-01", + "release_date": "2025-03-20", + "last_updated": "2025-06-05", + "modalities": { + "input": ["text", "image", "audio", "video", "pdf"], + "output": ["text"] + }, + "open_weights": false, + "cost": { + "input": 1.25, + "output": 10, + "cache_read": 0.31 + }, + "limit": { + "context": 1048576, + "output": 65536 + } + }, + "thudm/glm-z1-32b:free": { + "id": "thudm/glm-z1-32b:free", + "name": "GLM Z1 32B (free)", "attachment": false, "reasoning": true, "temperature": true, "tool_call": true, - "knowledge": "2024-11-08", - "release_date": "2024-12-23", - "last_updated": "2024-12-23", + "knowledge": "2025-04", + "release_date": "2025-04-17", + "last_updated": "2025-04-17", "modalities": { "input": ["text"], "output": ["text"] }, "open_weights": true, "cost": { - "input": 0.29, - "output": 0.59 + "input": 0, + "output": 0 }, "limit": { - "context": 131072, - "output": 16384 + "context": 32768, + "output": 32768 } }, - "meta-llama/llama-4-maverick-17b-128e-instruct": { - "id": "meta-llama/llama-4-maverick-17b-128e-instruct", - "name": "Llama 4 Maverick 17B", - "attachment": false, + "mistralai/mistral-small-3.1-24b-instruct": { + "id": "mistralai/mistral-small-3.1-24b-instruct", + "name": "Mistral Small 3.1 24B Instruct", + "attachment": true, "reasoning": false, "temperature": true, "tool_call": true, - "knowledge": "2024-08", - "release_date": "2025-04-05", - "last_updated": "2025-04-05", + "knowledge": "2024-10", + "release_date": "2025-03-17", + "last_updated": "2025-03-17", "modalities": { "input": ["text", "image"], "output": ["text"] }, "open_weights": true, "cost": { - "input": 0.2, - "output": 0.6 + "input": 0, + "output": 0 }, "limit": { - "context": 131072, + "context": 128000, "output": 8192 } }, - "meta-llama/llama-4-scout-17b-16e-instruct": { - "id": "meta-llama/llama-4-scout-17b-16e-instruct", - "name": "Llama 4 Scout 17B", + "mistralai/devstral-small-2507": { + "id": "mistralai/devstral-small-2507", + "name": "Devstral Small 1.1", "attachment": false, "reasoning": false, "temperature": true, "tool_call": true, - "knowledge": "2024-08", - "release_date": "2025-04-05", - "last_updated": "2025-04-05", + "knowledge": "2025-05", + "release_date": "2025-07-10", + "last_updated": "2025-07-10", "modalities": { - "input": ["text", "image"], + "input": ["text"], "output": ["text"] }, "open_weights": true, "cost": { - "input": 0.11, - "output": 0.34 + "input": 0.1, + "output": 0.3 }, "limit": { "context": 131072, + "output": 131072 + } + }, + "mistralai/mistral-small-3.2-24b-instruct": { + "id": "mistralai/mistral-small-3.2-24b-instruct", + "name": "Mistral Small 3.2 24B Instruct", + "attachment": true, + "reasoning": false, + "temperature": true, + "tool_call": true, + "knowledge": "2024-10", + "release_date": "2025-06-20", + "last_updated": "2025-06-20", + "modalities": { + "input": ["text", "image"], + "output": ["text"] + }, + "open_weights": true, + "cost": { + "input": 0, + "output": 0 + }, + "limit": { + "context": 96000, "output": 8192 } }, - "meta-llama/llama-guard-4-12b": { - "id": "meta-llama/llama-guard-4-12b", - "name": "Llama Guard 4 12B", + "mistralai/mistral-7b-instruct:free": { + "id": "mistralai/mistral-7b-instruct:free", + "name": "Mistral 7B Instruct (free)", "attachment": false, "reasoning": false, "temperature": true, - "tool_call": false, - "release_date": "2025-04-05", - "last_updated": "2025-04-05", + "tool_call": true, + "knowledge": "2024-05", + "release_date": "2024-05-27", + "last_updated": "2024-05-27", "modalities": { - "input": ["text", "image"], + "input": ["text"], "output": ["text"] }, "open_weights": true, "cost": { - "input": 0.2, - "output": 0.2 + "input": 0, + "output": 0 }, "limit": { - "context": 131072, - "output": 128 + "context": 32768, + "output": 32768 } - } - }, - "openrouter": { - "openai/gpt-4.1-mini": { - "id": "openai/gpt-4.1-mini", - "name": "GPT-4.1 Mini", - "attachment": true, + }, + "mistralai/devstral-medium-2507": { + "id": "mistralai/devstral-medium-2507", + "name": "Devstral Medium", + "attachment": false, "reasoning": false, "temperature": true, "tool_call": true, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "knowledge": "2025-05", + "release_date": "2025-07-10", + "last_updated": "2025-07-10", "modalities": { - "input": ["text", "image"], + "input": ["text"], "output": ["text"] }, - "open_weights": false, + "open_weights": true, "cost": { "input": 0.4, - "output": 1.6, - "cache_read": 0.1 + "output": 2 }, "limit": { - "context": 1047576, - "output": 32768 + "context": 131072, + "output": 131072 } }, - "openai/gpt-4.1": { - "id": "openai/gpt-4.1", - "name": "GPT-4.1", + "mistralai/mistral-small-3.2-24b-instruct:free": { + "id": "mistralai/mistral-small-3.2-24b-instruct:free", + "name": "Mistral Small 3.2 24B (free)", "attachment": true, "reasoning": false, "temperature": true, "tool_call": true, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "knowledge": "2025-06", + "release_date": "2025-06-20", + "last_updated": "2025-06-20", "modalities": { "input": ["text", "image"], "output": ["text"] }, - "open_weights": false, + "open_weights": true, "cost": { - "input": 2, - "output": 8, - "cache_read": 0.5 + "input": 0, + "output": 0 }, "limit": { - "context": 1047576, - "output": 32768 + "context": 96000, + "output": 96000 } }, - "openai/gpt-4o-mini": { - "id": "openai/gpt-4o-mini", - "name": "GPT-4o-mini", - "attachment": true, + "mistralai/devstral-small-2505:free": { + "id": "mistralai/devstral-small-2505:free", + "name": "Devstral Small 2505 (free)", + "attachment": false, "reasoning": false, "temperature": true, "tool_call": true, - "knowledge": "2024-10", - "release_date": "2024-07-18", - "last_updated": "2024-07-18", + "knowledge": "2025-05", + "release_date": "2025-05-21", + "last_updated": "2025-05-21", "modalities": { - "input": ["text", "image"], + "input": ["text"], "output": ["text"] }, - "open_weights": false, + "open_weights": true, "cost": { - "input": 0.15, - "output": 0.6, - "cache_read": 0.08 + "input": 0, + "output": 0 }, "limit": { - "context": 128000, - "output": 16384 + "context": 32768, + "output": 32768 } }, - "openai/o4-mini": { - "id": "openai/o4-mini", - "name": "o4 Mini", - "attachment": true, - "reasoning": true, + "mistralai/mistral-nemo:free": { + "id": "mistralai/mistral-nemo:free", + "name": "Mistral Nemo (free)", + "attachment": false, + "reasoning": false, "temperature": true, "tool_call": true, - "knowledge": "2024-06", - "release_date": "2025-04-16", - "last_updated": "2025-04-16", + "knowledge": "2024-07", + "release_date": "2024-07-19", + "last_updated": "2024-07-19", "modalities": { - "input": ["text", "image"], + "input": ["text"], "output": ["text"] }, - "open_weights": false, + "open_weights": true, "cost": { - "input": 1.1, - "output": 4.4, - "cache_read": 0.28 + "input": 0, + "output": 0 }, "limit": { - "context": 200000, - "output": 100000 + "context": 131072, + "output": 131072 } }, - "anthropic/claude-4-sonnet-20250522": { - "id": "anthropic/claude-4-sonnet-20250522", - "name": "Claude Sonnet 4", - "attachment": true, - "reasoning": true, + "mistralai/devstral-small-2505": { + "id": "mistralai/devstral-small-2505", + "name": "Devstral Small", + "attachment": false, + "reasoning": false, "temperature": true, "tool_call": true, - "knowledge": "2025-03-31", - "release_date": "2025-05-22", - "last_updated": "2025-05-22", + "knowledge": "2025-05", + "release_date": "2025-05-07", + "last_updated": "2025-05-07", "modalities": { - "input": ["text", "image"], + "input": ["text"], "output": ["text"] }, - "open_weights": false, + "open_weights": true, "cost": { - "input": 3, - "output": 15, - "cache_read": 0.3, - "cache_write": 3.75 + "input": 0.06, + "output": 0.12 }, "limit": { - "context": 200000, - "output": 64000 + "context": 128000, + "output": 128000 } }, - "anthropic/claude-3.7-sonnet": { - "id": "anthropic/claude-3.7-sonnet", - "name": "Claude Sonnet 3.7", - "attachment": true, + "nousresearch/deephermes-3-llama-3-8b-preview": { + "id": "nousresearch/deephermes-3-llama-3-8b-preview", + "name": "DeepHermes 3 Llama 3 8B Preview", + "attachment": false, "reasoning": true, "temperature": true, "tool_call": true, - "knowledge": "2024-01", - "release_date": "2025-02-19", - "last_updated": "2025-02-19", + "knowledge": "2024-04", + "release_date": "2025-02-28", + "last_updated": "2025-02-28", "modalities": { - "input": ["text", "image"], + "input": ["text"], "output": ["text"] }, - "open_weights": false, + "open_weights": true, "cost": { - "input": 15, - "output": 75, - "cache_read": 1.5, - "cache_write": 18.75 + "input": 0, + "output": 0 }, "limit": { - "context": 200000, - "output": 128000 + "context": 131072, + "output": 8192 } }, - "anthropic/claude-opus-4": { - "id": "anthropic/claude-opus-4", - "name": "Claude Opus 4", - "attachment": true, + "tngtech/deepseek-r1t2-chimera:free": { + "id": "tngtech/deepseek-r1t2-chimera:free", + "name": "DeepSeek R1T2 Chimera (free)", + "attachment": false, "reasoning": true, "temperature": true, "tool_call": true, - "knowledge": "2025-03-31", - "release_date": "2025-05-22", - "last_updated": "2025-05-22", + "knowledge": "2025-07", + "release_date": "2025-07-08", + "last_updated": "2025-07-08", "modalities": { - "input": ["text", "image"], + "input": ["text"], "output": ["text"] }, - "open_weights": false, + "open_weights": true, "cost": { - "input": 15, - "output": 75, - "cache_read": 1.5, - "cache_write": 18.75 + "input": 0, + "output": 0 }, "limit": { - "context": 200000, - "output": 32000 + "context": 163840, + "output": 163840 } }, - "x-ai/grok-3-mini": { - "id": "x-ai/grok-3-mini", - "name": "Grok 3 Mini", + "qwen/qwen3-30b-a3b:free": { + "id": "qwen/qwen3-30b-a3b:free", + "name": "Qwen3 30B A3B (free)", "attachment": false, "reasoning": true, "temperature": true, "tool_call": true, - "knowledge": "2024-11", - "release_date": "2025-02-17", - "last_updated": "2025-02-17", + "knowledge": "2025-04", + "release_date": "2025-04-28", + "last_updated": "2025-04-28", "modalities": { "input": ["text"], "output": ["text"] }, - "open_weights": false, + "open_weights": true, "cost": { - "input": 0.3, - "output": 0.5, - "cache_read": 0.075, - "cache_write": 0.5 + "input": 0, + "output": 0 }, "limit": { - "context": 131072, - "output": 8192 + "context": 40960, + "output": 40960 } }, - "x-ai/grok-3-mini-beta": { - "id": "x-ai/grok-3-mini-beta", - "name": "Grok 3 Mini Beta", + "qwen/qwen3-235b-a22b:free": { + "id": "qwen/qwen3-235b-a22b:free", + "name": "Qwen3 235B A22B (free)", "attachment": false, "reasoning": true, "temperature": true, "tool_call": true, - "knowledge": "2024-11", - "release_date": "2025-02-17", - "last_updated": "2025-02-17", + "knowledge": "2025-04", + "release_date": "2025-04-28", + "last_updated": "2025-04-28", "modalities": { "input": ["text"], "output": ["text"] }, - "open_weights": false, + "open_weights": true, "cost": { - "input": 0.3, - "output": 0.5, - "cache_read": 0.075, - "cache_write": 0.5 + "input": 0, + "output": 0 }, "limit": { "context": 131072, - "output": 8192 + "output": 131072 } }, - "x-ai/grok-3": { - "id": "x-ai/grok-3", - "name": "Grok 3", + "qwen/qwq-32b:free": { + "id": "qwen/qwq-32b:free", + "name": "QwQ 32B (free)", "attachment": false, - "reasoning": false, + "reasoning": true, "temperature": true, "tool_call": true, - "knowledge": "2024-11", - "release_date": "2025-02-17", - "last_updated": "2025-02-17", + "knowledge": "2025-03", + "release_date": "2025-03-05", + "last_updated": "2025-03-05", "modalities": { "input": ["text"], "output": ["text"] }, - "open_weights": false, + "open_weights": true, "cost": { - "input": 3, - "output": 15, - "cache_read": 0.75, - "cache_write": 15 + "input": 0, + "output": 0 }, "limit": { - "context": 131072, - "output": 8192 + "context": 32768, + "output": 32768 } }, - "x-ai/grok-3-beta": { - "id": "x-ai/grok-3-beta", - "name": "Grok 3 Beta", + "qwen/qwen-2.5-coder-32b-instruct": { + "id": "qwen/qwen-2.5-coder-32b-instruct", + "name": "Qwen2.5 Coder 32B Instruct", "attachment": false, "reasoning": false, "temperature": true, - "tool_call": true, - "knowledge": "2024-11", - "release_date": "2025-02-17", - "last_updated": "2025-02-17", + "tool_call": false, + "knowledge": "2024-10", + "release_date": "2024-11-11", + "last_updated": "2024-11-11", "modalities": { "input": ["text"], "output": ["text"] }, - "open_weights": false, + "open_weights": true, "cost": { - "input": 3, - "output": 15, - "cache_read": 0.75, - "cache_write": 15 + "input": 0, + "output": 0 }, "limit": { - "context": 131072, + "context": 32768, "output": 8192 } }, - "google/gemini-2.5-pro-preview-05-06": { - "id": "google/gemini-2.5-pro-preview-05-06", - "name": "Gemini 2.5 Pro Preview 05-06", - "attachment": true, + "qwen/qwen3-8b:free": { + "id": "qwen/qwen3-8b:free", + "name": "Qwen3 8B (free)", + "attachment": false, "reasoning": true, "temperature": true, "tool_call": true, - "knowledge": "2025-01", - "release_date": "2025-05-06", - "last_updated": "2025-05-06", + "knowledge": "2025-04", + "release_date": "2025-04-28", + "last_updated": "2025-04-28", "modalities": { - "input": ["text", "image", "audio", "video", "pdf"], + "input": ["text"], "output": ["text"] }, - "open_weights": false, + "open_weights": true, "cost": { - "input": 1.25, - "output": 10, - "cache_read": 0.31 + "input": 0, + "output": 0 }, "limit": { - "context": 1048576, - "output": 65536 + "context": 40960, + "output": 40960 } }, - "google/gemini-2.5-pro-preview-06-05": { - "id": "google/gemini-2.5-pro-preview-06-05", - "name": "Gemini 2.5 Pro Preview 06-05", - "attachment": true, + "qwen/qwen3-14b:free": { + "id": "qwen/qwen3-14b:free", + "name": "Qwen3 14B (free)", + "attachment": false, "reasoning": true, "temperature": true, "tool_call": true, - "knowledge": "2025-01", - "release_date": "2025-06-05", - "last_updated": "2025-06-05", + "knowledge": "2025-04", + "release_date": "2025-04-28", + "last_updated": "2025-04-28", "modalities": { - "input": ["text", "image", "audio", "video", "pdf"], + "input": ["text"], "output": ["text"] }, - "open_weights": false, + "open_weights": true, "cost": { - "input": 1.25, - "output": 10, - "cache_read": 0.31 + "input": 0, + "output": 0 }, "limit": { - "context": 1048576, - "output": 65536 + "context": 40960, + "output": 40960 } }, - "google/gemini-2.5-pro": { - "id": "google/gemini-2.5-pro", - "name": "Gemini 2.5 Pro", + "qwen/qwen2.5-vl-72b-instruct": { + "id": "qwen/qwen2.5-vl-72b-instruct", + "name": "Qwen2.5 VL 72B Instruct", "attachment": true, - "reasoning": true, + "reasoning": false, "temperature": true, - "tool_call": true, - "knowledge": "2025-01", - "release_date": "2025-03-20", - "last_updated": "2025-06-05", + "tool_call": false, + "knowledge": "2024-10", + "release_date": "2025-02-01", + "last_updated": "2025-02-01", "modalities": { - "input": ["text", "image", "audio", "video", "pdf"], + "input": ["text", "image"], "output": ["text"] }, - "open_weights": false, + "open_weights": true, "cost": { - "input": 1.25, - "output": 10, - "cache_read": 0.31 + "input": 0, + "output": 0 }, "limit": { - "context": 1048576, - "output": 65536 + "context": 32768, + "output": 8192 } }, - "google/gemini-2.5-flash-preview-04-17": { - "id": "google/gemini-2.5-flash-preview-04-17", - "name": "Gemini 2.5 Flash Preview 04-17", + "qwen/qwen2.5-vl-72b-instruct:free": { + "id": "qwen/qwen2.5-vl-72b-instruct:free", + "name": "Qwen2.5 VL 72B Instruct (free)", "attachment": true, - "reasoning": true, + "reasoning": false, "temperature": true, "tool_call": true, - "knowledge": "2025-01", - "release_date": "2025-04-17", - "last_updated": "2025-04-17", + "knowledge": "2025-02", + "release_date": "2025-02-01", + "last_updated": "2025-02-01", "modalities": { - "input": ["text", "image", "audio", "video", "pdf"], + "input": ["text", "image"], "output": ["text"] }, - "open_weights": false, + "open_weights": true, "cost": { - "input": 0.15, - "output": 0.6, - "cache_read": 0.0375 + "input": 0, + "output": 0 }, "limit": { - "context": 1048576, - "output": 65536 + "context": 32768, + "output": 32768 } }, - "google/gemini-2.5-flash-preview-05-20": { - "id": "google/gemini-2.5-flash-preview-05-20", - "name": "Gemini 2.5 Flash Preview 05-20", + "qwen/qwen2.5-vl-32b-instruct:free": { + "id": "qwen/qwen2.5-vl-32b-instruct:free", + "name": "Qwen2.5 VL 32B Instruct (free)", "attachment": true, - "reasoning": true, + "reasoning": false, "temperature": true, "tool_call": true, - "knowledge": "2025-01", - "release_date": "2025-05-20", - "last_updated": "2025-05-20", + "knowledge": "2025-03", + "release_date": "2025-03-24", + "last_updated": "2025-03-24", "modalities": { - "input": ["text", "image", "audio", "video", "pdf"], + "input": ["text", "image", "video"], "output": ["text"] }, - "open_weights": false, + "open_weights": true, "cost": { - "input": 0.15, - "output": 0.6, - "cache_read": 0.0375 + "input": 0, + "output": 0 }, "limit": { - "context": 1048576, - "output": 65536 + "context": 8192, + "output": 8192 } }, - "google/gemini-2.0-flash-001": { - "id": "google/gemini-2.0-flash-001", - "name": "Gemini 2.0 Flash", - "attachment": true, - "reasoning": false, + "qwen/qwen3-32b:free": { + "id": "qwen/qwen3-32b:free", + "name": "Qwen3 32B (free)", + "attachment": false, + "reasoning": true, "temperature": true, "tool_call": true, - "knowledge": "2024-06", - "release_date": "2024-12-11", - "last_updated": "2024-12-11", + "knowledge": "2025-04", + "release_date": "2025-04-28", + "last_updated": "2025-04-28", "modalities": { - "input": ["text", "image", "audio", "video", "pdf"], + "input": ["text"], "output": ["text"] }, - "open_weights": false, + "open_weights": true, "cost": { - "input": 0.1, - "output": 0.4, - "cache_read": 0.025 + "input": 0, + "output": 0 }, "limit": { - "context": 1048576, - "output": 8192 + "context": 40960, + "output": 40960 } } } diff --git a/examples/chat-ui/src/hooks/useStreamResponse.ts b/examples/chat-ui/src/hooks/useStreamResponse.ts index dbfdec0..f8e9b6b 100644 --- a/examples/chat-ui/src/hooks/useStreamResponse.ts +++ b/examples/chat-ui/src/hooks/useStreamResponse.ts @@ -90,7 +90,7 @@ export const useStreamResponse = ({ switch (model.provider.id) { case 'groq': { const apiKey = authHeaders.Authorization?.replace('Bearer ', '') - const groqProvider = createGroq({ apiKey }) + const groqProvider = createGroq({ apiKey, baseURL: model.provider.baseUrl }) baseModel = groqProvider(model.modelId) break } diff --git a/examples/chat-ui/src/types/models.ts b/examples/chat-ui/src/types/models.ts index 73c4d16..2e373a6 100644 --- a/examples/chat-ui/src/types/models.ts +++ b/examples/chat-ui/src/types/models.ts @@ -67,13 +67,22 @@ export const providers: Record = { baseUrl: 'https://api.groq.com/openai/v1', logo: '🚀', documentationUrl: 'https://console.groq.com/docs', - authType: 'apiKey', apiKeyHeader: 'Authorization', - // oauth: { - // authorizeUrl: 'http://localhost:3000/keys/request', - // tokenUrl: 'https://openrouter.ai/api/v1/auth/keys' - // }, + authType: 'apiKey', }, + // groq: { + // id: 'groq', + // name: 'Groq', + // baseUrl: 'http://localhost:8000/api/openai/v1', + // logo: '🚀', + // documentationUrl: 'https://console.groq.com/docs', + // apiKeyHeader: 'Authorization', + // authType: 'oauth', + // oauth: { + // authorizeUrl: 'http://localhost:3000/keys/request', + // tokenUrl: 'http://localhost:3000/keys/request/exchange', + // }, + // }, anthropic: { id: 'anthropic', name: 'Anthropic', diff --git a/examples/chat-ui/src/utils/auth.ts b/examples/chat-ui/src/utils/auth.ts index d476cfe..b1f46ff 100644 --- a/examples/chat-ui/src/utils/auth.ts +++ b/examples/chat-ui/src/utils/auth.ts @@ -11,7 +11,8 @@ export interface OAuthToken { // Types for PKCE flow interface PKCEState { code_verifier: string - state: string + // TODO: Add state support back if needed later + // state: string } // Generate a random code verifier for PKCE @@ -35,15 +36,15 @@ async function generateCodeChallenge(verifier: string): Promise { .replace(/=/g, '') } -// Generate random state parameter -function generateState(): string { - const array = new Uint8Array(16) - crypto.getRandomValues(array) - return btoa(String.fromCharCode(...array)) - .replace(/\+/g, '-') - .replace(/\//g, '_') - .replace(/=/g, '') -} +// TODO: Add state generation back if needed later +// function generateState(): string { +// const array = new Uint8Array(16) +// crypto.getRandomValues(array) +// return btoa(String.fromCharCode(...array)) +// .replace(/\+/g, '-') +// .replace(/\//g, '_') +// .replace(/=/g, '') +// } // API Key functions (existing functionality) export function hasApiKey(providerId: SupportedProvider): boolean { @@ -111,11 +112,17 @@ export async function beginOAuthFlow(providerId: SupportedProvider): Promise { - console.log('DEBUG: Starting OAuth completion for', providerId, 'with code:', code?.substring(0, 10) + '...', 'state:', state) +export async function completeOAuthFlow(providerId: SupportedProvider, code: string): Promise { + console.log('DEBUG: Starting OAuth completion for', providerId, 'with code:', code?.substring(0, 10) + '...') + console.log('DEBUG: Full code for debugging:', code) + console.log('DEBUG: Provider config:', providers[providerId]) const provider = providers[providerId] if (!provider.oauth) { throw new Error(`Provider ${providerId} does not support OAuth`) } - // Retrieve PKCE state - let pkceState: PKCEState + // Retrieve PKCE state (single key per provider) + const storageKey = `pkce_${providerId}` + const pkceStateJson = sessionStorage.getItem(storageKey) - if (state === 'no-state' && providerId === 'openrouter') { - // OpenRouter doesn't use state, find the most recent PKCE state for this provider - const allKeys = Object.keys(sessionStorage) - const pkceKeys = allKeys.filter((key) => key.startsWith(`pkce_${providerId}_`)) + console.log('DEBUG: Looking for PKCE key:', storageKey) - console.log('DEBUG: Found PKCE keys:', pkceKeys) - - if (pkceKeys.length === 0) { - throw new Error('PKCE state not found. Please try again.') - } + if (!pkceStateJson) { + throw new Error('PKCE state not found. Please try again.') + } - // Use the most recent one (sort by timestamp) - const sortedKeys = pkceKeys.sort((a, b) => { - const aTime = parseInt(a.split('_').pop() || '0') - const bTime = parseInt(b.split('_').pop() || '0') - return bTime - aTime // Most recent first - }) + const pkceState: PKCEState = JSON.parse(pkceStateJson) - const pkceStateJson = sessionStorage.getItem(sortedKeys[0])! - pkceState = JSON.parse(pkceStateJson) + console.log('DEBUG: Using PKCE state:', { key: storageKey, state: pkceState }) + console.log('DEBUG: Code verifier length:', pkceState.code_verifier.length) + console.log('DEBUG: Code verifier sample:', pkceState.code_verifier.substring(0, 20) + '...') - console.log('DEBUG: Using PKCE state:', { key: sortedKeys[0], state: pkceState }) + // Test: regenerate challenge from verifier to verify it matches server expectation + const recomputedChallenge = await generateCodeChallenge(pkceState.code_verifier) + console.log('DEBUG: Recomputed challenge from verifier:', recomputedChallenge) + console.log('DEBUG: Server reported challenge was: dW2iEvNljlkhcRcryo3Z0GITcJM1liKcHlB5v8CDEu8') - // Clean up the state - sessionStorage.removeItem(sortedKeys[0]) - } else { - const pkceStateJson = sessionStorage.getItem(`pkce_${providerId}_${state}`) - if (!pkceStateJson) { - throw new Error('PKCE state not found. Please try again.') - } - pkceState = JSON.parse(pkceStateJson) - console.log('DEBUG: Using PKCE state for state', state, ':', pkceState) - } + // Clean up the state + sessionStorage.removeItem(storageKey) // Exchange code for token let tokenResponse: Response @@ -218,18 +224,23 @@ export async function completeOAuthFlow(providerId: SupportedProvider, code: str duration: `${endTime - startTime}ms`, }) } else { - // Standard OAuth2 flow for other providers + // Standard OAuth2 flow for other providers (Groq) const requestBody = new URLSearchParams({ grant_type: 'authorization_code', code, redirect_uri: getRedirectUri(providerId), code_verifier: pkceState.code_verifier, }) - console.log('DEBUG: Standard OAuth token request:', { + + console.log('DEBUG: Groq token request:', { url: provider.oauth.tokenUrl, body: Object.fromEntries(requestBody.entries()), + codeVerifierLength: pkceState.code_verifier.length, + codeVerifierSample: pkceState.code_verifier.substring(0, 20) + '...', + fullCodeVerifier: pkceState.code_verifier, // For debugging }) + const startTime = performance.now() tokenResponse = await fetch(provider.oauth.tokenUrl, { method: 'POST', headers: { @@ -237,11 +248,13 @@ export async function completeOAuthFlow(providerId: SupportedProvider, code: str }, body: requestBody, }) + const endTime = performance.now() - console.log('DEBUG: Standard OAuth token response:', { + console.log('DEBUG: Groq token response:', { status: tokenResponse.status, statusText: tokenResponse.statusText, headers: Object.fromEntries(tokenResponse.headers.entries()), + duration: `${endTime - startTime}ms`, }) } @@ -263,6 +276,12 @@ export async function completeOAuthFlow(providerId: SupportedProvider, code: str access_token: tokenData.key, token_type: 'Bearer', } + } else if (providerId === 'groq') { + // Groq returns { api_key: "..." } + token = { + access_token: tokenData.api_key, + token_type: 'Bearer', + } } else { // Standard OAuth2 response token = { @@ -273,12 +292,10 @@ export async function completeOAuthFlow(providerId: SupportedProvider, code: str } } + console.log('DEBUG: Saving token for', providerId, ':', token) setOAuthToken(providerId, token) - // Clean up PKCE state (already cleaned up above for OpenRouter) - if (state !== 'no-state') { - sessionStorage.removeItem(`pkce_${providerId}_${state}`) - } + // PKCE state already cleaned up above } // Get authentication headers for API calls @@ -318,3 +335,30 @@ function getRedirectUri(providerId: SupportedProvider): string { const baseUrl = window.location.origin return `${baseUrl}/oauth/${providerId}/callback` } + +// Test function to verify PKCE implementation with known values +export async function testPKCEImplementation(): Promise { + console.log('=== TESTING PKCE IMPLEMENTATION ===') + + // Test with a known code verifier (from RFC 7636 example) + const testVerifier = 'dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk' + const expectedChallenge = 'E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM' + + const computedChallenge = await generateCodeChallenge(testVerifier) + + console.log('DEBUG: Test verifier:', testVerifier) + console.log('DEBUG: Expected challenge:', expectedChallenge) + console.log('DEBUG: Computed challenge:', computedChallenge) + console.log('DEBUG: Challenges match:', computedChallenge === expectedChallenge) + + // Test with current implementation + const currentVerifier = generateCodeVerifier() + const currentChallenge = await generateCodeChallenge(currentVerifier) + + console.log('DEBUG: Current verifier:', currentVerifier) + console.log('DEBUG: Current challenge:', currentChallenge) + console.log('DEBUG: Verifier length:', currentVerifier.length) + console.log('DEBUG: Challenge length:', currentChallenge.length) + + console.log('=== END PKCE TEST ===') +}