feat: add automatic web search support for OpenAI Native provider

- Add openAiNativeWebSearchEnabled configuration option to provider settings
- Implement web search tool integration in Responses API requests
- Handle web search events and store results for citation formatting
- Add citation formatting with clickable links in markdown format
- Display search status updates during web search operations
- Append sources list with numbered citations at end of response

This enables automatic web search for OpenAI Native provider when the
openAiNativeWebSearchEnabled option is set to true. The model will
automatically search the web when helpful and include inline citations
with a Sources list containing clickable links.
This commit is contained in:
Roo Code 2025-09-23 18:28:14 +00:00
parent 44cbee5e75
commit a93b162c0a
2 changed files with 66 additions and 2 deletions

View file

@ -297,6 +297,8 @@ const openAiNativeSchema = apiModelIdProviderModelSchema.extend({
// OpenAI Responses API service tier for openai-native provider only.
// UI should only expose this when the selected model supports flex/priority.
openAiNativeServiceTier: serviceTierSchema.optional(),
// Enable automatic web search for OpenAI Native provider
openAiNativeWebSearchEnabled: z.boolean().optional(),
})
const mistralSchema = apiModelIdProviderModelSchema.extend({

View file

@ -39,6 +39,8 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
private responseIdResolver: ((value: string | undefined) => void) | undefined
// Resolved service tier from Responses API (actual tier used by OpenAI)
private lastServiceTier: ServiceTier | undefined
// Store web search results for citation formatting
private lastWebSearchResults: any[] | undefined
// Event types handled by the shared event processor to avoid duplication
private readonly coreHandledEventTypes = new Set<string>([
@ -245,6 +247,7 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
store?: boolean
instructions?: string
service_tier?: ServiceTier
tools?: Array<{ type: string }>
}
// Validate requested tier against model support; if not supported, omit.
@ -283,6 +286,10 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
(requestedTier === "default" || allowedTierNames.has(requestedTier)) && {
service_tier: requestedTier,
}),
// Enable web search if configured
...(this.options.openAiNativeWebSearchEnabled && {
tools: [{ type: "web_search" }],
}),
}
// Include text.verbosity only when the model explicitly supports it
@ -887,11 +894,26 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
}
// Handle web search events
else if (parsed.type === "response.web_search_call.searching") {
// Web search in progress
// Web search in progress - could yield status update
if (parsed.query) {
yield {
type: "text",
text: `\n[Searching web for: "${parsed.query}"]\n`,
}
}
} else if (parsed.type === "response.web_search_call.in_progress") {
// Processing web search results
} else if (parsed.type === "response.web_search_call.completed") {
// Web search completed
// Web search completed - results will be included in the response
if (parsed.results && Array.isArray(parsed.results)) {
// Store search results for citation formatting
this.lastWebSearchResults = parsed.results
}
} else if (parsed.type === "response.web_search_call.results") {
// Alternative event for web search results
if (parsed.results && Array.isArray(parsed.results)) {
this.lastWebSearchResults = parsed.results
}
}
// Handle code interpreter events
else if (parsed.type === "response.code_interpreter_call_code.delta") {
@ -1016,6 +1038,13 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
}
}
// After content is complete, append citations if we have web search results
if (this.lastWebSearchResults && this.lastWebSearchResults.length > 0) {
yield* this.formatWebSearchCitations(this.lastWebSearchResults)
// Clear results after using them
this.lastWebSearchResults = undefined
}
// Usage for done/completed is already handled by processGpt5Event in SDK path.
// For SSE path, usage often arrives separately; avoid double-emitting here.
}
@ -1304,6 +1333,11 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
}
}
// Enable web search if configured
if (this.options.openAiNativeWebSearchEnabled) {
requestBody.tools = [{ type: "web_search" }]
}
// Only include temperature if the model supports it
if (model.info.supportsTemperature !== false) {
requestBody.temperature =
@ -1352,4 +1386,32 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
throw error
}
}
/**
* Formats web search results as citations with clickable links
*/
private async *formatWebSearchCitations(results: any[]): ApiStream {
// Format citations with inline links
const citations: string[] = []
const sources: string[] = []
for (let i = 0; i < results.length; i++) {
const result = results[i]
if (result.url && result.title) {
const citationNum = i + 1
// Create inline citation reference
citations.push(`[${citationNum}]`)
// Create source entry with clickable link
sources.push(`[${citationNum}] [${result.title}](${result.url})`)
}
}
// Only output if we have citations
if (sources.length > 0) {
yield {
type: "text",
text: "\n\n**Sources:**\n" + sources.join("\n"),
}
}
}
}