Merge pull request #651 from basecamp/search-command

Add search command
This commit is contained in:
Jorge Manrubia
2025-06-25 10:49:22 +02:00
committed by GitHub
35 changed files with 45551 additions and 902 deletions
-1
View File
@@ -50,7 +50,6 @@ gem "rails_structured_logging", bc: "rails-structured-logging"
# AI
gem "ruby_llm", git: "https://github.com/crmne/ruby_llm.git"
gem "sqlite-vec", "0.1.7.alpha.2"
gem "tiktoken_ruby"
group :development, :test do
-6
View File
@@ -519,11 +519,6 @@ GEM
fugit (~> 1.11.0)
railties (>= 7.1)
thor (~> 1.3.1)
sqlite-vec (0.1.7.alpha.2-arm64-darwin)
sqlite-vec (0.1.7.alpha.2-arm64-linux)
sqlite-vec (0.1.7.alpha.2-x86_64-darwin)
sqlite-vec (0.1.7.alpha.2-x86_64-linux)
sqlite-vec (0.1.7.alpha.2-x86_64-mingw32)
sqlite3 (2.7.0)
mini_portile2 (~> 2.8.0)
sqlite3 (2.7.0-arm64-darwin)
@@ -623,7 +618,6 @@ DEPENDENCIES
solid_cable (>= 3.0)
solid_cache (~> 1.0)
solid_queue (~> 1.1)
sqlite-vec (= 0.1.7.alpha.2)
sqlite3 (>= 2.0)
stimulus-rails
thruster
-5
View File
@@ -1,5 +0,0 @@
class Search::RefreshEmbeddingJob < ApplicationJob
def perform(record)
record.refresh_search_embedding
end
end
+10 -13
View File
@@ -9,7 +9,6 @@ class Command::Ai::Translator
def translate(query)
response = translate_query_with_llm(query)
Rails.logger.debug "Commands: #{response}"
normalize JSON.parse(response)
end
@@ -51,7 +50,7 @@ class Command::Ai::Translator
## Context Properties for Filtering (use explicitly):
* **terms**: Array of keywords (split individually, e.g., ["some", "term"]). Avoid redundancy.
* **terms**: Array of keywords (split individually, e.g., ["some", "term"]). Avoid redundancy. Only use if the query explicitly refers to cards.
* **indexed_by**: "newest", "oldest", "latest", "stalled", "closed".
* "closed": completed cards.
* "newest": by creation date
@@ -67,6 +66,7 @@ class Command::Ai::Translator
## Explicit Filtering Rules:
* Only use "terms" if the query explicitly refers to cards. If just searching for an expression, ALWAYS use /search.
* Numbers entered without explicit "card" or "cards" prefix should default to `terms`.
* Examples:
* "123": `terms: ["123"]`
@@ -83,15 +83,18 @@ class Command::Ai::Translator
* "Recent cards": use `indexed_by: "newest"`.
* "Cards with recent activity": use `indexed_by: "latest"`.
* "Completed/closed cards": use `indexed_by: "closed"`.
* Unknown terms default to `terms`.
* If cards are described as being “assigned to X” or “currently assigned to X”, treat X as an existing filter.
- For example: “close cards assigned to andy and assign them to kevin” → `assignee_ids: ["andy"]` with `/assign kevin` as a command.
- Only the first mention (“assigned to”) is a filter. The second (“assign”) is a new action.
## Command Interpretation Rules:
* Unless you can clearly match the query with a command, pass the expression verbatim to /search to perform a search with it.
* When searching for nouns (singular or plural), if they don't refer to a person, favor /search with them instead of using the "terms" filter.
* Respect strictly the order of commands as the appear in the user request.
* When using /search, pass the expression to search verbatim, don't interpret it.
* "tag with #design": always `/tag #design`. Do NOT create `tag_ids` context.
* "#design cards" or "cards tagged with #design": use `tag_ids`.
* "#design cards" or "cards tagged with #design": use `tag_ids`. Do not use the /tag command in this case.
* "Assign cards tagged with #design to jz": filter by `tag_ids`, command `/assign jz`. Do NOT generate `/tag` command.
* "close as [reason]" or "close because [reason]": include the reason in the `/close` command, e.g., `/close not now`.
* "close": always `/close`, even if no reason is given or no cards are explicitly described.
@@ -132,15 +135,7 @@ class Command::Ai::Translator
* Close cards: `/close [optional reason]`
* Tag cards: `/tag #[tag-name]`
* Clear filters: `/clear`
* Insights: `/insight [query]`.
- Always use `/insight` for user questions or requests like:
- steps to reproduce
- summary / summarize
- what's the root cause”
- “can you explain…”
- These must be interpreted as requests for insight — **not** tagging.
- Pass the exact query as the /insight param.
- Especially when **inside a card**, interpret standalone phrases as insight commands even if they could be tags.#{' '}
* Search cards: `/search [terms]`
## JSON Output Examples (strictly follow these patterns):
@@ -150,6 +145,8 @@ class Command::Ai::Translator
Omit empty arrays or unnecessary properties. At least one property (`context` or `commands`) must exist.
Never include JSON outside of "context" or "commands".
## Other Strict Instructions:
* NEVER add properties based on view descriptions ("card", "list", etc.).
-8
View File
@@ -12,14 +12,6 @@ class Command::GetInsight < Command
Command::Result::InsightResponse.new(response.content)
end
def undoable?
false
end
def needs_confirmation?
false
end
private
def chat
chat = RubyLLM.chat
+7 -4
View File
@@ -18,6 +18,7 @@ class Command::Parser
private
def parse_command(string)
command_name, *command_arguments = string.strip.split(" ")
combined_arguments = command_arguments.join(" ")
case command_name
when /^#/
@@ -26,16 +27,18 @@ class Command::Parser
Command::GoToUser.new(user_id: assignee_from(command_name)&.id)
when "/assign", "/assignto"
Command::Assign.new(assignee_ids: assignees_from(command_arguments).collect(&:id), card_ids: cards.ids)
when "/insight"
Command::GetInsight.new(query: command_arguments.join(" "), card_ids: cards.ids)
when "/clear"
Command::ClearFilters.new(params: filter.as_params)
when "/close"
Command::Close.new(card_ids: cards.ids, reason: command_arguments.join(" "))
Command::Close.new(card_ids: cards.ids, reason: combined_arguments)
when "/insight"
Command::GetInsight.new(query: combined_arguments, card_ids: cards.ids)
when "/search"
Command::Search.new(terms: combined_arguments)
when "/visit"
Command::VisitUrl.new(url: command_arguments.first)
when "/tag"
Command::Tag.new(tag_title: tag_title_from(command_arguments.join(" ")), card_ids: cards.ids)
Command::Tag.new(tag_title: tag_title_from(combined_arguments), card_ids: cards.ids)
else
parse_free_string(string)
end
+3
View File
@@ -33,6 +33,9 @@ class Command::Parser::Context
private
attr_reader :controller, :action, :params
MAX_CARDS = 20
MAX_CLOSED_CARDS = 10
def extract_url_components
uri = URI.parse(url || "")
route = Rails.application.routes.recognize_path(uri.path)
+11
View File
@@ -0,0 +1,11 @@
class Command::Search < Command
store_accessor :data, :terms
def title
"Search '#{terms}'"
end
def execute
redirect_to search_path(q: terms)
end
end
-29
View File
@@ -1,14 +1,6 @@
module Searchable
extend ActiveSupport::Concern
included do
has_one :search_embedding, as: :record, dependent: :destroy, class_name: "Search::Embedding"
after_create_commit :refresh_search_embedding_later
after_update_commit :refresh_search_embedding_later
after_destroy_commit :remove_search_embedding
end
class_methods do
def searchable_by(*fields, using:)
define_method :search_fields do
@@ -38,13 +30,6 @@ module Searchable
none
end
end
scope :search_similar, ->(query) do
query_embedding = Rails.cache.fetch("embed-search:#{query}") { RubyLLM.embed(Ai::Tokenizer.truncate(query)) }
joins(:search_embedding)
.where("embedding MATCH ? AND k = ?", query_embedding.vectors.to_json, 20)
.order(:distance)
end
end
end
@@ -52,12 +37,6 @@ module Searchable
update_in_search_index
end
def refresh_search_embedding
embedding = RubyLLM.embed(Ai::Tokenizer.truncate(search_embedding_content))
search_embedding = self.search_embedding || build_search_embedding
search_embedding.update! embedding: embedding.vectors.to_json
end
private
def create_in_search_index
fields_sql = [ "rowid", *search_fields ].join(", ")
@@ -88,16 +67,8 @@ module Searchable
execute_sql_with_binds "delete from #{search_table} where rowid = ?", id
end
def refresh_search_embedding_later
Search::RefreshEmbeddingJob.perform_later(self)
end
def execute_sql_with_binds(*statement)
self.class.connection.execute self.class.sanitize_sql(statement)
self.class.connection.raw_connection.changes.nonzero?
end
def remove_search_embedding
search_embedding&.destroy
end
end
+1 -1
View File
@@ -29,7 +29,7 @@ class Filter < ApplicationRecord
result = result.in_stage(stages.ids) if stages.present?
result = result.tagged_with(tags.ids) if tags.present?
result = terms.reduce(result) do |result, term|
result.similar_to(term)
result.mentioning(term)
end
result
-5
View File
@@ -1,5 +0,0 @@
class Search::Embedding < ApplicationRecord
self.table_name = "search_embeddings"
belongs_to :record, polymorphic: true
end
+1 -1
View File
@@ -12,7 +12,7 @@
<dd>Just show cards taggged [tag]</dd>
<dt><code>[id] [id]…</code></dt>
<dd>Show multiple cards by ID</dd>
<dt><code>[search term]</code></dt>
<dt><code>[/search term]</code></dt>
<dd>Keyword search</dd>
<dt><code>/clear</code></dt>
<dd>Clear all filters</dd>
-1
View File
@@ -1,7 +1,6 @@
require_relative "boot"
require "rails/all"
require "sqlite_vec" # Referenced in database.yml
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
-4
View File
@@ -8,8 +8,6 @@ default: &default
adapter: sqlite3
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
timeout: 5000
extensions:
- <%= SqliteVec.loadable_path %>
development:
primary:
@@ -42,8 +40,6 @@ production: &production
<<: *default
database: file:storage/tenants/<%= Rails.env %>/%{tenant}/db/main.sqlite3?vfs=beamer
tenanted: true
extensions:
- <%= SqliteVec.loadable_path %>
primary_original:
<<: *default
database: storage/production.sqlite3
@@ -1,10 +0,0 @@
class CreateSearchEmbeddings < ActiveRecord::Migration[7.1]
def change
create_virtual_table :search_embeddings, :vec0, [
"id INTEGER PRIMARY KEY",
"record_type TEXT NOT NULL",
"record_id INTEGER NOT NULL",
"embedding FLOAT[1536] distance_metric=cosine"
]
end
end
@@ -0,0 +1,5 @@
class DropSearchEmbeddings < ActiveRecord::Migration[8.1]
def change
drop_table :search_embeddings if table_exists?(:search_embeddings)
end
end
Generated
+1 -7
View File
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.1].define(version: 2025_06_24_080408) do
ActiveRecord::Schema[8.1].define(version: 2025_06_25_073215) do
create_table "accesses", force: :cascade do |t|
t.integer "collection_id", null: false
t.datetime "created_at", null: false
@@ -300,10 +300,6 @@ ActiveRecord::Schema[8.1].define(version: 2025_06_24_080408) do
t.index ["reacter_id"], name: "index_reactions_on_reacter_id"
end
# Could not dump table "search_embeddings_vector_chunks00" because of following StandardError
# Unknown type '' for column 'rowid'
create_table "search_queries", force: :cascade do |t|
t.datetime "created_at", null: false
t.string "terms", limit: 2000, null: false
@@ -340,7 +336,6 @@ ActiveRecord::Schema[8.1].define(version: 2025_06_24_080408) do
t.datetime "created_at", null: false
t.string "title"
t.datetime "updated_at", null: false
t.index ["title"], name: "index_tags_on_account_id_and_title", unique: true
end
create_table "users", force: :cascade do |t|
@@ -413,5 +408,4 @@ ActiveRecord::Schema[8.1].define(version: 2025_06_24_080408) do
# Note that virtual tables may not work with other database engines. Be careful if changing database.
create_virtual_table "cards_search_index", "fts5", ["title", "description"]
create_virtual_table "comments_search_index", "fts5", ["body"]
create_virtual_table "search_embeddings", "vec0", ["id INTEGER PRIMARY KEY", "record_type TEXT NOT NULL", "record_id INTEGER NOT NULL", "embedding FLOAT[1536] distance_metric=cosine"]
end
+2 -58
View File
@@ -876,43 +876,6 @@ columns:
default_function:
collation:
comment:
search_embeddings_vector_chunks00:
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: rowid
cast_type: !ruby/object:ActiveModel::Type::Value
precision:
scale:
limit:
sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
sql_type: ''
type:
limit:
precision:
scale:
'null': true
default:
default_function:
collation:
comment:
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: vectors
cast_type: !ruby/object:ActiveModel::Type::Binary
precision:
scale:
limit:
sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
sql_type: BLOB
type: :binary
limit:
precision:
scale:
'null': false
default:
default_function:
collation:
comment:
search_queries:
- *5
- *6
@@ -1112,7 +1075,6 @@ primary_keys:
pins: id
reactions: id
schema_migrations: version
search_embeddings_vector_chunks00: rowid
search_queries: id
search_results: id
sessions: id
@@ -1155,7 +1117,6 @@ data_sources:
pins: true
reactions: true
schema_migrations: true
search_embeddings_vector_chunks00: true
search_queries: true
search_results: true
sessions: true
@@ -2217,7 +2178,6 @@ indexes:
comment:
valid: true
schema_migrations: []
search_embeddings_vector_chunks00: []
search_queries:
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
table: search_queries
@@ -2304,23 +2264,7 @@ indexes:
nulls_not_distinct:
comment:
valid: true
tags:
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
table: tags
name: index_tags_on_account_id_and_title
unique: true
columns:
- title
lengths: {}
orders: {}
opclasses: {}
where:
type:
using:
include:
nulls_not_distinct:
comment:
valid: true
tags: []
users:
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
table: users
@@ -2421,4 +2365,4 @@ indexes:
comment:
valid: true
workflows: []
version: 20250624080408
version: 20250625073215
+1 -9
View File
@@ -49,14 +49,6 @@ class Command::Ai::TranslatorTest < ActionDispatch::IntegrationTest
assert_command({ context: { terms: [ "123" ] } }, "123") # Notice existing cards will be intercepted earlier
end
test "getting insight on cards" do
# Card context
assert_command({ commands: [ "/insight summarize" ] }, "summarize", context: :card)
assert_command({ commands: [ "/insight steps to reproduce" ] }, "steps to reproduce", context: :card)
assert_command({ commands: [ "/insight create a table with the main participants and takeaways" ] }, "create a table with the main participants and takeaways", context: :card)
assert_command({ commands: [ "/insight what's the issue here?" ] }, "what's the issue here?", context: :card)
end
test "filter by collections" do
assert_command({ context: { collection_ids: [ "writebook" ] } }, "writebook collection")
end
@@ -74,7 +66,7 @@ class Command::Ai::TranslatorTest < ActionDispatch::IntegrationTest
test "assign cards" do
# List context
assert_command({ commands: [ "/assign jz" ] }, "assign to jz")
assert_command({ context: { tag_ids: [ "design" ] }, commands: [ "/assign jz" ] }, "assign cards tagged with #design to jz", context: :card)
assert_command({ context: { tag_ids: [ "design" ] }, commands: [ "/assign jz" ] }, "assign cards agged with #design to jz", context: :card)
end
test "tag cards" do
+11
View File
@@ -0,0 +1,11 @@
require "test_helper"
class Command::SearchTest < ActionDispatch::IntegrationTest
include CommandTestHelper
test "redirect to the user perma" do
result = execute_command "/search something"
assert_equal search_path(q: "something"), result.url
end
end
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1139,7 +1139,7 @@ http_interactions:
message: OK
headers:
Date:
- Fri, 13 Jun 2025 08:47:48 GMT
- Wed, 25 Jun 2025 08:15:00 GMT
Content-Type:
- application/json
Transfer-Encoding:
@@ -1151,49 +1151,49 @@ http_interactions:
Openai-Organization:
- 37signals-u7qhwk
Openai-Processing-Ms:
- '552'
- '231'
Openai-Version:
- '2020-10-01'
X-Envoy-Upstream-Service-Time:
- '1219'
- '233'
X-Ratelimit-Limit-Requests:
- '500'
X-Ratelimit-Limit-Tokens:
- '200000'
X-Ratelimit-Remaining-Requests:
- '496'
- '499'
X-Ratelimit-Remaining-Tokens:
- '192836'
- '198471'
X-Ratelimit-Reset-Requests:
- 451ms
- 120ms
X-Ratelimit-Reset-Tokens:
- 2.149s
- 458ms
X-Request-Id:
- req_682e8fe2498c5871cd64c2fd2f4cd15f
- req_fcc9890d0b8aad9d2017564383b9b59b
Strict-Transport-Security:
- max-age=31536000; includeSubDomains; preload
Cf-Cache-Status:
- DYNAMIC
Set-Cookie:
- __cf_bm=kn3zjjJQjEqmQdYVaHa0I.nj_ngeABipZNiH.s1SuaE-1749804468-1.0.1.1-3jYmLThjvdnoysjaWvT.K8f2ixgyQvnn.8oBlDJL2iyYYgdhS6ZKsOk5Cmy6yN31uEfFkhQiO3_Y.CexwlPjVlUqe.y19zrHZMOyv0ThwME;
path=/; expires=Fri, 13-Jun-25 09:17:48 GMT; domain=.api.openai.com; HttpOnly;
- __cf_bm=WMpb9PUdEqxEiexd8jq08pgl1x4.FHZMRDu2LcpvRVg-1750839300-1.0.1.1-7Y4pTbsr1Z1ijRICMOe6KJSbaFuACK4z8hIc6BpiMl82o5QNqm1pRQdpD5W75VaLiO44mG8Un.I4tkUxgpURLXzJBQVjlQqLWsQhwhAnU8A;
path=/; expires=Wed, 25-Jun-25 08:45:00 GMT; domain=.api.openai.com; HttpOnly;
Secure; SameSite=None
- _cfuvid=Gkwdlvw_mgrwqz5bckb7xzoWlk4SsC9oxWnvrUjyC1w-1749804468311-0.0.1.1-604800000;
- _cfuvid=5NiCk.9LIA4D2CGvXkRP7VyoUb1bwhHYrzx8S9kgZWQ-1750839300967-0.0.1.1-604800000;
path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
X-Content-Type-Options:
- nosniff
Server:
- cloudflare
Cf-Ray:
- 94f0533daf9ecfd6-MAD
- 955303bbede20376-MAD
Alt-Svc:
- h3=":443"; ma=86400
body:
encoding: ASCII-8BIT
base64_string: |
ewogICJpZCI6ICJjaGF0Y21wbC1CaHVNZDlCb3p5cHNBeFNCNW5sVEZuam1W
TGE4SyIsCiAgIm9iamVjdCI6ICJjaGF0LmNvbXBsZXRpb24iLAogICJjcmVh
dGVkIjogMTc0OTgwNDQ2NywKICAibW9kZWwiOiAiZ3B0LTQuMS1uYW5vLTIw
ewogICJpZCI6ICJjaGF0Y21wbC1CbUZaVU8zTjhrdXBON3JnTlhJc29OdEM1
VEFTTiIsCiAgIm9iamVjdCI6ICJjaGF0LmNvbXBsZXRpb24iLAogICJjcmVh
dGVkIjogMTc1MDgzOTMwMCwKICAibW9kZWwiOiAiZ3B0LTQuMS1uYW5vLTIw
MjUtMDQtMTQiLAogICJjaG9pY2VzIjogWwogICAgewogICAgICAiaW5kZXgi
OiAwLAogICAgICAibWVzc2FnZSI6IHsKICAgICAgICAicm9sZSI6ICJhc3Np
c3RhbnQiLAogICAgICAgICJjb250ZW50IjogIntcbiAgXCJjb21tYW5kc1wi
@@ -1211,7 +1211,7 @@ http_interactions:
b25fdG9rZW5zIjogMAogICAgfQogIH0sCiAgInNlcnZpY2VfdGllciI6ICJk
ZWZhdWx0IiwKICAic3lzdGVtX2ZpbmdlcnByaW50IjogImZwXzM4MzQzYTJm
OGYiCn0K
recorded_at: Fri, 13 Jun 2025 08:47:48 GMT
recorded_at: Wed, 25 Jun 2025 08:15:00 GMT
- request:
method: post
uri: https://api.openai.com/v1/chat/completions
@@ -1381,7 +1381,7 @@ http_interactions:
message: OK
headers:
Date:
- Fri, 13 Jun 2025 08:47:49 GMT
- Wed, 25 Jun 2025 08:15:01 GMT
Content-Type:
- application/json
Transfer-Encoding:
@@ -1393,49 +1393,49 @@ http_interactions:
Openai-Organization:
- 37signals-u7qhwk
Openai-Processing-Ms:
- '492'
- '532'
Openai-Version:
- '2020-10-01'
X-Envoy-Upstream-Service-Time:
- '499'
- '571'
X-Ratelimit-Limit-Requests:
- '500'
X-Ratelimit-Limit-Tokens:
- '200000'
X-Ratelimit-Remaining-Requests:
- '493'
- '499'
X-Ratelimit-Remaining-Tokens:
- '168070'
- '198468'
X-Ratelimit-Reset-Requests:
- 833ms
- 120ms
X-Ratelimit-Reset-Tokens:
- 9.578s
- 459ms
X-Request-Id:
- req_49b4fd29ab1ef2cc2235bff8983af417
- req_197d08d40959a8c27fe72eab5234245a
Strict-Transport-Security:
- max-age=31536000; includeSubDomains; preload
Cf-Cache-Status:
- DYNAMIC
Set-Cookie:
- __cf_bm=X4twDVzvsLRMkxz4vZq0vcmm8ahGWQ9JbJEJwMRz0W4-1749804469-1.0.1.1-uSTi.0tHcF0Qc2.xcXNeiaKz2PNPiCdUbDL9PkeYzp4o_49OZhRSaCeB0z2Yynf7EC1ZCYGd0ussv0t0taF4CvtrMu.qAxKlzi6uR1Fy9Qs;
path=/; expires=Fri, 13-Jun-25 09:17:49 GMT; domain=.api.openai.com; HttpOnly;
- __cf_bm=T9oU.BmrDGSjLZcrFJ2HTmdcErRK12kk5GOCMaW6sFQ-1750839301-1.0.1.1-458TjrXi2347GETdBjdxZBMTb8lrB4jjo70fl.aHKGBv5F_Nt1HNBec22kIJbV8kidexkUJF0PXOsS9E8xVNlFM3NcNk65xVqd8Mv3DV.oA;
path=/; expires=Wed, 25-Jun-25 08:45:01 GMT; domain=.api.openai.com; HttpOnly;
Secure; SameSite=None
- _cfuvid=tMeAYaIywRrksYRl8cUmiByEf9yH60xTIohN1.ZSz88-1749804469613-0.0.1.1-604800000;
- _cfuvid=h6KhNvkd.dCvOmpZk88ChgL6H.4QaeRqeQ1yHbSpnpY-1750839301889-0.0.1.1-604800000;
path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
X-Content-Type-Options:
- nosniff
Server:
- cloudflare
Cf-Ray:
- 94f053474ad71ba7-MAD
- 955303bf59232f95-MAD
Alt-Svc:
- h3=":443"; ma=86400
body:
encoding: ASCII-8BIT
base64_string: |
ewogICJpZCI6ICJjaGF0Y21wbC1CaHVNZlN3TUsxWDVGb09wVTJRQksyeTg2
UmI1SSIsCiAgIm9iamVjdCI6ICJjaGF0LmNvbXBsZXRpb24iLAogICJjcmVh
dGVkIjogMTc0OTgwNDQ2OSwKICAibW9kZWwiOiAiZ3B0LTQuMS1uYW5vLTIw
ewogICJpZCI6ICJjaGF0Y21wbC1CbUZaVmlNSEs5aUFoUWw2UUtGaGZXU2ds
VjdqSCIsCiAgIm9iamVjdCI6ICJjaGF0LmNvbXBsZXRpb24iLAogICJjcmVh
dGVkIjogMTc1MDgzOTMwMSwKICAibW9kZWwiOiAiZ3B0LTQuMS1uYW5vLTIw
MjUtMDQtMTQiLAogICJjaG9pY2VzIjogWwogICAgewogICAgICAiaW5kZXgi
OiAwLAogICAgICAibWVzc2FnZSI6IHsKICAgICAgICAicm9sZSI6ICJhc3Np
c3RhbnQiLAogICAgICAgICJjb250ZW50IjogIntcbiAgXCJjb21tYW5kc1wi
@@ -1453,7 +1453,7 @@ http_interactions:
X3ByZWRpY3Rpb25fdG9rZW5zIjogMAogICAgfQogIH0sCiAgInNlcnZpY2Vf
dGllciI6ICJkZWZhdWx0IiwKICAic3lzdGVtX2ZpbmdlcnByaW50IjogImZw
XzM4MzQzYTJmOGYiCn0K
recorded_at: Fri, 13 Jun 2025 08:47:49 GMT
recorded_at: Wed, 25 Jun 2025 08:15:01 GMT
- request:
method: post
uri: https://api.openai.com/v1/chat/completions
@@ -1624,7 +1624,7 @@ http_interactions:
message: OK
headers:
Date:
- Fri, 13 Jun 2025 08:47:50 GMT
- Wed, 25 Jun 2025 08:15:02 GMT
Content-Type:
- application/json
Transfer-Encoding:
@@ -1636,49 +1636,49 @@ http_interactions:
Openai-Organization:
- 37signals-u7qhwk
Openai-Processing-Ms:
- '636'
- '436'
Openai-Version:
- '2020-10-01'
X-Envoy-Upstream-Service-Time:
- '643'
- '443'
X-Ratelimit-Limit-Requests:
- '500'
X-Ratelimit-Limit-Tokens:
- '200000'
X-Ratelimit-Remaining-Requests:
- '494'
- '499'
X-Ratelimit-Remaining-Tokens:
- '162655'
- '198460'
X-Ratelimit-Reset-Requests:
- 650ms
- 120ms
X-Ratelimit-Reset-Tokens:
- 11.203s
- 462ms
X-Request-Id:
- req_624109a0227870bea01613d671b69f93
- req_2e73bfd8b0109639112a54d022e152ad
Strict-Transport-Security:
- max-age=31536000; includeSubDomains; preload
Cf-Cache-Status:
- DYNAMIC
Set-Cookie:
- __cf_bm=.sxZ.MjG8iOm7VQMEuijMVYcJEKyxXIK1ZNJh.nolRQ-1749804470-1.0.1.1-lQ9uvHXe79.8D9w7SEmhuUaKucyEHSTbaBgxJklvJ0akHwboyhbfJgx4ZttjZni8iXPv6IjyYZQQmRexENuZX1O5agM9eghBRDB6X5vBJXs;
path=/; expires=Fri, 13-Jun-25 09:17:50 GMT; domain=.api.openai.com; HttpOnly;
- __cf_bm=_wEU25fjA2m_6D6bOm6MGk6Udb25jzD4Hr9yWl7dDMI-1750839302-1.0.1.1-8nBCackFOk7v4jAnxnG8JIhU.GEdDm8TPYhxD7WQ6ncXyboZaRHLHnyHFVuRiUVs6SGj7xeRyENJfHE8TC9sfQD8sjxNVfABlCeXmV6LYv0;
path=/; expires=Wed, 25-Jun-25 08:45:02 GMT; domain=.api.openai.com; HttpOnly;
Secure; SameSite=None
- _cfuvid=A5qLm_no2aVnDzOLhFcmE13Q3GsisQyX9iWtQ2EnoWs-1749804470590-0.0.1.1-604800000;
- _cfuvid=uLaQDLm_jVt0YAOjc0DFPP_Vpg9Lzj.1o..Ycch.c3I-1750839302695-0.0.1.1-604800000;
path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
X-Content-Type-Options:
- nosniff
Server:
- cloudflare
Cf-Ray:
- 94f0534f6809cbe3-MAD
- 955303c53b38cbb3-MAD
Alt-Svc:
- h3=":443"; ma=86400
body:
encoding: ASCII-8BIT
base64_string: |
ewogICJpZCI6ICJjaGF0Y21wbC1CaHVNZkpqaDlEMzZuNXBpRlNielVhYUFj
MXNWNSIsCiAgIm9iamVjdCI6ICJjaGF0LmNvbXBsZXRpb24iLAogICJjcmVh
dGVkIjogMTc0OTgwNDQ2OSwKICAibW9kZWwiOiAiZ3B0LTQuMS1uYW5vLTIw
ewogICJpZCI6ICJjaGF0Y21wbC1CbUZaV3BkN1ozMjgzUEU3VFBMekZHUHBV
MXFwMCIsCiAgIm9iamVjdCI6ICJjaGF0LmNvbXBsZXRpb24iLAogICJjcmVh
dGVkIjogMTc1MDgzOTMwMiwKICAibW9kZWwiOiAiZ3B0LTQuMS1uYW5vLTIw
MjUtMDQtMTQiLAogICJjaG9pY2VzIjogWwogICAgewogICAgICAiaW5kZXgi
OiAwLAogICAgICAibWVzc2FnZSI6IHsKICAgICAgICAicm9sZSI6ICJhc3Np
c3RhbnQiLAogICAgICAgICJjb250ZW50IjogIntcbiAgXCJjb21tYW5kc1wi
@@ -1697,7 +1697,7 @@ http_interactions:
aW9uX3Rva2VucyI6IDAKICAgIH0KICB9LAogICJzZXJ2aWNlX3RpZXIiOiAi
ZGVmYXVsdCIsCiAgInN5c3RlbV9maW5nZXJwcmludCI6ICJmcF8zODM0M2Ey
ZjhmIgp9Cg==
recorded_at: Fri, 13 Jun 2025 08:47:50 GMT
recorded_at: Wed, 25 Jun 2025 08:15:02 GMT
- request:
method: post
uri: https://api.openai.com/v1/chat/completions
@@ -1867,7 +1867,7 @@ http_interactions:
message: OK
headers:
Date:
- Fri, 13 Jun 2025 08:47:51 GMT
- Wed, 25 Jun 2025 08:15:03 GMT
Content-Type:
- application/json
Transfer-Encoding:
@@ -1879,49 +1879,49 @@ http_interactions:
Openai-Organization:
- 37signals-u7qhwk
Openai-Processing-Ms:
- '454'
- '302'
Openai-Version:
- '2020-10-01'
X-Envoy-Upstream-Service-Time:
- '458'
- '317'
X-Ratelimit-Limit-Requests:
- '500'
X-Ratelimit-Limit-Tokens:
- '200000'
X-Ratelimit-Remaining-Requests:
- '498'
- '499'
X-Ratelimit-Remaining-Tokens:
- '157020'
- '198468'
X-Ratelimit-Reset-Requests:
- 216ms
- 120ms
X-Ratelimit-Reset-Tokens:
- 12.893s
- 459ms
X-Request-Id:
- req_15a18d411d2fe922272221e0ff7f298e
- req_22e40c5e6140e493f7b15830323e510b
Strict-Transport-Security:
- max-age=31536000; includeSubDomains; preload
Cf-Cache-Status:
- DYNAMIC
Set-Cookie:
- __cf_bm=kvQZ_3DaJuFzllRMfWDIBSRBxWCGavdRJdrB.q08DNo-1749804471-1.0.1.1-f2qyEnR8rxKd921J6ChS38FMhiNjIjifB..Re3glmNsT6RXbhK4PVrV8c7TueglC9a2CbaXaPiJe8DcHLNQGzdJdCguRIgXgIG_GAZTx9BY;
path=/; expires=Fri, 13-Jun-25 09:17:51 GMT; domain=.api.openai.com; HttpOnly;
- __cf_bm=r.SLrzLTOg4lA12ATHGy.mvkfFqFSLN64c8hV0dRU9g-1750839303-1.0.1.1-JeWYzqubi4fI0BQw8S6xniw9R1kGAtXjKpGpfiN0pDTXJYWCpglzDxuZT4Q_mEj_3kB72o4dwCUFbe2YONMXRNyexVslKsPcBmEuCW7y3V8;
path=/; expires=Wed, 25-Jun-25 08:45:03 GMT; domain=.api.openai.com; HttpOnly;
Secure; SameSite=None
- _cfuvid=4CTVSG5qWcaCpTVPEt7UbHBCmF03X5XQ3fjDYve5J40-1749804471542-0.0.1.1-604800000;
- _cfuvid=m1GIxv436pFdPxSi.A2SDw14CLHrP94_QyCbLhPqUc8-1750839303372-0.0.1.1-604800000;
path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
X-Content-Type-Options:
- nosniff
Server:
- cloudflare
Cf-Ray:
- 94f053569b5beca6-MAD
- 955303ca3863e071-MAD
Alt-Svc:
- h3=":443"; ma=86400
body:
encoding: ASCII-8BIT
base64_string: |
ewogICJpZCI6ICJjaGF0Y21wbC1CaHVNZ1pTTGdOVDY3aTh6RnNYd01YbmFj
TjNBbyIsCiAgIm9iamVjdCI6ICJjaGF0LmNvbXBsZXRpb24iLAogICJjcmVh
dGVkIjogMTc0OTgwNDQ3MCwKICAibW9kZWwiOiAiZ3B0LTQuMS1uYW5vLTIw
ewogICJpZCI6ICJjaGF0Y21wbC1CbUZaV1NJbW5GSXNNU3U4elRvM1NtMW5y
NHUzMSIsCiAgIm9iamVjdCI6ICJjaGF0LmNvbXBsZXRpb24iLAogICJjcmVh
dGVkIjogMTc1MDgzOTMwMiwKICAibW9kZWwiOiAiZ3B0LTQuMS1uYW5vLTIw
MjUtMDQtMTQiLAogICJjaG9pY2VzIjogWwogICAgewogICAgICAiaW5kZXgi
OiAwLAogICAgICAibWVzc2FnZSI6IHsKICAgICAgICAicm9sZSI6ICJhc3Np
c3RhbnQiLAogICAgICAgICJjb250ZW50IjogIntcbiAgXCJjb21tYW5kc1wi
@@ -1939,5 +1939,247 @@ http_interactions:
Y3RlZF9wcmVkaWN0aW9uX3Rva2VucyI6IDAKICAgIH0KICB9LAogICJzZXJ2
aWNlX3RpZXIiOiAiZGVmYXVsdCIsCiAgInN5c3RlbV9maW5nZXJwcmludCI6
ICJmcF8zODM0M2EyZjhmIgp9Cg==
recorded_at: Fri, 13 Jun 2025 08:47:51 GMT
recorded_at: Wed, 25 Jun 2025 08:15:03 GMT
- request:
method: post
uri: https://api.openai.com/v1/chat/completions
body:
encoding: UTF-8
base64_string: |
eyJtb2RlbCI6ImdwdC00LjEtbmFubyIsIm1lc3NhZ2VzIjpbeyJyb2xlIjoi
ZGV2ZWxvcGVyIiwiY29udGVudCI6IllvdSBhcmUgRml6ennigJlzIGNvbW1h
bmQgdHJhbnNsYXRvci4gWW91ciB0YXNrIGlzIHRvOlxuXG4xLiBSZWFkIHRo
ZSB1c2VyJ3MgcmVxdWVzdC5cbjIuIENvbnN1bHQgdGhlIGN1cnJlbnQgY29u
dGV4dCAocHJvdmlkZWQgYmVsb3cgZm9yIGluZm9ybWF0aW9uYWwgcHVycG9z
ZXMgb25seSkuXG4zLiBEZXRlcm1pbmUgaWYgdGhlIGN1cnJlbnQgY29udGV4
dCBzdWZmaWNlcyBvciBpZiBhIG5ldyBjb250ZXh0IGlzIHJlcXVpcmVkLlxu
NC4gR2VuZXJhdGUgb25seSB0aGUgbmVjZXNzYXJ5IGNvbW1hbmRzIHRvIGZ1
bGZpbGwgdGhlIHJlcXVlc3QuXG41LiBPdXRwdXQgYSBKU09OIG9iamVjdCBj
b250YWluaW5nIE9OTFk6XG4gICogQSBcImNvbnRleHRcIiBvYmplY3QgKHJl
cXVpcmVkIGlmIGFueSBmaWx0ZXJpbmcgYXBwbGllcyDigJQgaW5jbHVkaW5n
IHRlcm1zIOKAlCBhbmQgbXVzdCBpbmNsdWRlIGFsbCBmaWx0ZXJzIGluc2lk
ZSBpdCkuXG4gICogQSBcImNvbW1hbmRzXCIgYXJyYXkgKG9ubHkgaWYgY29t
bWFuZHMgYXJlIGV4cGxpY2l0bHkgcmVxdWVzdGVkIG9yIGNsZWFybHkgaW1w
bGllZCkuXG5cbkRvIE5PVCBhZGQgYW55IG90aGVyIHByb3BlcnRpZXMgdG8g
eW91ciBKU09OIG91dHB1dC5cblxuVGhlIGRlc2NyaXB0aW9uIG9mIHRoZSBj
dXJyZW50IHZpZXcgKFwiaW5zaWRlIGEgY2FyZFwiLCBcInZpZXdpbmcgYSBs
aXN0IG9mIGNhcmRzXCIsIG9yIFwibm90IHNlZWluZyBjYXJkc1wiKSBpcyBp
bmZvcm1hdGlvbmFsIG9ubHkuIERvIE5PVCByZWZsZWN0IHRoaXMgZGVzY3Jp
cHRpb24gZXhwbGljaXRseSBvciBpbXBsaWNpdGx5IGluIHlvdXIgb3V0cHV0
IEpTT04uIE5FVkVSIGdlbmVyYXRlIHByb3BlcnRpZXMgbGlrZSBcInZpZXdc
IiBvciBhZGQgXCJ0ZXJtc1wiIGJhc2VkIG9uIFwiY2FyZFwiIG9yIFwibGlz
dFwiIGNvbnRleHQuXG5cbiMjIEZpenp5IERhdGEgU3RydWN0dXJlXG5cbiog
KipDYXJkcyoqOiBSZXByZXNlbnQgaXNzdWVzLCBmZWF0dXJlcywgYnVncywg
dGFza3MsIG9yIHByb2JsZW1zLlxuKiBDYXJkcyBoYXZlICoqY29tbWVudHMq
KiBhbmQgYXJlIGNvbnRhaW5lZCB3aXRoaW4gKipjb2xsZWN0aW9ucyoqLlxu
XG4jIyBDb250ZXh0IFByb3BlcnRpZXMgZm9yIEZpbHRlcmluZyAodXNlIGV4
cGxpY2l0bHkpOlxuXG4qICoqdGVybXMqKjogQXJyYXkgb2Yga2V5d29yZHMg
KHNwbGl0IGluZGl2aWR1YWxseSwgZS5nLiwgW1wic29tZVwiLCBcInRlcm1c
Il0pLiBBdm9pZCByZWR1bmRhbmN5LiBPbmx5IHVzZSBpZiB0aGUgcXVlcnkg
ZXhwbGljaXRseSByZWZlcnMgdG8gY2FyZHMuXG4qICoqaW5kZXhlZF9ieSoq
OiBcIm5ld2VzdFwiLCBcIm9sZGVzdFwiLCBcImxhdGVzdFwiLCBcInN0YWxs
ZWRcIiwgXCJjbG9zZWRcIi5cbiAgKiBcImNsb3NlZFwiOiBjb21wbGV0ZWQg
Y2FyZHMuXG4gICogXCJuZXdlc3RcIjogYnkgY3JlYXRpb24gZGF0ZVxuICAq
IFwibGF0ZXN0XCI6IGJ5IHVwZGF0ZSBkYXRlLlxuICAqIFwic3RhbGxlZFwi
OiBjYXJkcyB0aGF0IHN0b3BwZWQgc2hvd2luZyBhY3Rpdml0eSBhZnRlciBh
biBpbml0aWFsIGFjdGl2aXR5IHNwaWtlLlxuKiAqKmFzc2lnbmVlX2lkcyoq
OiBBcnJheSBvZiBhc3NpZ25lZSBuYW1lcy5cbiogKiphc3NpZ25tZW50X3N0
YXR1cyoqOiBcInVuYXNzaWduZWRcIi5cbiogKiplbmdhZ2VtZW50X3N0YXR1
cyoqOiBcImNvbnNpZGVyaW5nXCIgb3IgXCJkb2luZ1wiLlxuKiAqKmNhcmRf
aWRzKio6IEFycmF5IG9mIGNhcmQgSURzLlxuKiAqKmNyZWF0b3JfaWQqKjog
Q3JlYXRvcidzIG5hbWUuXG4qICoqY29sbGVjdGlvbl9pZHMqKjogQXJyYXkg
b2YgZXhwbGljaXRseSBtZW50aW9uZWQgY29sbGVjdGlvbnMuXG4qICoqdGFn
X2lkcyoqOiBBcnJheSBvZiB0YWcgbmFtZXMgKHVzZSBmb3IgXCIjdGFnXCIg
b3IgXCJ0YWdnZWQgd2l0aFwiKS5cblxuIyMgRXhwbGljaXQgRmlsdGVyaW5n
IFJ1bGVzOlxuXG4qIE9ubHkgdXNlIFwidGVybXNcIiBpZiB0aGUgcXVlcnkg
ZXhwbGljaXRseSByZWZlcnMgdG8gY2FyZHMuIElmIGp1c3Qgc2VhcmNoaW5n
IGZvciBhbiBleHByZXNzaW9uLCBBTFdBWVMgdXNlIC9zZWFyY2guXG4qIE51
bWJlcnMgZW50ZXJlZCB3aXRob3V0IGV4cGxpY2l0IFwiY2FyZFwiIG9yIFwi
Y2FyZHNcIiBwcmVmaXggc2hvdWxkIGRlZmF1bHQgdG8gYHRlcm1zYC5cbiAg
KiBFeGFtcGxlczpcbiAgICAqIFwiMTIzXCI6IGB0ZXJtczogW1wiMTIzXCJd
YFxuICAgICogXCJjYXJkIDEyM1wiOiBgY2FyZF9pZHM6IFsxMjNdYFxuICAg
ICogXCJjYXJkIDEsMlwiOiB1c2UgYGNhcmRfaWRzOiBbMSwgMl1gXG4qIElm
IHRoZSB1c2VyIHNheXMgc29tZXRoaW5nIGxpa2Ug4oCcWCBjb2xsZWN0aW9u
4oCdIG9yIOKAnGNvbGxlY3Rpb24gWOKAnSwgdHJlYXQgWCBhcyBhIGBjb2xs
ZWN0aW9uX2lkYC5cbiAgLSBlLmcuLCDigJx3cml0ZWJvb2sgY29sbGVjdGlv
buKAnSDihpIgYGNvbGxlY3Rpb25faWRzOiBbXCJ3cml0ZWJvb2tcIl1gXG4q
IFwiQXNzaWduZWQgdG8gWFwiOiB1c2UgYGFzc2lnbmVlX2lkc2AuXG4qIFwi
Q3JlYXRlZCBieSBYXCI6IHVzZSBgY3JlYXRvcl9pZGAuXG4qIFwiVGFnZ2Vk
IHdpdGggWFwiLCBcIiNYIGNhcmRzXCI6IHVzZSBgdGFnX2lkc2AgKG5ldmVy
IFwidGVybXNcIikuXG4gIC0gRm9yIGV4YW1wbGU6IFwiI2Rlc2lnbiBjYXJk
c1wiIG9yIFwiY2FyZHMgdGFnZ2VkIHdpdGggI2Rlc2lnblwiIHNob3VsZCBh
bHdheXMgcmVzdWx0IGluIGB0YWdfaWRzOiBbXCJkZXNpZ25cIl1gLlxuKiBc
IlVuYXNzaWduZWQgY2FyZHNcIjogdXNlIGBhc3NpZ25tZW50X3N0YXR1czog
XCJ1bmFzc2lnbmVkXCJgLlxuKiBcIk15IGNhcmRzXCI6IENhcmRzIGFzc2ln
bmVkIHRvIHRoZSByZXF1ZXN0ZXIuXG4qIFwiUmVjZW50IGNhcmRzXCI6IHVz
ZSBgaW5kZXhlZF9ieTogXCJuZXdlc3RcImAuXG4qIFwiQ2FyZHMgd2l0aCBy
ZWNlbnQgYWN0aXZpdHlcIjogdXNlIGBpbmRleGVkX2J5OiBcImxhdGVzdFwi
YC5cbiogXCJDb21wbGV0ZWQvY2xvc2VkIGNhcmRzXCI6IHVzZSBgaW5kZXhl
ZF9ieTogXCJjbG9zZWRcImAuXG4qIElmIGNhcmRzIGFyZSBkZXNjcmliZWQg
YXMgYmVpbmcg4oCcYXNzaWduZWQgdG8gWOKAnSBvciDigJxjdXJyZW50bHkg
YXNzaWduZWQgdG8gWOKAnSwgdHJlYXQgWCBhcyBhbiBleGlzdGluZyBmaWx0
ZXIuXG4gIC0gRm9yIGV4YW1wbGU6IOKAnGNsb3NlIGNhcmRzIGFzc2lnbmVk
IHRvIGFuZHkgYW5kIGFzc2lnbiB0aGVtIHRvIGtldmlu4oCdIOKGkiBgYXNz
aWduZWVfaWRzOiBbXCJhbmR5XCJdYCB3aXRoIGAvYXNzaWduIGtldmluYCBh
cyBhIGNvbW1hbmQuXG4gIC0gT25seSB0aGUgZmlyc3QgbWVudGlvbiAo4oCc
YXNzaWduZWQgdG/igJ0pIGlzIGEgZmlsdGVyLiBUaGUgc2Vjb25kICjigJxh
c3NpZ27igJ0pIGlzIGEgbmV3IGFjdGlvbi5cblxuIyMgQ29tbWFuZCBJbnRl
cnByZXRhdGlvbiBSdWxlczpcblxuKiBVbmxlc3MgeW91IGNhbiBjbGVhcmx5
IG1hdGNoIHRoZSBxdWVyeSB3aXRoIGEgY29tbWFuZCwgcGFzcyB0aGUgZXhw
cmVzc2lvbiB2ZXJiYXRpbSB0byAvc2VhcmNoIHRvIHBlcmZvcm0gYSBzZWFy
Y2ggd2l0aCBpdC5cbiogV2hlbiBzZWFyY2hpbmcgZm9yIG5vdW5zIChzaW5n
dWxhciBvciBwbHVyYWwpLCBpZiB0aGV5IGRvbid0IHJlZmVyIHRvIGEgcGVy
c29uLCBmYXZvciAvc2VhcmNoIHdpdGggdGhlbSBpbnN0ZWFkIG9mIHVzaW5n
IHRoZSBcInRlcm1zXCIgZmlsdGVyLlxuKiBXaGVuIHVzaW5nIC9zZWFyY2gs
IHBhc3MgdGhlIGV4cHJlc3Npb24gdG8gc2VhcmNoIHZlcmJhdGltLCBkb24n
dCBpbnRlcnByZXQgaXQuXG4qIFwidGFnIHdpdGggI2Rlc2lnblwiOiBhbHdh
eXMgYC90YWcgI2Rlc2lnbmAuIERvIE5PVCBjcmVhdGUgYHRhZ19pZHNgIGNv
bnRleHQuXG4qIFwiI2Rlc2lnbiBjYXJkc1wiIG9yIFwiY2FyZHMgdGFnZ2Vk
IHdpdGggI2Rlc2lnblwiOiB1c2UgYHRhZ19pZHNgLlxuKiBcIkFzc2lnbiBj
YXJkcyB0YWdnZWQgd2l0aCAjZGVzaWduIHRvIGp6XCI6IGZpbHRlciBieSBg
dGFnX2lkc2AsIGNvbW1hbmQgYC9hc3NpZ24ganpgLiBEbyBOT1QgZ2VuZXJh
dGUgYC90YWdgIGNvbW1hbmQuXG4qIFwiY2xvc2UgYXMgW3JlYXNvbl1cIiBv
ciBcImNsb3NlIGJlY2F1c2UgW3JlYXNvbl1cIjogaW5jbHVkZSB0aGUgcmVh
c29uIGluIHRoZSBgL2Nsb3NlYCBjb21tYW5kLCBlLmcuLCBgL2Nsb3NlIG5v
dCBub3dgLlxuKiBcImNsb3NlXCI6IGFsd2F5cyBgL2Nsb3NlYCwgZXZlbiBp
ZiBubyByZWFzb24gaXMgZ2l2ZW4gb3Igbm8gY2FyZHMgYXJlIGV4cGxpY2l0
bHkgZGVzY3JpYmVkLlxuICAgIC0gSWYgdGhlIHVzZXIganVzdCBzYXlzIOKA
nGNsb3Nl4oCdLCBhc3N1bWUgdGhleSBtZWFuIHRvIGNsb3NlIHRoZSBjdXJy
ZW50IHNldCBvZiB2aXNpYmxlIGNhcmRzIG9yIGNvbnRleHQuXG4qIEFsd2F5
cyBnZW5lcmF0ZSBjb21tYW5kcyBpbiB0aGUgb3JkZXIgdGhleSBhcHBlYXIg
aW4gdGhlIHF1ZXJ5LlxuXG4jIyDimqDvuI8gQ3J1Y2lhbCBSdWxlcyB0byBB
dm9pZCBDb25mdXNpb246XG5cbiogKipDb250ZXh0IGZpbHRlcnMqKiBhbHdh
eXMgcmVwcmVzZW50ICoqZXhpc3RpbmcgY29uZGl0aW9ucyoqIHRoYXQgY2Fy
ZHMgKiphbHJlYWR5IHNhdGlzZnkqKi5cbiogKipDb21tYW5kcyoqIChgL2Fz
c2lnbmAsIGAvdGFnYCwgYC9jbG9zZWApIHJlcHJlc2VudCAqKm5ldyBhY3Rp
b25zKiogdG8gYXBwbHkuXG4qICoqTkVWRVIqKiB1c2UgbmFtZXMgb3IgdGFn
cyBtZW50aW9uZWQgaW4gKipjb21tYW5kcyoqIGFzIGZpbHRlcmluZyBjcml0
ZXJpYS5cblxuICAqIEUuZy46IFwiQXNzaWduIGFuZHlcIiBtZWFucyBhICoq
bmV3IGFzc2lnbm1lbnQqKiB0byBgYW5keWAuIERvIE5PVCBmaWx0ZXIgYnkg
YGFzc2lnbmVlX2lkczogW1wiYW5keVwiXWAuXG4gICogRS5nLjogXCJUYWcg
d2l0aCAjdjJcIiBtZWFucyBhcHBseWluZyBhICoqbmV3IHRhZyoqLiBEbyBO
T1QgZmlsdGVyIGJ5IGB0YWdfaWRzOiBbXCJ2MlwiXWAuXG5cbiMjIyBFeGFt
cGxlcyAoc3RyaWN0bHkgZm9sbG93IHRoZXNlKTpcblxuVXNlciBxdWVyeTpc
bmBcImFzc2lnbiBhbmR5IHRvIHRoZSBjdXJyZW50ICNkZXNpZ24gY2FyZHMg
YXNzaWduZWQgdG8ganogYW5kIHRhZyB0aGVtIHdpdGggI3YyXCJgXG5cbuKc
hSBDb3JyZWN0IE91dHB1dDpcblxue1xuICBcImNvbnRleHRcIjogeyBcImFz
c2lnbmVlX2lkc1wiOiBbXCJqelwiXSwgXCJ0YWdfaWRzXCI6IFtcImRlc2ln
blwiXSB9LFxuICBcImNvbW1hbmRzXCI6IFtcIi9hc3NpZ24gYW5keVwiLCBc
Ii90YWcgI3YyXCJdXG59XG5cbuKdjCBJbmNvcnJlY3QgKERPIE5PVCBnZW5l
cmF0ZSk6XG5cbntcbiAgXCJjb250ZXh0XCI6IHsgXCJhc3NpZ25lZV9pZHNc
IjogW1wiYW5keVwiXSwgXCJ0YWdfaWRzXCI6IFtcInYyXCJdIH0sXG4gIFwi
Y29tbWFuZHNcIjogW1wiL2Fzc2lnbiBhbmR5XCIsIFwiL3RhZyAjdjJcIl1c
bn1cblxuIyMgQ29tbWFuZHMgKHByZWZpeCAnLycpOlxuXG4qIEFzc2lnbiB1
c2VyOiBgL2Fzc2lnbiBbdXNlcl1gXG4qIENsb3NlIGNhcmRzOiBgL2Nsb3Nl
IFtvcHRpb25hbCByZWFzb25dYFxuKiBUYWcgY2FyZHM6IGAvdGFnICNbdGFn
LW5hbWVdYFxuKiBDbGVhciBmaWx0ZXJzOiBgL2NsZWFyYFxuKiBTZWFyY2gg
Y2FyZHM6IGAvc2VhcmNoIFt0ZXJtc11gXG5cbiMjIEpTT04gT3V0cHV0IEV4
YW1wbGVzIChzdHJpY3RseSBmb2xsb3cgdGhlc2UgcGF0dGVybnMpOlxuXG57
IFwiY29udGV4dFwiOiB7IFwiYXNzaWduZWVfaWRzXCI6IFtcImpvcmdlXCJd
IH0sIFwiY29tbWFuZHNcIjogW1wiL2Nsb3NlXCJdIH1cbnsgXCJjb250ZXh0
XCI6IHsgXCJ0YWdfaWRzXCI6IFtcImRlc2lnblwiXSB9IH1cbnsgXCJjb21t
YW5kc1wiOiBbXCIvYXNzaWduIGpvcmdlXCIsIFwiL3RhZyAjZGVzaWduXCJd
IH1cblxuT21pdCBlbXB0eSBhcnJheXMgb3IgdW5uZWNlc3NhcnkgcHJvcGVy
dGllcy4gQXQgbGVhc3Qgb25lIHByb3BlcnR5IChgY29udGV4dGAgb3IgYGNv
bW1hbmRzYCkgbXVzdCBleGlzdC5cblxuIyMgT3RoZXIgU3RyaWN0IEluc3Ry
dWN0aW9uczpcblxuKiBORVZFUiBhZGQgcHJvcGVydGllcyBiYXNlZCBvbiB2
aWV3IGRlc2NyaXB0aW9ucyAoXCJjYXJkXCIsIFwibGlzdFwiLCBldGMuKS5c
biogQXZvaWQgcmVkdW5kYW50IHRlcm1zLlxuKiBEb24ndCBkdXBsaWNhdGUg
dGVybXMgYWNyb3NzIHByb3BlcnRpZXMuXG4qIEFsbCBmaWx0ZXJzIOKAlCBp
bmNsdWRpbmcgdGVybXMg4oCUIG11c3QgYmUgaW5zaWRlIHRoZSBgXCJjb250
ZXh0XCJgIG9iamVjdC5cbiogRmF2b3IgY2xhcml0eSwgcHJlY2lzaW9uLCBh
bmQgY29uY2lzZW5lc3MuXG5UaGUgbmFtZSBvZiB0aGUgdXNlciBtYWtpbmcg
cmVxdWVzdHMgaXMgZGF2aWQuXG5cbiMjIEN1cnJlbnQgdmlldzpcblxuVGhl
IHVzZXIgaXMgY3VycmVudGx5IGluc2lkZSBhIGNhcmQgfS5cbiJ9LHsicm9s
ZSI6InVzZXIiLCJjb250ZW50Ijoic3VtbWFyaXplIn1dLCJzdHJlYW0iOmZh
bHNlLCJ0ZW1wZXJhdHVyZSI6MC43fQ==
headers:
User-Agent:
- Faraday v2.13.1
Authorization:
- Bearer <OPEN_API_KEY>
Content-Type:
- application/json
Accept-Encoding:
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept:
- "*/*"
response:
status:
code: 200
message: OK
headers:
Date:
- Wed, 25 Jun 2025 08:28:53 GMT
Content-Type:
- application/json
Transfer-Encoding:
- chunked
Connection:
- keep-alive
Access-Control-Expose-Headers:
- X-Request-ID
Openai-Organization:
- 37signals-u7qhwk
Openai-Processing-Ms:
- '263'
Openai-Version:
- '2020-10-01'
X-Envoy-Upstream-Service-Time:
- '268'
X-Ratelimit-Limit-Requests:
- '500'
X-Ratelimit-Limit-Tokens:
- '200000'
X-Ratelimit-Remaining-Requests:
- '499'
X-Ratelimit-Remaining-Tokens:
- '198461'
X-Ratelimit-Reset-Requests:
- 120ms
X-Ratelimit-Reset-Tokens:
- 461ms
X-Request-Id:
- req_0ead0653ada331592da6c8c49789244a
Strict-Transport-Security:
- max-age=31536000; includeSubDomains; preload
Cf-Cache-Status:
- DYNAMIC
Set-Cookie:
- __cf_bm=zL2S4L1i1KMLrrqcCCerR87vbLBhIZJ41LbX9vpmE_o-1750840133-1.0.1.1-d1AKhPMwj_pMib8DFzhjR.zGXVpQZLXPCtXa1uN0PMohUp.XixrQ0QT3smL2TT05Xkkv0ThVyAqhj50CwauYpw68UAnJQrItR5Zx_EeXUT0;
path=/; expires=Wed, 25-Jun-25 08:58:53 GMT; domain=.api.openai.com; HttpOnly;
Secure; SameSite=None
- _cfuvid=8tJnnp.o3OSIgT6AWdpIKLdiT6d8fuR5IykEuUEFE7U-1750840133486-0.0.1.1-604800000;
path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
X-Content-Type-Options:
- nosniff
Server:
- cloudflare
Cf-Ray:
- 9553180ee9c40328-MAD
Alt-Svc:
- h3=":443"; ma=86400
body:
encoding: ASCII-8BIT
base64_string: |
ewogICJpZCI6ICJjaGF0Y21wbC1CbUZtdm51RTA0S241RE9oZjhrMzBKVTZ1
WUt6ayIsCiAgIm9iamVjdCI6ICJjaGF0LmNvbXBsZXRpb24iLAogICJjcmVh
dGVkIjogMTc1MDg0MDEzMywKICAibW9kZWwiOiAiZ3B0LTQuMS1uYW5vLTIw
MjUtMDQtMTQiLAogICJjaG9pY2VzIjogWwogICAgewogICAgICAiaW5kZXgi
OiAwLAogICAgICAibWVzc2FnZSI6IHsKICAgICAgICAicm9sZSI6ICJhc3Np
c3RhbnQiLAogICAgICAgICJjb250ZW50IjogIntcbiAgXCJjb250ZXh0XCI6
IHt9XG59IiwKICAgICAgICAicmVmdXNhbCI6IG51bGwsCiAgICAgICAgImFu
bm90YXRpb25zIjogW10KICAgICAgfSwKICAgICAgImxvZ3Byb2JzIjogbnVs
bCwKICAgICAgImZpbmlzaF9yZWFzb24iOiAic3RvcCIKICAgIH0KICBdLAog
ICJ1c2FnZSI6IHsKICAgICJwcm9tcHRfdG9rZW5zIjogMTU1OSwKICAgICJj
b21wbGV0aW9uX3Rva2VucyI6IDcsCiAgICAidG90YWxfdG9rZW5zIjogMTU2
NiwKICAgICJwcm9tcHRfdG9rZW5zX2RldGFpbHMiOiB7CiAgICAgICJjYWNo
ZWRfdG9rZW5zIjogMTQwOCwKICAgICAgImF1ZGlvX3Rva2VucyI6IDAKICAg
IH0sCiAgICAiY29tcGxldGlvbl90b2tlbnNfZGV0YWlscyI6IHsKICAgICAg
InJlYXNvbmluZ190b2tlbnMiOiAwLAogICAgICAiYXVkaW9fdG9rZW5zIjog
MCwKICAgICAgImFjY2VwdGVkX3ByZWRpY3Rpb25fdG9rZW5zIjogMCwKICAg
ICAgInJlamVjdGVkX3ByZWRpY3Rpb25fdG9rZW5zIjogMAogICAgfQogIH0s
CiAgInNlcnZpY2VfdGllciI6ICJkZWZhdWx0IiwKICAic3lzdGVtX2Zpbmdl
cnByaW50IjogImZwXzM4MzQzYTJmOGYiCn0K
recorded_at: Wed, 25 Jun 2025 08:28:53 GMT
recorded_with: VCR 6.3.1
File diff suppressed because it is too large Load Diff