packages/provider-openai is one of three answer-visibility provider adapters (alongside Gemini and Claude). It queries the OpenAI Responses API with web search enabled to determine which domains are cited in AI-generated answers for tracked queries.
Validates that the config has a non-empty API key. Returns the model name that will be used.
Makes a lightweight OpenAI API call to verify the key works. Returns ok/error with a message.
Sends the query to the OpenAI Responses API with web_search tool enabled. The query is sent as-is. Returns:
rawResponse— the full OpenAI API response (output items, usage metadata)groundingSources— extracted{ uri, title }pairs from URL citation annotationssearchQueries— web search queries extracted fromweb_search_call.action.query/action.queriesmodel— the model used (default:gpt-5.4)
Extracts analyst-relevant fields from the raw response:
answerText— concatenated text fromoutput_textcontent items in message outputscitedDomains— unique domains extracted from URL citation annotations (www. stripped)groundingSources— pass-through of{ uri, title }pairssearchQueries— pass-through of search queries used
Default: gpt-5.4. Configurable via OpenAIConfig.model.
Optional. Set OpenAIConfig.baseUrl — via the OPENAI_BASE_URL env var or providers.openai.baseUrl in ~/.canonry/config.yaml — to route requests through a proxy or gateway in front of the OpenAI API. It maps to the SDK's baseURL. When unset, the SDK uses its default endpoint (https://api.openai.com/v1).
The provider uses OpenAI's web search tool (web_search, the current GA tool — released 2025-08-26 in the SDK as web_search_2025_08_26). When enabled, the Responses API:
- Executes web searches relevant to the query (exposed as
web_search_calloutput items) - Generates a response with inline URL citations
- URL citations appear as annotations on
output_textcontent blocks
Citation detection works by extracting domains from final output_text.annotations entries where type === 'url_citation'. The provider intentionally does not treat web_search_call.action.sources as citations, because those are retrieval/search telemetry rather than final answer citations. The job runner then matches the cited domains against the project's canonical domain and competitor domains to determine citation state.
We deliberately do not set the new web_search tool's filters.allowed_domains — Canonry tracks who actually gets cited across the open web, so allow-listing would defeat the point. web_search (GA) is used over the legacy web_search_preview for the same reason: measure the open web as users see it.
- Web search guide: https://developers.openai.com/api/docs/guides/tools-web-search
- Responses web search type: https://github.com/openai/openai-python/blob/main/src/openai/types/responses/response_function_web_search.py
- Output text annotation type: https://github.com/openai/openai-python/blob/main/src/openai/types/responses/response_output_text.py
- URIs are parsed with
new URL() www.prefix is stripped- Duplicates are removed
- Invalid URIs are silently skipped
- max 2 in-flight requests per workspace
- 10 requests per minute
- 1000 requests per day
Quota policy is passed via OpenAIConfig.quotaPolicy but enforcement is handled by the job runner (not the provider itself).
The job runner stores the following in query_snapshots.raw_response as JSON:
{
"model": "gpt-4o",
"groundingSources": [
{ "uri": "https://example.com/page", "title": "Page Title" }
],
"searchQueries": ["keyword related search"],
"apiResponse": { "output": [...] }
}Live OpenAI API calls implemented with Responses API web search. The openai SDK is used for API communication.