rubyn-code 0.2.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +151 -5
- data/db/migrations/013_add_failed_status_to_tasks.rb +51 -0
- data/lib/rubyn_code/agent/background_job_handler.rb +71 -0
- data/lib/rubyn_code/agent/conversation.rb +84 -56
- data/lib/rubyn_code/agent/dynamic_tool_schema.rb +152 -0
- data/lib/rubyn_code/agent/feedback_handler.rb +49 -0
- data/lib/rubyn_code/agent/llm_caller.rb +157 -0
- data/lib/rubyn_code/agent/loop.rb +182 -683
- data/lib/rubyn_code/agent/loop_detector.rb +50 -11
- data/lib/rubyn_code/agent/prompts.rb +109 -0
- data/lib/rubyn_code/agent/response_modes.rb +111 -0
- data/lib/rubyn_code/agent/response_parser.rb +111 -0
- data/lib/rubyn_code/agent/system_prompt_builder.rb +211 -0
- data/lib/rubyn_code/agent/tool_processor.rb +178 -0
- data/lib/rubyn_code/agent/usage_tracker.rb +59 -0
- data/lib/rubyn_code/auth/key_encryption.rb +118 -0
- data/lib/rubyn_code/auth/oauth.rb +80 -64
- data/lib/rubyn_code/auth/server.rb +21 -24
- data/lib/rubyn_code/auth/token_store.rb +80 -52
- data/lib/rubyn_code/autonomous/daemon.rb +146 -32
- data/lib/rubyn_code/autonomous/idle_poller.rb +4 -24
- data/lib/rubyn_code/autonomous/task_claimer.rb +46 -44
- data/lib/rubyn_code/background/worker.rb +64 -76
- data/lib/rubyn_code/cli/app.rb +159 -114
- data/lib/rubyn_code/cli/commands/doctor.rb +73 -0
- data/lib/rubyn_code/cli/commands/mcp.rb +77 -0
- data/lib/rubyn_code/cli/commands/model.rb +105 -18
- data/lib/rubyn_code/cli/commands/new_session.rb +45 -0
- data/lib/rubyn_code/cli/commands/provider.rb +123 -0
- data/lib/rubyn_code/cli/commands/skill.rb +52 -3
- data/lib/rubyn_code/cli/daemon_runner.rb +64 -11
- data/lib/rubyn_code/cli/first_run.rb +159 -0
- data/lib/rubyn_code/cli/renderer.rb +109 -60
- data/lib/rubyn_code/cli/repl.rb +48 -374
- data/lib/rubyn_code/cli/repl_commands.rb +177 -0
- data/lib/rubyn_code/cli/repl_lifecycle.rb +76 -0
- data/lib/rubyn_code/cli/repl_setup.rb +181 -0
- data/lib/rubyn_code/cli/setup.rb +6 -2
- data/lib/rubyn_code/cli/stream_formatter.rb +56 -49
- data/lib/rubyn_code/cli/version_check.rb +28 -11
- data/lib/rubyn_code/config/defaults.rb +11 -0
- data/lib/rubyn_code/config/project_profile.rb +185 -0
- data/lib/rubyn_code/config/schema.json +49 -0
- data/lib/rubyn_code/config/settings.rb +103 -1
- data/lib/rubyn_code/config/validator.rb +63 -0
- data/lib/rubyn_code/context/auto_compact.rb +1 -1
- data/lib/rubyn_code/context/context_budget.rb +182 -0
- data/lib/rubyn_code/context/context_collapse.rb +34 -4
- data/lib/rubyn_code/context/decision_compactor.rb +99 -0
- data/lib/rubyn_code/context/manager.rb +44 -8
- data/lib/rubyn_code/context/manual_compact.rb +1 -1
- data/lib/rubyn_code/context/micro_compact.rb +29 -19
- data/lib/rubyn_code/context/schema_filter.rb +64 -0
- data/lib/rubyn_code/db/connection.rb +31 -26
- data/lib/rubyn_code/db/migrator.rb +44 -28
- data/lib/rubyn_code/hooks/built_in.rb +14 -10
- data/lib/rubyn_code/hooks/registry.rb +4 -0
- data/lib/rubyn_code/ide/adapters/tool_output.rb +330 -0
- data/lib/rubyn_code/ide/client.rb +110 -0
- data/lib/rubyn_code/ide/handlers/accept_edit_handler.rb +35 -0
- data/lib/rubyn_code/ide/handlers/approve_tool_use_handler.rb +34 -0
- data/lib/rubyn_code/ide/handlers/cancel_handler.rb +41 -0
- data/lib/rubyn_code/ide/handlers/config_get_handler.rb +63 -0
- data/lib/rubyn_code/ide/handlers/config_set_handler.rb +86 -0
- data/lib/rubyn_code/ide/handlers/initialize_handler.rb +79 -0
- data/lib/rubyn_code/ide/handlers/models_list_handler.rb +39 -0
- data/lib/rubyn_code/ide/handlers/prompt_handler.rb +215 -0
- data/lib/rubyn_code/ide/handlers/review_handler.rb +110 -0
- data/lib/rubyn_code/ide/handlers/session_fork_handler.rb +49 -0
- data/lib/rubyn_code/ide/handlers/session_list_handler.rb +41 -0
- data/lib/rubyn_code/ide/handlers/session_reset_handler.rb +31 -0
- data/lib/rubyn_code/ide/handlers/session_resume_handler.rb +42 -0
- data/lib/rubyn_code/ide/handlers/shutdown_handler.rb +37 -0
- data/lib/rubyn_code/ide/handlers.rb +76 -0
- data/lib/rubyn_code/ide/protocol.rb +111 -0
- data/lib/rubyn_code/ide/server.rb +186 -0
- data/lib/rubyn_code/index/codebase_index.rb +311 -0
- data/lib/rubyn_code/learning/extractor.rb +65 -82
- data/lib/rubyn_code/learning/injector.rb +22 -23
- data/lib/rubyn_code/learning/instinct.rb +71 -42
- data/lib/rubyn_code/learning/shortcut.rb +95 -0
- data/lib/rubyn_code/llm/adapters/anthropic.rb +274 -0
- data/lib/rubyn_code/llm/adapters/anthropic_compatible.rb +60 -0
- data/lib/rubyn_code/llm/adapters/anthropic_streaming.rb +215 -0
- data/lib/rubyn_code/llm/adapters/base.rb +35 -0
- data/lib/rubyn_code/llm/adapters/json_parsing.rb +21 -0
- data/lib/rubyn_code/llm/adapters/openai.rb +246 -0
- data/lib/rubyn_code/llm/adapters/openai_compatible.rb +50 -0
- data/lib/rubyn_code/llm/adapters/openai_message_translator.rb +90 -0
- data/lib/rubyn_code/llm/adapters/openai_streaming.rb +141 -0
- data/lib/rubyn_code/llm/adapters/prompt_caching.rb +60 -0
- data/lib/rubyn_code/llm/client.rb +75 -247
- data/lib/rubyn_code/llm/model_router.rb +237 -0
- data/lib/rubyn_code/llm/streaming.rb +4 -227
- data/lib/rubyn_code/mcp/client.rb +1 -1
- data/lib/rubyn_code/mcp/config.rb +10 -12
- data/lib/rubyn_code/mcp/sse_transport.rb +15 -13
- data/lib/rubyn_code/mcp/stdio_transport.rb +16 -18
- data/lib/rubyn_code/mcp/tool_bridge.rb +31 -62
- data/lib/rubyn_code/memory/search.rb +1 -0
- data/lib/rubyn_code/memory/session_persistence.rb +59 -58
- data/lib/rubyn_code/memory/store.rb +42 -55
- data/lib/rubyn_code/observability/budget_enforcer.rb +46 -32
- data/lib/rubyn_code/observability/cost_calculator.rb +32 -8
- data/lib/rubyn_code/observability/skill_analytics.rb +116 -0
- data/lib/rubyn_code/observability/token_analytics.rb +130 -0
- data/lib/rubyn_code/observability/usage_reporter.rb +79 -61
- data/lib/rubyn_code/output/diff_renderer.rb +102 -77
- data/lib/rubyn_code/output/formatter.rb +11 -11
- data/lib/rubyn_code/permissions/policy.rb +11 -13
- data/lib/rubyn_code/permissions/prompter.rb +8 -9
- data/lib/rubyn_code/protocols/plan_approval.rb +25 -20
- data/lib/rubyn_code/self_test.rb +315 -0
- data/lib/rubyn_code/skills/catalog.rb +66 -0
- data/lib/rubyn_code/skills/document.rb +33 -29
- data/lib/rubyn_code/skills/loader.rb +43 -0
- data/lib/rubyn_code/skills/ttl_manager.rb +100 -0
- data/lib/rubyn_code/sub_agents/runner.rb +20 -25
- data/lib/rubyn_code/tasks/dag.rb +25 -24
- data/lib/rubyn_code/tasks/models.rb +1 -0
- data/lib/rubyn_code/tools/ask_user.rb +44 -0
- data/lib/rubyn_code/tools/background_run.rb +2 -1
- data/lib/rubyn_code/tools/base.rb +39 -32
- data/lib/rubyn_code/tools/bash.rb +7 -1
- data/lib/rubyn_code/tools/edit_file.rb +130 -17
- data/lib/rubyn_code/tools/executor.rb +130 -25
- data/lib/rubyn_code/tools/file_cache.rb +95 -0
- data/lib/rubyn_code/tools/git_commit.rb +12 -10
- data/lib/rubyn_code/tools/git_log.rb +12 -10
- data/lib/rubyn_code/tools/glob.rb +29 -7
- data/lib/rubyn_code/tools/grep.rb +8 -1
- data/lib/rubyn_code/tools/ide_diagnostics.rb +51 -0
- data/lib/rubyn_code/tools/ide_symbols.rb +53 -0
- data/lib/rubyn_code/tools/load_skill.rb +13 -6
- data/lib/rubyn_code/tools/memory_search.rb +14 -13
- data/lib/rubyn_code/tools/memory_write.rb +2 -1
- data/lib/rubyn_code/tools/output_compressor.rb +190 -0
- data/lib/rubyn_code/tools/read_file.rb +17 -6
- data/lib/rubyn_code/tools/registry.rb +11 -0
- data/lib/rubyn_code/tools/review_pr.rb +127 -80
- data/lib/rubyn_code/tools/run_specs.rb +26 -15
- data/lib/rubyn_code/tools/schema.rb +4 -10
- data/lib/rubyn_code/tools/spawn_agent.rb +113 -82
- data/lib/rubyn_code/tools/spawn_teammate.rb +107 -64
- data/lib/rubyn_code/tools/spec_output_parser.rb +118 -0
- data/lib/rubyn_code/tools/task.rb +17 -17
- data/lib/rubyn_code/tools/web_fetch.rb +62 -47
- data/lib/rubyn_code/tools/web_search.rb +66 -48
- data/lib/rubyn_code/tools/write_file.rb +76 -1
- data/lib/rubyn_code/version.rb +1 -1
- data/lib/rubyn_code.rb +62 -1
- data/skills/rubyn_self_test.md +133 -0
- metadata +83 -1
|
@@ -68,7 +68,7 @@ module RubynCode
|
|
|
68
68
|
]
|
|
69
69
|
|
|
70
70
|
options = {}
|
|
71
|
-
options[:model] = 'claude-sonnet-4-
|
|
71
|
+
options[:model] = 'claude-sonnet-4-6' if llm_client.respond_to?(:chat)
|
|
72
72
|
|
|
73
73
|
response = llm_client.chat(messages: summary_messages, **options)
|
|
74
74
|
|
|
@@ -22,22 +22,27 @@ module RubynCode
|
|
|
22
22
|
|
|
23
23
|
tool_name_index = build_tool_name_index(messages)
|
|
24
24
|
candidates = tool_result_refs[0..-(keep_recent + 1)]
|
|
25
|
-
|
|
25
|
+
compact_candidates(candidates, tool_name_index, preserve_tools)
|
|
26
|
+
end
|
|
26
27
|
|
|
28
|
+
def self.compact_candidates(candidates, tool_name_index, preserve_tools)
|
|
29
|
+
compacted = 0
|
|
27
30
|
candidates.each do |ref|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
compacted += 1 if compact_single_ref(ref, tool_name_index, preserve_tools)
|
|
32
|
+
end
|
|
33
|
+
compacted
|
|
34
|
+
end
|
|
31
35
|
|
|
32
|
-
|
|
33
|
-
|
|
36
|
+
def self.compact_single_ref(ref, tool_name_index, preserve_tools) # rubocop:disable Naming/PredicateMethod -- returns boolean but is an action method
|
|
37
|
+
block = ref[:block]
|
|
38
|
+
content = extract_content(block)
|
|
39
|
+
return false if content.nil? || content.length < MIN_CONTENT_LENGTH
|
|
34
40
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
compacted += 1
|
|
38
|
-
end
|
|
41
|
+
tool_name = resolve_tool_name(block, tool_name_index)
|
|
42
|
+
return false if preserve_tools.include?(tool_name)
|
|
39
43
|
|
|
40
|
-
|
|
44
|
+
replace_content!(block, format(PLACEHOLDER_TEMPLATE, tool_name: tool_name || 'tool'))
|
|
45
|
+
true
|
|
41
46
|
end
|
|
42
47
|
|
|
43
48
|
# Collects all tool_result content blocks across user messages, preserving
|
|
@@ -70,19 +75,23 @@ module RubynCode
|
|
|
70
75
|
messages.each do |msg|
|
|
71
76
|
next unless msg[:role] == 'assistant' && msg[:content].is_a?(Array)
|
|
72
77
|
|
|
73
|
-
msg[:content].each
|
|
74
|
-
case block
|
|
75
|
-
when Hash
|
|
76
|
-
index[block[:id] || block['id']] = block[:name] || block['name'] if block_type(block) == 'tool_use'
|
|
77
|
-
when LLM::ToolUseBlock
|
|
78
|
-
index[block.id] = block.name
|
|
79
|
-
end
|
|
80
|
-
end
|
|
78
|
+
msg[:content].each { |block| index_tool_use(index, block) }
|
|
81
79
|
end
|
|
82
80
|
|
|
83
81
|
index
|
|
84
82
|
end
|
|
85
83
|
|
|
84
|
+
def self.index_tool_use(index, block)
|
|
85
|
+
case block
|
|
86
|
+
when Hash
|
|
87
|
+
return unless block_type(block) == 'tool_use'
|
|
88
|
+
|
|
89
|
+
index[block[:id] || block['id']] = block[:name] || block['name']
|
|
90
|
+
when LLM::ToolUseBlock
|
|
91
|
+
index[block.id] = block.name
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
86
95
|
def self.tool_result_block?(block)
|
|
87
96
|
case block
|
|
88
97
|
when Hash
|
|
@@ -128,6 +137,7 @@ module RubynCode
|
|
|
128
137
|
end
|
|
129
138
|
|
|
130
139
|
private_class_method :collect_tool_results, :build_tool_name_index,
|
|
140
|
+
:index_tool_use, :compact_candidates, :compact_single_ref,
|
|
131
141
|
:tool_result_block?, :block_type, :extract_content,
|
|
132
142
|
:resolve_tool_name, :replace_content!
|
|
133
143
|
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubynCode
|
|
4
|
+
module Context
|
|
5
|
+
# Extracts only the relevant table definitions from db/schema.rb
|
|
6
|
+
# based on which models are currently in context. Loading the full
|
|
7
|
+
# schema for a large Rails app can be 5-10K tokens; filtering to
|
|
8
|
+
# relevant tables typically reduces this to 200-500 tokens.
|
|
9
|
+
module SchemaFilter
|
|
10
|
+
TABLE_PATTERN = /create_table\s+"([^"]+)"/
|
|
11
|
+
END_PATTERN = /\A\s+end\s*\z/
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
# Returns schema definitions for only the specified table names.
|
|
15
|
+
#
|
|
16
|
+
# @param schema_path [String] path to db/schema.rb
|
|
17
|
+
# @param table_names [Array<String>] table names to include
|
|
18
|
+
# @return [String] filtered schema content
|
|
19
|
+
def filter(schema_path, table_names:)
|
|
20
|
+
return '' if table_names.empty?
|
|
21
|
+
return '' unless File.exist?(schema_path)
|
|
22
|
+
|
|
23
|
+
lines = File.readlines(schema_path)
|
|
24
|
+
extract_tables(lines, table_names.to_set(&:to_s))
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Derives table names from model class names using Rails conventions.
|
|
28
|
+
#
|
|
29
|
+
# @param model_names [Array<String>] e.g., ["User", "OrderItem"]
|
|
30
|
+
# @return [Array<String>] e.g., ["users", "order_items"]
|
|
31
|
+
def tableize(model_names)
|
|
32
|
+
model_names.map { |name| "#{name.gsub(/([a-z])([A-Z])/, '\1_\2').downcase}s" }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Convenience: filter schema by model names instead of table names.
|
|
36
|
+
def filter_for_models(schema_path, model_names:)
|
|
37
|
+
tables = tableize(model_names)
|
|
38
|
+
filter(schema_path, table_names: tables)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def extract_tables(lines, table_set)
|
|
44
|
+
result = []
|
|
45
|
+
capturing = false
|
|
46
|
+
|
|
47
|
+
lines.each do |line|
|
|
48
|
+
match = TABLE_PATTERN.match(line)
|
|
49
|
+
capturing = true if match && table_set.include?(match[1])
|
|
50
|
+
|
|
51
|
+
result << line if capturing
|
|
52
|
+
|
|
53
|
+
if capturing && END_PATTERN.match?(line)
|
|
54
|
+
capturing = false
|
|
55
|
+
result << "\n"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
result.join
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -106,34 +106,11 @@ module RubynCode
|
|
|
106
106
|
#
|
|
107
107
|
# @yield the block to execute
|
|
108
108
|
# @return [Object] the block's return value
|
|
109
|
-
def transaction
|
|
109
|
+
def transaction(&block)
|
|
110
110
|
synchronize do
|
|
111
|
-
|
|
112
|
-
begin_top_level_transaction
|
|
113
|
-
else
|
|
114
|
-
begin_savepoint
|
|
115
|
-
end
|
|
116
|
-
|
|
111
|
+
@transaction_depth.zero? ? begin_top_level_transaction : begin_savepoint
|
|
117
112
|
@transaction_depth += 1
|
|
118
|
-
|
|
119
|
-
result = yield
|
|
120
|
-
if @transaction_depth == 1
|
|
121
|
-
@db.execute('COMMIT')
|
|
122
|
-
else
|
|
123
|
-
@db.execute("RELEASE SAVEPOINT sp_#{@transaction_depth}")
|
|
124
|
-
end
|
|
125
|
-
result
|
|
126
|
-
rescue StandardError => e
|
|
127
|
-
if @transaction_depth == 1
|
|
128
|
-
@db.execute('ROLLBACK')
|
|
129
|
-
else
|
|
130
|
-
@db.execute("ROLLBACK TO SAVEPOINT sp_#{@transaction_depth}")
|
|
131
|
-
@db.execute("RELEASE SAVEPOINT sp_#{@transaction_depth}")
|
|
132
|
-
end
|
|
133
|
-
raise e
|
|
134
|
-
ensure
|
|
135
|
-
@transaction_depth -= 1
|
|
136
|
-
end
|
|
113
|
+
execute_transaction_body(&block)
|
|
137
114
|
end
|
|
138
115
|
end
|
|
139
116
|
|
|
@@ -171,6 +148,34 @@ module RubynCode
|
|
|
171
148
|
def begin_savepoint
|
|
172
149
|
@db.execute("SAVEPOINT sp_#{@transaction_depth + 1}")
|
|
173
150
|
end
|
|
151
|
+
|
|
152
|
+
def execute_transaction_body
|
|
153
|
+
result = yield
|
|
154
|
+
commit_or_release
|
|
155
|
+
result
|
|
156
|
+
rescue StandardError => e
|
|
157
|
+
rollback_or_release
|
|
158
|
+
raise e
|
|
159
|
+
ensure
|
|
160
|
+
@transaction_depth -= 1
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def commit_or_release
|
|
164
|
+
if @transaction_depth == 1
|
|
165
|
+
@db.execute('COMMIT')
|
|
166
|
+
else
|
|
167
|
+
@db.execute("RELEASE SAVEPOINT sp_#{@transaction_depth}")
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def rollback_or_release
|
|
172
|
+
if @transaction_depth == 1
|
|
173
|
+
@db.execute('ROLLBACK')
|
|
174
|
+
else
|
|
175
|
+
@db.execute("ROLLBACK TO SAVEPOINT sp_#{@transaction_depth}")
|
|
176
|
+
@db.execute("RELEASE SAVEPOINT sp_#{@transaction_depth}")
|
|
177
|
+
end
|
|
178
|
+
end
|
|
174
179
|
end
|
|
175
180
|
end
|
|
176
181
|
end
|
|
@@ -38,7 +38,7 @@ module RubynCode
|
|
|
38
38
|
# @return [Array<Array(Integer, String)>] pairs of [version, file_path]
|
|
39
39
|
def pending_migrations
|
|
40
40
|
applied = applied_versions
|
|
41
|
-
available_migrations.reject { |version, _| applied.include?(version) }
|
|
41
|
+
available_migrations.reject { |version, _| applied.include?(version) } # rubocop:disable Style/HashExcept
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
# Returns the set of already-applied migration versions.
|
|
@@ -67,16 +67,16 @@ module RubynCode
|
|
|
67
67
|
def available_migrations
|
|
68
68
|
all = Dir.glob(File.join(MIGRATIONS_DIR, '*'))
|
|
69
69
|
.select { |path| path.end_with?('.sql', '.rb') }
|
|
70
|
-
.
|
|
71
|
-
.compact
|
|
70
|
+
.filter_map { |path| parse_migration_file(path) }
|
|
72
71
|
|
|
73
|
-
|
|
72
|
+
deduplicate_migrations(all)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def deduplicate_migrations(all)
|
|
74
76
|
by_version = {}
|
|
75
77
|
all.each do |version, path|
|
|
76
|
-
|
|
77
|
-
by_version[version] = [version, path] if existing.nil? || path.end_with?('.rb')
|
|
78
|
+
by_version[version] = [version, path] if !by_version[version] || path.end_with?('.rb')
|
|
78
79
|
end
|
|
79
|
-
|
|
80
80
|
by_version.values.sort_by(&:first)
|
|
81
81
|
end
|
|
82
82
|
|
|
@@ -139,33 +139,49 @@ module RubynCode
|
|
|
139
139
|
in_block = false
|
|
140
140
|
|
|
141
141
|
sql.each_line do |line|
|
|
142
|
-
|
|
142
|
+
in_block, current = process_sql_line(line, statements, current, in_block)
|
|
143
|
+
end
|
|
143
144
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
end
|
|
145
|
+
finalize_statements(statements, current)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def process_sql_line(line, statements, current, in_block)
|
|
149
|
+
stripped = line.strip
|
|
150
|
+
in_block = true if begin_block?(stripped)
|
|
151
|
+
current << line
|
|
152
|
+
|
|
153
|
+
if in_block && stripped.match?(/\bEND\b\s*;?\s*$/i)
|
|
154
|
+
statements << current.strip.chomp(';')
|
|
155
|
+
[false, +'']
|
|
156
|
+
elsif !in_block && stripped.end_with?(';')
|
|
157
|
+
append_statement(statements, current)
|
|
158
|
+
[false, +'']
|
|
159
|
+
else
|
|
160
|
+
[in_block, current]
|
|
161
161
|
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def begin_block?(stripped)
|
|
165
|
+
stripped.match?(/\bBEGIN\b/i) &&
|
|
166
|
+
!stripped.match?(/\ABEGIN\s+(IMMEDIATE|DEFERRED|EXCLUSIVE)/i)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def append_statement(statements, current)
|
|
170
|
+
stmt = current.strip.chomp(';').strip
|
|
171
|
+
return if stmt.empty? || (stmt.match?(/\A\s*--/) && !stmt.include?("\n"))
|
|
162
172
|
|
|
163
|
-
|
|
173
|
+
statements << stmt
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def finalize_statements(statements, current)
|
|
164
177
|
remainder = current.strip.chomp(';').strip
|
|
165
178
|
statements << remainder unless remainder.empty?
|
|
166
179
|
|
|
167
|
-
|
|
168
|
-
|
|
180
|
+
statements.reject { |s| comment_only?(s) }
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def comment_only?(stmt)
|
|
184
|
+
stmt.lines.all? { |l| l.strip.empty? || l.strip.start_with?('--') }
|
|
169
185
|
end
|
|
170
186
|
|
|
171
187
|
# Extracts the version number and name from a migration filename.
|
|
@@ -25,20 +25,24 @@ module RubynCode
|
|
|
25
25
|
usage = response[:usage] || response['usage']
|
|
26
26
|
return unless usage
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
cache_write = usage[:cache_creation_input_tokens] || usage['cache_creation_input_tokens'] || 0
|
|
28
|
+
record_usage(response, usage)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
33
32
|
|
|
33
|
+
def record_usage(response, usage)
|
|
34
34
|
@budget_enforcer.record!(
|
|
35
|
-
model: model,
|
|
36
|
-
input_tokens: input_tokens,
|
|
37
|
-
output_tokens: output_tokens,
|
|
38
|
-
cache_read_tokens:
|
|
39
|
-
cache_write_tokens:
|
|
35
|
+
model: fetch_value(response, :model, 'unknown'),
|
|
36
|
+
input_tokens: fetch_value(usage, :input_tokens, 0),
|
|
37
|
+
output_tokens: fetch_value(usage, :output_tokens, 0),
|
|
38
|
+
cache_read_tokens: fetch_value(usage, :cache_read_input_tokens, 0),
|
|
39
|
+
cache_write_tokens: fetch_value(usage, :cache_creation_input_tokens, 0)
|
|
40
40
|
)
|
|
41
41
|
end
|
|
42
|
+
|
|
43
|
+
def fetch_value(hash, sym_key, default)
|
|
44
|
+
hash[sym_key] || hash[sym_key.to_s] || default
|
|
45
|
+
end
|
|
42
46
|
end
|
|
43
47
|
|
|
44
48
|
# Logs tool calls and their results via the formatter.
|