Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion lib/actions/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ import { db } from '@/lib/db'
import { users, chats, chatParticipants } from '@/lib/db/schema'
import { eq, or, and, sql } from 'drizzle-orm'
import { getCurrentUserIdOnServer } from '@/lib/auth/get-current-user'
import { getModel, normalizeMessageContent } from '../utils'
import { normalizeMessageContent } from '../utils'
import { getModel } from '../utils/server'
import { executiveSummaryAgent } from '../agents/report/executive-summary'
import { strategicSynthesisAgent } from '../agents/report/strategic-synthesis'

Expand Down
2 changes: 1 addition & 1 deletion lib/actions/suggest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { createStreamableUI, createStreamableValue } from 'ai/rsc'
import { CoreMessage, LanguageModel, streamObject } from 'ai'
import { PartialRelated, relatedSchema } from '@/lib/schema/related'
import { getModel } from '../utils'
import { getModel } from '../utils/server'
import { MapData } from '@/components/map/map-data-context'

export async function getSuggestions(
Expand Down
2 changes: 1 addition & 1 deletion lib/actions/system-prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { getCurrentUserIdOnServer } from '@/lib/auth/get-current-user';
import { z } from 'zod';
import { createJob, updateJob, getJobByIdAndUserId } from './system-prompt-db';
import { getFirecrawlClient } from '@/lib/agents/tools/firecrawl';
import { getModel } from '@/lib/utils';
import { getModel } from '@/lib/utils/server';
import { generateText } from 'ai';

const domainSchema = z.string().min(1).refine((val) => {
Expand Down
2 changes: 1 addition & 1 deletion lib/agents/inquire.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Copilot } from '@/components/copilot';
import { createStreamableUI, createStreamableValue } from 'ai/rsc';
import { CoreMessage, LanguageModel, streamObject } from 'ai';
import { PartialInquiry, inquirySchema } from '@/lib/schema/inquiry';
import { getModel } from '../utils';
import { getModel } from '../utils/server';

// Define a plain object type for the inquiry prop
interface InquiryProp {
Expand Down
2 changes: 1 addition & 1 deletion lib/agents/query-suggestor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { CoreMessage, LanguageModel, streamObject } from 'ai'
import { PartialRelated, relatedSchema } from '@/lib/schema/related'
import { Section } from '@/components/section'
import SearchRelated from '@/components/search-related'
import { getModel } from '../utils'
import { getModel } from '../utils/server'

interface CacheEntry {
data: PartialRelated;
Expand Down
2 changes: 1 addition & 1 deletion lib/agents/report/executive-summary.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { generateText } from 'ai'
import { getModel } from '@/lib/utils'
import { getModel } from '@/lib/utils/server'

export async function executiveSummaryAgent(crossSessionContext: string, activeMessages: any[]) {
try {
Expand Down
2 changes: 1 addition & 1 deletion lib/agents/report/strategic-synthesis.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { generateText } from 'ai'
import { getModel } from '@/lib/utils'
import { getModel } from '@/lib/utils/server'

export async function strategicSynthesisAgent(sensorFusionFindings: any[], strategicContent: any[]) {
try {
Expand Down
2 changes: 1 addition & 1 deletion lib/agents/researcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
import { Section } from '@/components/section'
import { BotMessage } from '@/components/message'
import { getTools } from './tools'
import { getModel } from '../utils'
import { getModel } from '../utils/server'
import { MapProvider } from '@/lib/store/settings'
import { DrawnFeature } from './resolution-search'

Expand Down
2 changes: 1 addition & 1 deletion lib/agents/resolution-search.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CoreMessage, streamObject } from 'ai'
import { getModel } from '@/lib/utils'
import { getModel } from '@/lib/utils/server'
import { tavily } from '@tavily/core'
import { resolutionSearchSchema } from '@/lib/schema/resolution-search'

Expand Down
2 changes: 1 addition & 1 deletion lib/agents/task-manager.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { CoreMessage, generateObject, LanguageModel } from 'ai'
import { nextActionSchema } from '../schema/next-action'
import { getModel } from '../utils'
import { getModel } from '../utils/server'

// Decide whether inquiry is required for the user input
export async function taskManager(messages: CoreMessage[]) {
Expand Down
2 changes: 1 addition & 1 deletion lib/agents/writer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createStreamableUI, createStreamableValue } from 'ai/rsc'
import { CoreMessage, LanguageModel, streamText as nonexperimental_streamText } from 'ai'
import { Section } from '@/components/section'
import { BotMessage } from '@/components/message'
import { getModel } from '../utils'
import { getModel } from '../utils/server'

export async function writer(
dynamicSystemPrompt: string, // New parameter
Expand Down
110 changes: 0 additions & 110 deletions lib/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
import { getSelectedModel } from '@/lib/actions/users'
import { createOpenAI } from '@ai-sdk/openai'
import { createGoogleGenerativeAI } from '@ai-sdk/google'
import { createAmazonBedrock } from '@ai-sdk/amazon-bedrock'
import { createXai } from '@ai-sdk/xai';
import { v4 as uuidv4 } from 'uuid';

export function cn(...inputs: ClassValue[]) {
Expand All @@ -21,111 +16,6 @@ export function generateUUID(): string {
*/
export { generateUUID as nanoid };

export async function getModel(requireVision: boolean = false) {
const selectedModel = await getSelectedModel();

const xaiApiKey = process.env.XAI_API_KEY;
const gemini3ProApiKey = process.env.GEMINI_3_PRO_API_KEY;
const awsAccessKeyId = process.env.AWS_ACCESS_KEY_ID;
const awsSecretAccessKey = process.env.AWS_SECRET_ACCESS_KEY;
const awsRegion = process.env.AWS_REGION;
const bedrockModelId = process.env.BEDROCK_MODEL_ID || 'anthropic.claude-3-5-sonnet-20241022-v2:0';
const openaiApiKey = process.env.OPENAI_API_KEY;

if (selectedModel) {
switch (selectedModel) {
case 'Grok 4.2':
if (xaiApiKey) {
const xai = createXai({
apiKey: xaiApiKey,
baseURL: 'https://api.x.ai/v1',
});
try {
const modelId = requireVision ? 'grok-vision-beta' : 'grok-2-1212';
return xai(modelId);
} catch (error) {
console.error('Selected model "Grok 4.2" is configured but failed to initialize.', error);
throw new Error('Failed to initialize selected model.');
}
} else {
console.error('User selected "Grok 4.2" but XAI_API_KEY is not set.');
throw new Error('Selected model is not configured.');
}
case 'Gemini 3':
case 'Gemini 3.1 Pro':
if (gemini3ProApiKey) {
const google = createGoogleGenerativeAI({
apiKey: gemini3ProApiKey,
});
try {
return google('gemini-1.5-pro-latest');
} catch (error) {
console.error('Selected model "Gemini 3.1 Pro" is configured but failed to initialize.', error);
throw new Error('Failed to initialize selected model.');
}
} else {
console.error('User selected "Gemini 3.1 Pro" but GEMINI_3_PRO_API_KEY is not set.');
throw new Error('Selected model is not configured.');
}
case 'GPT-5.1':
if (openaiApiKey) {
const openai = createOpenAI({
apiKey: openaiApiKey,
});
return openai('gpt-4o');
} else {
console.error('User selected "GPT-5.1" but OPENAI_API_KEY is not set.');
throw new Error('Selected model is not configured.');
}
}
}

// Default behavior: Grok -> Gemini -> Bedrock -> OpenAI
if (xaiApiKey) {
const xai = createXai({
apiKey: xaiApiKey,
baseURL: 'https://api.x.ai/v1',
});
try {
const modelId = requireVision ? 'grok-vision-beta' : 'grok-latest';
return xai(modelId);
} catch (error) {
console.warn('xAI API unavailable, falling back to next provider:');
}
}

if (gemini3ProApiKey) {
const google = createGoogleGenerativeAI({
apiKey: gemini3ProApiKey,
});
try {
return google('gemini-1.5-pro-latest');
} catch (error) {
console.warn('Gemini 3.1 Pro API unavailable, falling back to next provider:', error);
}
}

if (awsAccessKeyId && awsSecretAccessKey) {
const bedrock = createAmazonBedrock({
bedrockOptions: {
region: awsRegion,
credentials: {
accessKeyId: awsAccessKeyId,
secretAccessKey: awsSecretAccessKey,
},
},
});
const model = bedrock(bedrockModelId, {
additionalModelRequestFields: { top_k: 350 },
});
return model;
}

const openai = createOpenAI({
apiKey: openaiApiKey,
});
return openai('gpt-4o');
}

/**
* Normalizes and sanitizes message content to plain text.
Expand Down
111 changes: 111 additions & 0 deletions lib/utils/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { getSelectedModel } from '@/lib/actions/users'
import { createOpenAI } from '@ai-sdk/openai'
import { createGoogleGenerativeAI } from '@ai-sdk/google'
import { createAmazonBedrock } from '@ai-sdk/amazon-bedrock'
import { createXai } from '@ai-sdk/xai'

export async function getModel(requireVision: boolean = false) {
const selectedModel = await getSelectedModel();

const xaiApiKey = process.env.XAI_API_KEY;
const gemini3ProApiKey = process.env.GEMINI_3_PRO_API_KEY;
const awsAccessKeyId = process.env.AWS_ACCESS_KEY_ID;
const awsSecretAccessKey = process.env.AWS_SECRET_ACCESS_KEY;
const awsRegion = process.env.AWS_REGION;
const bedrockModelId = process.env.BEDROCK_MODEL_ID || 'anthropic.claude-3-5-sonnet-20241022-v2:0';
const openaiApiKey = process.env.OPENAI_API_KEY;

if (selectedModel) {
switch (selectedModel) {
case 'Grok 4.2':
if (xaiApiKey) {
const xai = createXai({
apiKey: xaiApiKey,
baseURL: 'https://api.x.ai/v1',
});
try {
const modelId = requireVision ? 'grok-vision-beta' : 'grok-2-1212';
return xai(modelId);
} catch (error) {
console.error('Selected model "Grok 4.2" is configured but failed to initialize.', error);
throw new Error('Failed to initialize selected model.');
}
} else {
console.error('User selected "Grok 4.2" but XAI_API_KEY is not set.');
throw new Error('Selected model is not configured.');
}
case 'Gemini 3':
case 'Gemini 3.1 Pro':
if (gemini3ProApiKey) {
const google = createGoogleGenerativeAI({
apiKey: gemini3ProApiKey,
});
try {
return google('gemini-1.5-pro-latest');
} catch (error) {
console.error('Selected model "Gemini 3.1 Pro" is configured but failed to initialize.', error);
throw new Error('Failed to initialize selected model.');
}
} else {
console.error('User selected "Gemini 3.1 Pro" but GEMINI_3_PRO_API_KEY is not set.');
throw new Error('Selected model is not configured.');
}
case 'GPT-5.1':
if (openaiApiKey) {
const openai = createOpenAI({
apiKey: openaiApiKey,
});
return openai('gpt-4o');
Comment on lines +20 to +58

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Honor the model selected by the user.

These branches silently substitute older models: Grok 4.2 returns grok-2-1212, both Gemini 3 choices return Gemini 1.5, and GPT-5.1 returns GPT-4o. Align the persisted option names with the actual model IDs or reject unsupported selections instead of returning a different model.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/utils/server.ts` around lines 20 - 58, Update the model-selection
branches in the server model factory so each persisted option maps to its actual
supported model ID: replace the legacy IDs for “Grok 4.2,” “Gemini 3,” “Gemini
3.1 Pro,” and “GPT-5.1” with matching IDs, or explicitly reject any option
without a supported ID. Do not silently return older models from the Grok,
Gemini, or OpenAI initialization paths.

} else {
console.error('User selected "GPT-5.1" but OPENAI_API_KEY is not set.');
throw new Error('Selected model is not configured.');
}
}
}

// Default behavior: Grok -> Gemini -> Bedrock -> OpenAI
if (xaiApiKey) {
const xai = createXai({
apiKey: xaiApiKey,
baseURL: 'https://api.x.ai/v1',
});
try {
const modelId = requireVision ? 'grok-vision-beta' : 'grok-latest';
return xai(modelId);
} catch (error) {
console.warn('xAI API unavailable, falling back to next provider:');
}
}

if (gemini3ProApiKey) {
const google = createGoogleGenerativeAI({
apiKey: gemini3ProApiKey,
});
try {
return google('gemini-1.5-pro-latest');
} catch (error) {
console.warn('Gemini 3.1 Pro API unavailable, falling back to next provider:', error);
}
}

if (awsAccessKeyId && awsSecretAccessKey) {
const bedrock = createAmazonBedrock({
bedrockOptions: {
region: awsRegion,
credentials: {
accessKeyId: awsAccessKeyId,
secretAccessKey: awsSecretAccessKey,
},
},
});
const model = bedrock(bedrockModelId, {
additionalModelRequestFields: { top_k: 350 },
});
return model;
}

const openai = createOpenAI({
apiKey: openaiApiKey,
});
return openai('gpt-4o');
Comment on lines +66 to +110

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file list =="
git ls-files lib/utils/server.ts
echo

echo "== file size =="
wc -l lib/utils/server.ts
echo

echo "== outline =="
ast-grep outline lib/utils/server.ts --view expanded
echo

echo "== relevant lines =="
sed -n '1,220p' lib/utils/server.ts | cat -n
echo

echo "== search for consumers of the returned model =="
rg -n "create[A-Za-z]+\\(|generateText\\(|streamObject\\(|return .*openai\\(|return .*google\\(|return .*xai\\(" lib -g '!**/dist/**' -g '!**/build/**'

Repository: QueueLab/QCX

Length of output: 6969


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== getModel call sites =="
rg -n "\bgetModel\(" lib -g '!**/dist/**' -g '!**/build/**'
echo

echo "== AI SDK provider/generation docs lookup =="
python3 - <<'PY'
import urllib.request, urllib.parse, json, re
queries = [
    "ai sdk provider factory returns model object generateText streamObject network request happens later",
    "site:ai-sdk.dev createOpenAI model object generateText streamObject",
]
for q in queries:
    print(f"\nQUERY: {q}")
PY

Repository: QueueLab/QCX

Length of output: 1264


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== ai-sdk dependencies =="
rg -n '"`@ai-sdk/`|ai"' package.json pnpm-lock.yaml package-lock.json yarn.lock -g '!**/node_modules/**'
echo

echo "== provider factories in repo =="
rg -n "create(OpenAI|GoogleGenerativeAI|AmazonBedrock|Xai)\(" lib package.json -g '!**/node_modules/**'

Repository: QueueLab/QCX

Length of output: 684


🌐 Web query:

ai-sdk provider factory createOpenAI createGoogleGenerativeAI createXai returns language model object generateText streamObject network request happens later

💡 Result:

When you use factory functions like createOpenAI, createGoogleGenerativeAI, or createXai in the Vercel AI SDK, they do not initiate any network requests at the time of creation [1][2]. These functions simply return a provider instance—a configuration object containing the settings (such as API keys, base URLs, and headers) and the logic required to instantiate specific language models later [1][3][2]. The network request only occurs later, when you actually use the returned language model object in a function such as generateText or streamText [4][5][6]. At that point, the SDK uses the configuration defined in your provider instance to construct and execute the API call to the respective AI service [1][3][7].

Citations:


Move fallback selection to the generation path. createXai(...), google(...), bedrock(...), and openai(...) only return model handles; provider/auth failures surface when generateText or streamObject runs. As written, the first configured provider is returned immediately, so later fallbacks never execute.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/utils/server.ts` around lines 66 - 110, Move provider fallback handling
out of the model-selection function and into the generation path that invokes
generateText or streamObject. Update the flow around the xAI, Gemini, Bedrock,
and OpenAI provider handles so provider/auth errors are caught during generation
and the next configured provider is attempted, while preserving the existing
provider order and model configuration.

}
4 changes: 3 additions & 1 deletion mapbox_mcp/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
'use client';

import { useState, useCallback, useRef, useEffect } from 'react';
import { generateText } from 'ai';
import { getModel } from '@/lib/utils';
import { getModel } from '@/lib/utils/server';
Comment on lines +1 to +5

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift

Do not import the server model utility into a Client Component.

'use client' makes this entire dependency graph client-side, but getModel reads server environment credentials and imports getSelectedModel. This recreates the mixed client/server bundle failure the PR is intended to fix. Move generateText and model selection behind a server action or route handler, then call that boundary from this hook.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mapbox_mcp/hooks.ts` around lines 1 - 5, Remove the server-only getModel
import from the client hook and keep hooks.ts client-safe. Move generateText and
model selection into a server action or route handler, then update the hook’s
generation flow to call that server boundary while preserving its current
behavior.


// Define Tool type locally if needed
type Tool = {
Expand Down