Skip to content
Open
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
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ source 'https://rubygems.org'
ruby '4.0.1'

gem 'base64'
gem 'llm_gateway', github: 'Hyper-Unearthing/llm_gateway', branch: 'refactor/major-internal-organisation'
gem 'llm_gateway', github: 'Hyper-Unearthing/llm_gateway', tag: 'v0.8.0'
gem 'singleton'
gem 'activerecord'
gem 'sqlite3'
Expand Down
6 changes: 3 additions & 3 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
GIT
remote: https://github.com/Hyper-Unearthing/llm_gateway.git
revision: a9b8eff24b34c80f957fa57ebdf0d34fe52d4a6f
branch: refactor/major-internal-organisation
revision: 838d670daecb90bb79115e737f41d18aae2c6e73
tag: v0.8.0
specs:
llm_gateway (0.3.0)
llm_gateway (0.8.0)
dry-struct

GEM
Expand Down
26 changes: 13 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,19 @@ cd gruv
Using OpenAI OAuth Codex
```bash
ruby setup_provider.rb openai
./gruv -p openai_oauth_codex
./gruv -p openai_codex -m gpt-5.4
```

Using Anthropic provider (OAuth from auth file if present, otherwise API key env var)
```bash
ruby setup_provider.rb anthropic
./gruv -p anthropic_apikey_messages
./gruv -p anthropic_messages -m claude-sonnet-4-20250514
```

Using OpenAI API key providers
```bash
OPENAI_API_KEY=... ./gruv -p openai_apikey_completions
OPENAI_API_KEY=... ./gruv -p openai_apikey_responses
OPENAI_API_KEY=... ./gruv -p openai_completions -m gpt-5.1
OPENAI_API_KEY=... ./gruv -p openai_responses -m gpt-5.4
```

`-p` maps directly to llm_gateway provider keys. Use `-m`/`--model` for model selection.
Expand All @@ -32,25 +32,25 @@ OAuth auth is read from `~/.config/gruv/auth.json`.

Stream test provider/model combinations:
```bash
# openai_apikey_completions_gpt_5_1
OPENAI_API_KEY=... ./gruv -p openai_apikey_completions -m gpt-5.1
# openai_completions_gpt_5_1
OPENAI_API_KEY=... ./gruv -p openai_completions -m gpt-5.1

# anthropic_apikey_messages_claude_sonnet_4
# anthropic_messages_claude_sonnet_4
# (uses anthropic OAuth token from auth.json if present, else ANTHROPIC_API_KEY)
ANTHROPIC_API_KEY=... ./gruv -p anthropic_apikey_messages -m claude-sonnet-4-20250514
ANTHROPIC_API_KEY=... ./gruv -p anthropic_messages -m claude-sonnet-4-20250514

# openai_apikey_responses_gpt_5_4
OPENAI_API_KEY=... ./gruv -p openai_apikey_responses -m gpt-5.4
# openai_responses_gpt_5_4
OPENAI_API_KEY=... ./gruv -p openai_responses -m gpt-5.4

# openai_oauth_codex_gpt_5_4
# openai_codex_gpt_5_4
ruby setup_provider.rb openai
./gruv -p openai_oauth_codex -m gpt-5.4
./gruv -p openai_codex -m gpt-5.4
```
Run the unified setup wizard (can be run multiple times safely):

```bash
bundle exec ruby setup.rb
./gruv -p openai_oauth_codex -m gpt-5.4
./gruv -p openai_codex -m gpt-5.4
```

The wizard lets you pick which things to configure:
Expand Down
4 changes: 2 additions & 2 deletions agents/sessions/agent_session.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def continue(&event_handler)
end

def compact
session_manager.compaction(@agent.client)
session_manager.compaction(@agent.client, model: @agent.model || RuntimeConfig.model)
@agent.transcript = model_input_messages
end

Expand All @@ -48,7 +48,7 @@ def handle_agent_event(event)
assistant_message = event.message.to_h
@session_manager.push_message(assistant_message)
when Agent::Event::TurnEnd
tool_result_content = event.tool_results.flat_map { |tool_result| tool_result.to_h[:content] }
tool_result_content = event.tool_results.map(&:to_h)
if tool_result_content.any?
tool_result_message = {
role: 'user',
Expand Down
32 changes: 28 additions & 4 deletions agents/sessions/base_session_manager.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
require 'securerandom'
require 'time'
require_relative 'concerns/basic_compaction'
require_relative 'usage_normalizer'

class BaseSessionManager
include BasicCompaction
Expand Down Expand Up @@ -60,8 +59,10 @@ def build_model_input_messages
end

def total_tokens
entry = message_events.reverse.find { |event| event.dig(:data, :usage, :total_tokens) }
entry&.dig(:data, :usage, :total_tokens) || 0
entry = message_events.reverse.find { |event| usage_total(message_usage(event[:data])) }
return 0 unless entry

usage_total(message_usage(entry[:data])) || 0
end

private
Expand All @@ -79,7 +80,30 @@ def last_compaction_entry
end

def message_usage(message)
UsageNormalizer.normalize(message[:usage])
usage = message[:usage] || message['usage']
return nil unless usage

usage = usage.transform_keys(&:to_sym)
return usage if usage.key?(:total)

input = usage[:input] || usage[:input_tokens] || usage[:prompt_tokens] || 0
cache_read = usage[:cache_read] || usage[:cache_read_input_tokens] || 0
cache_write = usage[:cache_write] || usage[:cache_creation_input_tokens] || 0
output = usage[:output] || usage[:output_tokens] || usage[:completion_tokens] || 0
total = usage[:total] || usage[:total_tokens] || (input + cache_read + cache_write + output)

usage.merge(
input: input,
cache_read: cache_read,
cache_write: cache_write,
output: output,
total: total,
total_tokens: total
)
end

def usage_total(usage)
usage && (usage[:total] || usage[:total_tokens])
end

def persist_entry(entry)
Expand Down
7 changes: 3 additions & 4 deletions agents/sessions/compaction_prompt.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,8 @@ class CompactionPrompt < LlmGateway::Prompt
- Keep the summary concise and structured by topic.
PROMPT

def initialize(client, messages, last_summary: nil)
super(client)
@client = client
def initialize(provider:, messages:, model: nil, last_summary: nil)
super(provider: provider, model: model)
@messages = messages
@last_summary = last_summary
end
Expand Down Expand Up @@ -59,7 +58,7 @@ def system_prompt
end

def post(&block)
@client.stream(
stream(
prompt,
tools: [],
system: system_prompt,
Expand Down
9 changes: 7 additions & 2 deletions agents/sessions/concerns/basic_compaction.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
module BasicCompaction
def compaction(adapter)
result = CompactionPrompt.new(adapter, active_messages, last_summary: last_compaction_entry&.dig(:data, :summary)).post
def compaction(adapter, model: nil)
result = CompactionPrompt.new(
provider: adapter,
model: model,
messages: active_messages,
last_summary: last_compaction_entry&.dig(:data, :summary)
).post
content_blocks, usage = extract_compaction_content_and_usage(result)

text_parts = content_blocks.filter_map do |part|
Expand Down
73 changes: 0 additions & 73 deletions agents/sessions/usage_normalizer.rb

This file was deleted.

11 changes: 8 additions & 3 deletions config/runtime_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@

module RuntimeConfig
class << self
attr_accessor :provider_name, :model_key
attr_accessor :provider_name, :model

def set(provider_name:, model_key: nil)
def set(provider_name:, model: nil)
@provider_name = provider_name
@model_key = model_key
@model = model
end

# Backward-compatible reader for callers that still refer to model_key.
def model_key
model
end
end
end
73 changes: 26 additions & 47 deletions lib/agents/agent.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,18 @@

class Agent < LlmGateway::Prompt
attr_reader :client
attr_accessor :transcript, :cache_key, :cache_retention
attr_accessor :transcript

def initialize(client, transcript: [])
super(client.client.model_key)
def initialize(client, model: RuntimeConfig.model)
super(provider: client, model: model, reasoning: 'high')
@client = client
@transcript = transcript
@transcript = []
end

def prompt
transcript
end

def self.tools
self::TOOLS
end

def self.find_tool(name)
tools.find { |tool| tool.tool_name == name }
end

def tools
self.class.tools.map(&:definition)
end

def run(user_input, &block)
append_user_message([user_input])
continue(&block)
Expand All @@ -39,16 +27,18 @@ def continue(&block)
send_and_process(messages:, &block)
end

def post(&block)
stream_options = {
def stream_options
{
tools: tools,
system: system_prompt,
reasoning: 'high'
}

stream_options[:cache_key] = cache_key if cache_key
stream_options[:cache_retention] = cache_retention if cache_retention
reasoning: reasoning,
model: model,
cache_key: cache_key,
cache_retention: cache_retention
}.compact
end

def post(&block)
@client.stream(
prompt,
**stream_options,
Expand All @@ -75,13 +65,20 @@ def send_and_process(messages:, &block)
tool_results = result.content.select { |message| message.type == 'tool_use' }.map do |message|
parameters = message.to_h
emit(Event::ToolExecutionStart.new(parameters: parameters), &block)
tool_result = execute_tool(message)
emit(Event::ToolExecutionEnd.new(parameters: parameters, result: tool_result.to_h), &block)
transcript << tool_result.to_h
messages.concat([tool_result])
tool_result = execute_tool_request(message)
emit(Event::ToolExecutionEnd.new(parameters: parameters, result: tool_result), &block)
tool_result
end

if tool_results.any?
tool_result_message = {
role: 'user',
content: tool_results.map(&:to_h)
}
transcript << tool_result_message
messages << tool_result_message
end

turn_end_event = Event::TurnEnd.new(message: assistant_message, tool_results: tool_results)
emit(turn_end_event, &block)

Expand All @@ -103,26 +100,8 @@ def emit(event, &block)
block.call(event)
end

def execute_tool(tool_request)
tool_name = tool_request.name
tool_input = tool_request.input
tool_class = self.class.find_tool(tool_name)

result = begin
if tool_class
tool_class.new.execute(tool_input)
else
"Unknown tool: #{tool_name}"
end
rescue StandardError => e
"Error executing tool: #{e.message}"
end

Event::ToolCallResult.new(
type: 'tool_result',
tool_use_id: tool_request.id,
content: result
)
def execute_tool_request(tool_request)
find_and_execute_tool(tool_request)
end
end

Expand Down
Loading
Loading