Skip to content

feat: add Exa AI-powered search tool#19

Merged
macOS26 merged 1 commit into
macOS26:mainfrom
tgonzalezc5:feat/exa-search
May 1, 2026
Merged

feat: add Exa AI-powered search tool#19
macOS26 merged 1 commit into
macOS26:mainfrom
tgonzalezc5:feat/exa-search

Conversation

@tgonzalezc5
Copy link
Copy Markdown
Contributor

Summary

  • Adds Exa (api.exa.ai/search) as a web-search provider, fitting into the existing Tavily / Z.AI / Ollama / DuckDuckGo fallback chain in performWebSearchForTask.
  • New key is stored in Keychain (KeychainService.get/setExaAPIKey) and exposed in Settings next to the existing Tavily field. When configured, Exa is preferred over Tavily; DuckDuckGo remains the keyless fallback.
  • Exa is requested with type: auto and contents: { text, highlights }. A typed Decodable response model is decoded, and a exaSnippet helper cascades through highlights → summary → text so the formatter handles whatever combination Exa returns for a given page.

Why Exa

Exa's auto search type chooses between neural and keyword retrieval per query, which complements the existing keyword-style providers and is useful for the more open-ended research queries the agent often makes. The new field is opt-in and does not change behavior for users who only have a Tavily key set.

Usage

Open Settings → "Web Search" and paste an Exa key:

exa-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Once set, calls through performWebSearchForTask (e.g. the web_search / search tool) hit Exa first.

// performWebSearchForTask fallback chain:
// 1. Ollama Web Search (if provider == .ollama and key set)
// 2. Z.AI search-prime (if provider in [.zAI, .bigModel] and key set)
// 3. Exa /search (if Exa key set)              ← new
// 4. Tavily /search (if Tavily key passed in)
// 5. DuckDuckGo HTML scrape (always-on fallback)

Files changed

  • Agent/AgentViewModel/TaskUtilities/WebSearch.swift — new performExaSearchInternal, typed response decoder, snippet helper; Exa step inserted into the fallback chain
  • Agent/Services/KeychainService.swiftget/setExaAPIKey
  • Agent/AgentViewModel/Core/AgentViewModel.swiftexaAPIKey property bound to Keychain
  • Agent/Views/Settings/SettingsView.swift — Exa API key field in the Web Search section
  • Agent.xcodeproj/project.pbxproj — register ExaSearchTests.swift in the AgentTests target
  • AgentTests/ExaSearchTests.swift — unit tests for response parsing and snippet fallback
  • README.md — tool table updated to mention Exa/Tavily/DuckDuckGo

Test plan

  • swiftc -parse passes on every modified Swift file
  • ExaSearchTests covers: standard response, empty results, malformed JSON, missing optional fields, snippet cascade (highlights → summary → text), whitespace-only highlight skipping, empty-key guard
  • xcodebuild -scheme "Agent!" build (requires full Xcode; my local env only has Command Line Tools, so I couldn't run it — happy to iterate if CI flags anything)
  • xcodebuild -scheme "Agent!" test runs the new ExaSearchTests suite (same caveat)
  • Manual smoke test: enter an Exa key in Settings, run a web_search and confirm Exa results render

Notes

  • No new dependencies — the implementation uses URLSession + JSONDecoder, matching how the other providers are written.
  • All existing providers (Tavily, Z.AI, Ollama, DuckDuckGo) are untouched.

Slots Exa into the existing Tavily/Z.AI/Ollama/DuckDuckGo fallback
chain in performWebSearchForTask, just before Tavily, so users with
an Exa key get neural search with text+highlights content.

- KeychainService: add get/setExaAPIKey
- AgentViewModel: add exaAPIKey property bound to Keychain
- WebSearch: new performExaSearchInternal (POST api.exa.ai/search,
  type: auto, contents: text + highlights) with a typed Decodable
  response and a snippet helper that cascades highlights → summary
  → text
- Settings: Exa key field next to the existing Tavily field
- README: tool table reflects the new provider list
- Tests: ExaSearchTests covers parsing, snippet fallback, and the
  empty-key guard
@macOS26 macOS26 merged commit 9f25195 into macOS26:main May 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants