Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.5.6] - 2026-03-02

### Changed

- **Proxy default from env**: `AI::Chat.new` now enables proxy mode by default when `AICHAT_PROXY` is exactly `"true"`.

- **Schema generation proxy default**: `AI::Chat.generate_schema!` now uses the same `AICHAT_PROXY` default when `proxy:` is omitted.

- **Explicit override precedence**: Explicit `chat.proxy = ...` and `generate_schema!(..., proxy: ...)` continue to override env defaults.

### Added

- **Unit coverage for proxy defaults**: Added tests for env parsing (`"true"` exact match), explicit override behavior, and `generate_schema!` precedence.

- **Proxy env documentation**: README now documents `AICHAT_PROXY` behavior for both chat generation and schema generation.

## [0.5.5] - 2026-02-10

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
ai-chat (0.5.5)
ai-chat (0.5.6)
amazing_print (~> 2.0)
base64 (~> 0.1, > 0.1.1)
json (~> 2.0)
Expand Down
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,14 @@ AI::Chat.generate_schema!("A user with full name (required), first_name (require
AI::Chat.generate_schema!("A user with full name (required), first_name (required), and last_name (required).", api_key_env_var: "CUSTOM_KEY")
```

`generate_schema!` also follows proxy defaults from the `AICHAT_PROXY` environment variable. Proxy is enabled only when `AICHAT_PROXY` is exactly `"true"`.

```bash
export AICHAT_PROXY=true
```

If you pass `proxy: true` or `proxy: false`, that explicit value overrides the env default.

You can choose a location for the schema to save by using the `location` keyword argument.

```rb
Expand Down Expand Up @@ -622,6 +630,14 @@ puts chat.last[:content]
# => "Once upon a time..."
```

You can also default proxy mode from the environment for both `AI::Chat.new` and `AI::Chat.generate_schema!`:

```bash
export AICHAT_PROXY=true
```

Proxy is enabled only when `AICHAT_PROXY` is exactly `"true"`. Any other value (including `"TRUE"` or `"1"`) leaves proxy disabled unless you explicitly set `chat.proxy = true` or pass `proxy: true`.

When proxy is enabled, **you must use the API key provided by prepend.me** in place of a real OpenAI API key. Refer to [the section on API keys](#api-key) for options on how to set your key.

## Building Conversations Without API Calls
Expand Down
2 changes: 1 addition & 1 deletion ai-chat.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Gem::Specification.new do |spec|
spec.name = "ai-chat"
spec.version = "0.5.5"
spec.version = "0.5.6"
spec.authors = ["Raghu Betina", "Jelani Woods"]
spec.email = ["raghu@firstdraft.com", "jelani@firstdraft.com"]
spec.homepage = "https://github.com/firstdraft/ai-chat"
Expand Down
9 changes: 6 additions & 3 deletions lib/ai/chat.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,23 @@ class Chat

def initialize(api_key: nil, api_key_env_var: "OPENAI_API_KEY")
@api_key = api_key || ENV.fetch(api_key_env_var)
@proxy = false
@proxy = ENV["AICHAT_PROXY"] == "true"
@messages = []
@reasoning_effort = nil
@model = "gpt-5.2"
@client = OpenAI::Client.new(api_key: @api_key)
client_options = {api_key: @api_key}
client_options[:base_url] = BASE_PROXY_URL if @proxy
@client = OpenAI::Client.new(**client_options)
@last_response_id = nil
@image_generation = false
@image_folder = "./images"
@api_key_validated = false
@verbosity = :medium
end

def self.generate_schema!(description, location: "schema.json", api_key: nil, api_key_env_var: "OPENAI_API_KEY", proxy: false)
def self.generate_schema!(description, location: "schema.json", api_key: nil, api_key_env_var: "OPENAI_API_KEY", proxy: nil)
api_key ||= ENV.fetch(api_key_env_var)
proxy = ENV["AICHAT_PROXY"] == "true" if proxy.nil?
prompt_path = File.expand_path("../prompts/schema_generator.md", __dir__)
system_prompt = File.read(prompt_path)

Expand Down
134 changes: 134 additions & 0 deletions spec/unit/chat_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,140 @@
RSpec.describe AI::Chat do
let(:chat) { AI::Chat.new }

def with_env_var(name, value)
original = ENV.key?(name) ? ENV[name] : :__undefined__

if value.nil?
ENV.delete(name)
else
ENV[name] = value
end

yield
ensure
if original == :__undefined__
ENV.delete(name)
else
ENV[name] = original
end
end

def schema_client_double
response = double("response", output_text: '{"type":"object","properties":{},"required":[],"additionalProperties":false}')
responses = double("responses")
allow(responses).to receive(:create).and_return(response)
double("client", responses: responses)
end

around do |example|
with_env_var("AICHAT_PROXY", nil) do
example.run
end
end

describe "proxy defaults" do
it "defaults proxy to false when AICHAT_PROXY is not set" do
with_env_var("AICHAT_PROXY", nil) do
client_double = instance_double(OpenAI::Client)
expect(OpenAI::Client).to receive(:new).with(api_key: "test-key").and_return(client_double)

instance = AI::Chat.new(api_key: "test-key")

expect(instance.proxy).to be(false)
end
end

it "defaults proxy to true when AICHAT_PROXY is exactly true" do
with_env_var("AICHAT_PROXY", "true") do
client_double = instance_double(OpenAI::Client)
expect(OpenAI::Client).to receive(:new).with(
api_key: "test-key",
base_url: AI::Chat::BASE_PROXY_URL
).and_return(client_double)

instance = AI::Chat.new(api_key: "test-key")

expect(instance.proxy).to be(true)
end
end

it "does not enable proxy for non-exact truthy values" do
with_env_var("AICHAT_PROXY", "TRUE") do
client_double = instance_double(OpenAI::Client)
expect(OpenAI::Client).to receive(:new).with(api_key: "test-key").and_return(client_double)

instance = AI::Chat.new(api_key: "test-key")

expect(instance.proxy).to be(false)
end
end

it "allows explicit override to false even when env default is true" do
with_env_var("AICHAT_PROXY", "true") do
client_double = instance_double(OpenAI::Client)
allow(OpenAI::Client).to receive(:new).and_return(client_double)

instance = AI::Chat.new(api_key: "test-key")
instance.proxy = false

expect(OpenAI::Client).to have_received(:new).with(api_key: "test-key", base_url: AI::Chat::BASE_PROXY_URL)
expect(OpenAI::Client).to have_received(:new).with(api_key: "test-key")
expect(instance.proxy).to be(false)
end
end

it "allows explicit override to true when env default is false" do
with_env_var("AICHAT_PROXY", nil) do
client_double = instance_double(OpenAI::Client)
allow(OpenAI::Client).to receive(:new).and_return(client_double)

instance = AI::Chat.new(api_key: "test-key")
instance.proxy = true

expect(OpenAI::Client).to have_received(:new).with(api_key: "test-key")
expect(OpenAI::Client).to have_received(:new).with(api_key: "test-key", base_url: AI::Chat::BASE_PROXY_URL)
expect(instance.proxy).to be(true)
end
end
end

describe ".generate_schema!" do
it "uses env proxy default when proxy keyword is omitted" do
with_env_var("AICHAT_PROXY", "true") do
client_double = schema_client_double
expect(OpenAI::Client).to receive(:new).with(
api_key: "test-key",
base_url: AI::Chat::BASE_PROXY_URL
).and_return(client_double)

result = AI::Chat.generate_schema!("A tiny schema", api_key: "test-key", location: false)

expect(result).to include("\"type\": \"object\"")
end
end

it "lets explicit proxy false override env proxy default" do
with_env_var("AICHAT_PROXY", "true") do
client_double = schema_client_double
expect(OpenAI::Client).to receive(:new).with(api_key: "test-key").and_return(client_double)

AI::Chat.generate_schema!("A tiny schema", api_key: "test-key", location: false, proxy: false)
end
end

it "lets explicit proxy true override env proxy default" do
with_env_var("AICHAT_PROXY", nil) do
client_double = schema_client_double
expect(OpenAI::Client).to receive(:new).with(
api_key: "test-key",
base_url: AI::Chat::BASE_PROXY_URL
).and_return(client_double)

AI::Chat.generate_schema!("A tiny schema", api_key: "test-key", location: false, proxy: true)
end
end
end

describe "#add" do
it "returns the added message, not the messages array" do
result = chat.add("Hello", role: "user")
Expand Down