blob: 8bbffc8dad9e2a97bf63ac79fa299399a0fe4d47 [file]
import type { ModelInfo, ProviderType } from './llm-connections.js';
export type HostedWebSearchAdapter =
| 'openai-responses'
| 'anthropic-messages'
| 'google-grounding'
| 'zai-web-search'
| 'mistral-agents'
| 'groq-compound'
| 'openrouter-web-plugin';
export interface HostedWebSearchCapability {
readonly adapter: HostedWebSearchAdapter;
/** Whether Maka currently has an executor for this provider wire. */
readonly implemented: boolean;
}
/**
* Resolves provider-hosted search for one exact model.
*
* Stored model metadata wins when it explicitly declares webSearch. Unknown
* models then use narrow provider rules backed by the provider's public API
* contract. Unknown never means supported: sending an unsupported built-in
* tool can be silently ignored by several OpenAI-compatible providers.
*
* The connection provider/wire is authoritative when a service supports both
* Responses and Anthropic Messages. Search never switches wire independently
* from the primary model request.
*/
export function resolveHostedWebSearchCapability(
providerType: ProviderType,
models: readonly ModelInfo[] | undefined,
modelId: string,
): HostedWebSearchCapability | null {
const id = modelId.trim();
if (!id) return null;
const stored = models?.find((model) => model.id === id);
if (stored?.capabilities?.webSearch === false) return null;
const adapter = providerHostedWebSearchAdapter(providerType);
if (!adapter) return null;
if (
stored?.apiProtocol !== undefined &&
((adapter.adapter === 'openai-responses' && stored.apiProtocol !== 'openai-responses') ||
(adapter.adapter === 'anthropic-messages' && stored.apiProtocol !== 'anthropic-messages'))
) {
return null;
}
if (stored?.capabilities?.webSearch === true) {
return adapter;
}
return providerDefaultHostedWebSearchCapability(providerType, id, adapter);
}
function providerHostedWebSearchAdapter(
providerType: ProviderType,
): HostedWebSearchCapability | null {
switch (providerType) {
case 'deepseek':
case 'openai':
case 'openai-responses-compatible':
case 'xai':
case 'xai-oauth':
return { adapter: 'openai-responses', implemented: true };
case 'alibaba':
return { adapter: 'openai-responses', implemented: false };
case 'anthropic':
case 'claude-subscription':
case 'MiniMax':
case 'MiniMax-cn':
case 'minimax-coding-plan':
case 'anthropic-compatible':
return { adapter: 'anthropic-messages', implemented: true };
case 'google':
case 'gemini-cli':
return { adapter: 'google-grounding', implemented: false };
case 'zai':
case 'zai-coding-plan':
return { adapter: 'zai-web-search', implemented: false };
case 'mistral':
return { adapter: 'mistral-agents', implemented: false };
case 'groq':
return { adapter: 'groq-compound', implemented: false };
case 'openrouter':
return { adapter: 'openrouter-web-plugin', implemented: false };
default:
return null;
}
}
function providerDefaultHostedWebSearchCapability(
providerType: ProviderType,
modelId: string,
capability: HostedWebSearchCapability,
): HostedWebSearchCapability | null {
switch (providerType) {
case 'deepseek':
return modelId === 'deepseek-v4-flash' ? capability : null;
case 'openai':
return /^gpt-5(?:[.-]|$)/i.test(modelId) ? capability : null;
case 'xai':
case 'xai-oauth':
return modelId === 'grok-4.5' ? capability : null;
case 'alibaba':
return /^qwen3\.5-(?:plus|flash)(?:[.-]|$)/i.test(modelId) ? capability : null;
case 'anthropic':
case 'claude-subscription':
return /^claude-(?:[\d.]+-)*(?:opus|sonnet|haiku|fable)\b/i.test(modelId) ? capability : null;
case 'MiniMax':
case 'MiniMax-cn':
case 'minimax-coding-plan':
return /^MiniMax-M(?:2\.7|3)(?:[.-]|$)/i.test(modelId) ? capability : null;
case 'anthropic-compatible':
return modelId === 'deepseek-v4-flash' ? capability : null;
case 'openai-responses-compatible':
return null;
case 'google':
case 'gemini-cli':
return /^gemini-(?:2\.0|2\.5|3|3\.1|3\.5)(?:[.-]|$)/i.test(modelId) ? capability : null;
case 'zai':
case 'zai-coding-plan':
return /^glm-(?:4\.5|4\.6|4\.7|5)(?:[.-]|$)/i.test(modelId) ? capability : null;
case 'mistral':
return capability;
case 'groq':
return /^groq\/compound(?:-mini)?$/i.test(modelId) ? capability : null;
case 'openrouter':
return capability;
default:
return null;
}
}