Skip to content

Commit fe60134

Browse files
vicentereigclaude
andcommitted
fix: wrap text/highlights/summary/context in contents object
The Exa API requires content extraction options (text, highlights, summary, context) to be nested inside a `contents` object in the request payload. Previously, these were sent at the top level, causing the API to silently ignore them and return no text content. This fix modifies SearchRequest#to_payload to automatically wrap these options in the required `contents` structure, making text extraction work as documented. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 02346ac commit fe60134

File tree

4 files changed

+29
-3
lines changed

4 files changed

+29
-3
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## [1.2.2] - 2025-12-22
4+
- **BUGFIX**: Fix text/highlights/summary/context extraction in search results.
5+
- The Exa API requires these options to be wrapped in a `contents` object.
6+
- Previously, passing `text: true` or `text: {max_characters: 1000}` would silently return no text.
7+
- Now correctly serializes to `{"contents": {"text": ...}}` as the API expects.
8+
39
## [1.2.0] - 2025-10-31
410
- Add `Exa::Internal::Transport::AsyncRequester`, enabling optional fiber-scheduler-friendly HTTP via the `async` ecosystem.
511
- Document async usage in the README with Gemfile requirements and concurrent request example.

lib/exa/types/search.rb

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,33 @@ class ExtrasOptions < T::Struct
6161
const :image_links, T.nilable(Integer)
6262
end
6363

64+
# Content options that get wrapped in a `contents` object for the API
65+
CONTENT_OPTION_KEYS = %i[text highlights summary context].freeze
66+
6467
class SearchRequest < T::Struct
6568
include StructWrapper
6669
include SearchOptionProps
6770
const :query, String
6871

6972
def to_payload
70-
Serializer.to_payload(self)
73+
payload = Serializer.to_payload(self)
74+
wrap_contents_options(payload)
75+
end
76+
77+
private
78+
79+
def wrap_contents_options(payload)
80+
contents = {}
81+
82+
CONTENT_OPTION_KEYS.each do |key|
83+
camel_key = Serializer.camelize(key)
84+
next unless payload.key?(camel_key)
85+
86+
contents[camel_key] = payload.delete(camel_key)
87+
end
88+
89+
payload["contents"] = contents unless contents.empty?
90+
payload
7191
end
7292
end
7393
end

lib/exa/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# frozen_string_literal: true
22

33
module Exa
4-
VERSION = "1.2.1"
4+
VERSION = "1.2.2"
55
end

test/types/serializer_test.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def test_search_request_serializes_to_camel_case
2222
assert_equal "latest ai", payload["query"]
2323
assert_equal 5, payload["numResults"]
2424
assert_equal 10, payload["livecrawlTimeout"]
25-
assert_equal 1000, payload.dig("text", "maxCharacters")
25+
assert_equal 1000, payload.dig("contents", "text", "maxCharacters")
2626
end
2727

2828
def test_find_similar_serializes_flags

0 commit comments

Comments
 (0)