anima-core 0.3.0 → 1.0.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/.reek.yml +27 -1
- data/CHANGELOG.md +4 -0
- data/README.md +219 -25
- data/agents/codebase-analyzer.md +88 -0
- data/agents/codebase-pattern-finder.md +83 -0
- data/agents/documentation-researcher.md +59 -0
- data/agents/thoughts-analyzer.md +102 -0
- data/agents/web-search-researcher.md +71 -0
- data/anima-core.gemspec +3 -0
- data/app/channels/session_channel.rb +76 -28
- data/app/jobs/agent_request_job.rb +24 -0
- data/app/jobs/analytical_brain_job.rb +33 -0
- data/app/jobs/count_event_tokens_job.rb +1 -1
- data/app/models/concerns/event/broadcasting.rb +20 -2
- data/app/models/event.rb +1 -1
- data/app/models/goal.rb +91 -0
- data/app/models/session.rb +347 -22
- data/config/application.rb +2 -0
- data/db/migrate/20260314075248_add_subagent_support_to_sessions.rb +6 -0
- data/db/migrate/20260314112417_add_granted_tools_to_sessions.rb +5 -0
- data/db/migrate/20260314140000_add_name_to_sessions.rb +7 -0
- data/db/migrate/20260314150000_add_viewport_event_ids_to_sessions.rb +7 -0
- data/db/migrate/20260315100000_add_active_skills_to_sessions.rb +7 -0
- data/db/migrate/20260315140843_create_goals.rb +16 -0
- data/db/migrate/20260315144837_add_completed_at_to_goals.rb +5 -0
- data/db/migrate/20260315191105_add_active_workflow_to_sessions.rb +5 -0
- data/lib/agent_loop.rb +65 -9
- data/lib/agents/definition.rb +116 -0
- data/lib/agents/registry.rb +106 -0
- data/lib/analytical_brain/runner.rb +276 -0
- data/lib/analytical_brain/tools/activate_skill.rb +52 -0
- data/lib/analytical_brain/tools/deactivate_skill.rb +43 -0
- data/lib/analytical_brain/tools/deactivate_workflow.rb +34 -0
- data/lib/analytical_brain/tools/everything_is_ready.rb +28 -0
- data/lib/analytical_brain/tools/finish_goal.rb +62 -0
- data/lib/analytical_brain/tools/read_workflow.rb +58 -0
- data/lib/analytical_brain/tools/rename_session.rb +63 -0
- data/lib/analytical_brain/tools/set_goal.rb +60 -0
- data/lib/analytical_brain/tools/update_goal.rb +60 -0
- data/lib/analytical_brain.rb +23 -0
- data/lib/anima/cli/mcp/secrets.rb +76 -0
- data/lib/anima/cli/mcp.rb +197 -0
- data/lib/anima/cli.rb +4 -0
- data/lib/anima/installer.rb +168 -0
- data/lib/anima/settings.rb +226 -0
- data/lib/anima/version.rb +1 -1
- data/lib/anima.rb +9 -0
- data/lib/credential_store.rb +103 -0
- data/lib/environment_probe.rb +232 -0
- data/lib/llm/client.rb +29 -10
- data/lib/mcp/client_manager.rb +86 -0
- data/lib/mcp/config.rb +213 -0
- data/lib/mcp/health_check.rb +77 -0
- data/lib/mcp/secrets.rb +73 -0
- data/lib/mcp/stdio_transport.rb +206 -0
- data/lib/providers/anthropic.rb +8 -7
- data/lib/shell_session.rb +11 -10
- data/lib/skills/definition.rb +97 -0
- data/lib/skills/registry.rb +105 -0
- data/lib/tools/edit.rb +3 -4
- data/lib/tools/mcp_tool.rb +114 -0
- data/lib/tools/read.rb +15 -16
- data/lib/tools/registry.rb +14 -12
- data/lib/tools/request_feature.rb +121 -0
- data/lib/tools/return_result.rb +81 -0
- data/lib/tools/spawn_specialist.rb +109 -0
- data/lib/tools/spawn_subagent.rb +111 -0
- data/lib/tools/subagent_prompts.rb +12 -0
- data/lib/tools/web_get.rb +8 -9
- data/lib/tui/app.rb +332 -43
- data/lib/tui/message_store.rb +20 -0
- data/lib/tui/screens/chat.rb +207 -20
- data/lib/workflows/definition.rb +97 -0
- data/lib/workflows/registry.rb +89 -0
- data/skills/activerecord/SKILL.md +255 -0
- data/skills/activerecord/examples/associations/association_extensions.rb +298 -0
- data/skills/activerecord/examples/associations/basic_associations.rb +118 -0
- data/skills/activerecord/examples/associations/counter_caches.rb +215 -0
- data/skills/activerecord/examples/associations/polymorphic_associations.rb +217 -0
- data/skills/activerecord/examples/associations/self_referential.rb +302 -0
- data/skills/activerecord/examples/associations/through_associations.rb +203 -0
- data/skills/activerecord/examples/basics/crud_operations.rb +209 -0
- data/skills/activerecord/examples/basics/dirty_tracking.rb +218 -0
- data/skills/activerecord/examples/basics/inheritance.rb +377 -0
- data/skills/activerecord/examples/basics/type_casting.rb +317 -0
- data/skills/activerecord/examples/callbacks/alternatives_to_callbacks.rb +447 -0
- data/skills/activerecord/examples/callbacks/conditional_callbacks.rb +353 -0
- data/skills/activerecord/examples/callbacks/lifecycle_callbacks.rb +280 -0
- data/skills/activerecord/examples/callbacks/transaction_callbacks.rb +340 -0
- data/skills/activerecord/examples/migrations/indexes_and_constraints.rb +337 -0
- data/skills/activerecord/examples/migrations/reversible_patterns.rb +403 -0
- data/skills/activerecord/examples/migrations/safe_patterns.rb +420 -0
- data/skills/activerecord/examples/migrations/schema_changes.rb +277 -0
- data/skills/activerecord/examples/querying/batch_processing.rb +226 -0
- data/skills/activerecord/examples/querying/eager_loading.rb +259 -0
- data/skills/activerecord/examples/querying/finder_methods.rb +170 -0
- data/skills/activerecord/examples/querying/optimization.rb +275 -0
- data/skills/activerecord/examples/querying/scopes.rb +260 -0
- data/skills/activerecord/examples/validations/built_in_validators.rb +277 -0
- data/skills/activerecord/examples/validations/conditional_validations.rb +288 -0
- data/skills/activerecord/examples/validations/custom_validators.rb +381 -0
- data/skills/activerecord/examples/validations/database_constraints.rb +432 -0
- data/skills/activerecord/examples/validations/validation_contexts.rb +367 -0
- data/skills/activerecord/references/associations.md +709 -0
- data/skills/activerecord/references/basics.md +622 -0
- data/skills/activerecord/references/callbacks.md +738 -0
- data/skills/activerecord/references/migrations.md +657 -0
- data/skills/activerecord/references/querying.md +655 -0
- data/skills/activerecord/references/validations.md +596 -0
- data/skills/dragonruby/SKILL.md +250 -0
- data/skills/dragonruby/examples/audio/audio_events.rb +55 -0
- data/skills/dragonruby/examples/audio/background_music.rb +29 -0
- data/skills/dragonruby/examples/audio/crossfade.rb +51 -0
- data/skills/dragonruby/examples/audio/music_controls.rb +51 -0
- data/skills/dragonruby/examples/audio/sound_effects.rb +30 -0
- data/skills/dragonruby/examples/core/coordinate_system.rb +27 -0
- data/skills/dragonruby/examples/core/hello_world.rb +24 -0
- data/skills/dragonruby/examples/core/labels.rb +22 -0
- data/skills/dragonruby/examples/core/sprites.rb +35 -0
- data/skills/dragonruby/examples/core/state_management.rb +29 -0
- data/skills/dragonruby/examples/distribution/background_pause.rb +42 -0
- data/skills/dragonruby/examples/distribution/build_workflow.sh +26 -0
- data/skills/dragonruby/examples/distribution/cvars_production.txt +16 -0
- data/skills/dragonruby/examples/distribution/game_metadata_hd.txt +23 -0
- data/skills/dragonruby/examples/distribution/game_metadata_minimal.txt +9 -0
- data/skills/dragonruby/examples/distribution/game_metadata_mobile.txt +31 -0
- data/skills/dragonruby/examples/distribution/platform_detection.rb +36 -0
- data/skills/dragonruby/examples/distribution/steam_metadata.txt +19 -0
- data/skills/dragonruby/examples/entities/collision_detection.rb +43 -0
- data/skills/dragonruby/examples/entities/entity_lifecycle.rb +68 -0
- data/skills/dragonruby/examples/entities/entity_storage.rb +38 -0
- data/skills/dragonruby/examples/entities/factory_methods.rb +45 -0
- data/skills/dragonruby/examples/entities/random_spawning.rb +50 -0
- data/skills/dragonruby/examples/game-logic/reset_patterns.rb +98 -0
- data/skills/dragonruby/examples/game-logic/save_load.rb +101 -0
- data/skills/dragonruby/examples/game-logic/scoring.rb +104 -0
- data/skills/dragonruby/examples/game-logic/state_transitions.rb +103 -0
- data/skills/dragonruby/examples/game-logic/timers.rb +87 -0
- data/skills/dragonruby/examples/input/action_triggers.rb +36 -0
- data/skills/dragonruby/examples/input/analog_movement.rb +28 -0
- data/skills/dragonruby/examples/input/controller_input.rb +28 -0
- data/skills/dragonruby/examples/input/directional_input.rb +24 -0
- data/skills/dragonruby/examples/input/keyboard_input.rb +28 -0
- data/skills/dragonruby/examples/input/mouse_click.rb +26 -0
- data/skills/dragonruby/examples/input/movement_with_bounds.rb +22 -0
- data/skills/dragonruby/examples/input/normalized_movement.rb +32 -0
- data/skills/dragonruby/examples/rendering/frame_animation.rb +32 -0
- data/skills/dragonruby/examples/rendering/labels.rb +32 -0
- data/skills/dragonruby/examples/rendering/layering.rb +51 -0
- data/skills/dragonruby/examples/rendering/solids.rb +61 -0
- data/skills/dragonruby/examples/rendering/sprites.rb +33 -0
- data/skills/dragonruby/examples/rendering/spritesheet_animation.rb +39 -0
- data/skills/dragonruby/examples/scenes/case_dispatch.rb +60 -0
- data/skills/dragonruby/examples/scenes/class_based.rb +150 -0
- data/skills/dragonruby/examples/scenes/pause_overlay.rb +100 -0
- data/skills/dragonruby/examples/scenes/safe_transitions.rb +68 -0
- data/skills/dragonruby/examples/scenes/scene_transitions.rb +98 -0
- data/skills/dragonruby/examples/scenes/send_dispatch.rb +88 -0
- data/skills/dragonruby/references/audio.md +396 -0
- data/skills/dragonruby/references/core.md +385 -0
- data/skills/dragonruby/references/distribution.md +434 -0
- data/skills/dragonruby/references/entities.md +516 -0
- data/skills/dragonruby/references/game-logic/persistence.md +386 -0
- data/skills/dragonruby/references/game-logic/state.md +389 -0
- data/skills/dragonruby/references/input.md +414 -0
- data/skills/dragonruby/references/rendering/animation.md +467 -0
- data/skills/dragonruby/references/rendering/primitives.md +403 -0
- data/skills/dragonruby/references/scenes.md +443 -0
- data/skills/draper-decorators/SKILL.md +344 -0
- data/skills/draper-decorators/examples/application_decorator.rb +61 -0
- data/skills/draper-decorators/examples/decorator_spec.rb +253 -0
- data/skills/draper-decorators/examples/model_decorator.rb +152 -0
- data/skills/draper-decorators/references/anti-patterns.md +640 -0
- data/skills/draper-decorators/references/patterns.md +507 -0
- data/skills/draper-decorators/references/testing.md +559 -0
- data/skills/gh-issue.md +182 -0
- data/skills/mcp-server/SKILL.md +177 -0
- data/skills/mcp-server/examples/dynamic_tools.rb +36 -0
- data/skills/mcp-server/examples/file_manager_tool.rb +85 -0
- data/skills/mcp-server/examples/http_client.rb +48 -0
- data/skills/mcp-server/examples/http_server.rb +97 -0
- data/skills/mcp-server/examples/rails_integration.rb +88 -0
- data/skills/mcp-server/examples/stdio_server.rb +108 -0
- data/skills/mcp-server/examples/streaming_client.rb +95 -0
- data/skills/mcp-server/references/gotchas.md +183 -0
- data/skills/mcp-server/references/prompts.md +98 -0
- data/skills/mcp-server/references/resources.md +53 -0
- data/skills/mcp-server/references/server.md +140 -0
- data/skills/mcp-server/references/tools.md +146 -0
- data/skills/mcp-server/references/transport.md +104 -0
- data/skills/ratatui-ruby/SKILL.md +315 -0
- data/skills/ratatui-ruby/references/core-concepts.md +340 -0
- data/skills/ratatui-ruby/references/events.md +387 -0
- data/skills/ratatui-ruby/references/frameworks.md +522 -0
- data/skills/ratatui-ruby/references/layout.md +423 -0
- data/skills/ratatui-ruby/references/styling.md +268 -0
- data/skills/ratatui-ruby/references/testing.md +433 -0
- data/skills/ratatui-ruby/references/widgets.md +532 -0
- data/skills/rspec/SKILL.md +340 -0
- data/skills/rspec/examples/core/basic_structure.rb +69 -0
- data/skills/rspec/examples/core/configuration.rb +126 -0
- data/skills/rspec/examples/core/hooks.rb +126 -0
- data/skills/rspec/examples/core/memoized_helpers.rb +139 -0
- data/skills/rspec/examples/core/metadata_filtering.rb +144 -0
- data/skills/rspec/examples/core/shared_examples.rb +145 -0
- data/skills/rspec/examples/factory_bot/associations.rb +314 -0
- data/skills/rspec/examples/factory_bot/build_strategies.rb +272 -0
- data/skills/rspec/examples/factory_bot/callbacks.rb +320 -0
- data/skills/rspec/examples/factory_bot/custom_construction.rb +328 -0
- data/skills/rspec/examples/factory_bot/factory_definition.rb +191 -0
- data/skills/rspec/examples/factory_bot/inheritance.rb +314 -0
- data/skills/rspec/examples/factory_bot/traits.rb +293 -0
- data/skills/rspec/examples/factory_bot/transients.rb +229 -0
- data/skills/rspec/examples/matchers/change.rb +115 -0
- data/skills/rspec/examples/matchers/collections.rb +154 -0
- data/skills/rspec/examples/matchers/comparisons.rb +79 -0
- data/skills/rspec/examples/matchers/composing.rb +155 -0
- data/skills/rspec/examples/matchers/custom_matchers.rb +197 -0
- data/skills/rspec/examples/matchers/equality.rb +58 -0
- data/skills/rspec/examples/matchers/errors.rb +136 -0
- data/skills/rspec/examples/matchers/output.rb +103 -0
- data/skills/rspec/examples/matchers/predicates.rb +87 -0
- data/skills/rspec/examples/matchers/truthiness.rb +101 -0
- data/skills/rspec/examples/matchers/types.rb +82 -0
- data/skills/rspec/examples/matchers/yield.rb +147 -0
- data/skills/rspec/examples/mocks/any_instance.rb +172 -0
- data/skills/rspec/examples/mocks/argument_matchers.rb +206 -0
- data/skills/rspec/examples/mocks/constants.rb +177 -0
- data/skills/rspec/examples/mocks/doubles.rb +139 -0
- data/skills/rspec/examples/mocks/expectations.rb +137 -0
- data/skills/rspec/examples/mocks/message_chains.rb +173 -0
- data/skills/rspec/examples/mocks/ordering.rb +144 -0
- data/skills/rspec/examples/mocks/receive_counts.rb +181 -0
- data/skills/rspec/examples/mocks/responses.rb +223 -0
- data/skills/rspec/examples/mocks/spies.rb +149 -0
- data/skills/rspec/examples/mocks/stubbing.rb +133 -0
- data/skills/rspec/examples/rails/channels.rb +250 -0
- data/skills/rspec/examples/rails/controller_specs.rb +302 -0
- data/skills/rspec/examples/rails/helper_specs.rb +245 -0
- data/skills/rspec/examples/rails/job_specs.rb +256 -0
- data/skills/rspec/examples/rails/mailer_specs.rb +228 -0
- data/skills/rspec/examples/rails/matchers.rb +374 -0
- data/skills/rspec/examples/rails/model_specs.rb +193 -0
- data/skills/rspec/examples/rails/request_specs.rb +275 -0
- data/skills/rspec/examples/rails/routing_specs.rb +276 -0
- data/skills/rspec/examples/rails/system_specs.rb +294 -0
- data/skills/rspec/examples/rails/transactions.rb +254 -0
- data/skills/rspec/examples/rails/view_specs.rb +252 -0
- data/skills/rspec/references/core.md +816 -0
- data/skills/rspec/references/factory_bot.md +641 -0
- data/skills/rspec/references/matchers.md +516 -0
- data/skills/rspec/references/mocks.md +381 -0
- data/skills/rspec/references/rails.md +528 -0
- data/templates/soul.md +40 -0
- data/workflows/commit.md +45 -0
- data/workflows/create_handoff.md +98 -0
- data/workflows/create_note.md +82 -0
- data/workflows/create_plan.md +457 -0
- data/workflows/decompose_ticket.md +109 -0
- data/workflows/feature.md +91 -0
- data/workflows/implement_plan.md +87 -0
- data/workflows/iterate_plan.md +247 -0
- data/workflows/research_codebase.md +210 -0
- data/workflows/resume_handoff.md +217 -0
- data/workflows/review_pr.md +320 -0
- data/workflows/thoughts_init.md +71 -0
- data/workflows/validate_plan.md +166 -0
- metadata +284 -1
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tools
|
|
4
|
+
# Spawns a generic child session that works on a task autonomously.
|
|
5
|
+
# The sub-agent inherits the parent's viewport context at fork time,
|
|
6
|
+
# runs via {AgentRequestJob}, and delivers results back
|
|
7
|
+
# through {Tools::ReturnResult}.
|
|
8
|
+
#
|
|
9
|
+
# For named specialists with predefined prompts and tools, see {SpawnSpecialist}.
|
|
10
|
+
class SpawnSubagent < Base
|
|
11
|
+
include SubagentPrompts
|
|
12
|
+
|
|
13
|
+
GENERIC_PROMPT = "You are a focused sub-agent. #{RETURN_INSTRUCTION}\n"
|
|
14
|
+
|
|
15
|
+
def self.tool_name = "spawn_subagent"
|
|
16
|
+
|
|
17
|
+
def self.description
|
|
18
|
+
"Spawn a generic sub-agent to work on a task autonomously. " \
|
|
19
|
+
"The sub-agent inherits your conversation context, works independently, " \
|
|
20
|
+
"and returns results as a tool response when done."
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.input_schema
|
|
24
|
+
{
|
|
25
|
+
type: "object",
|
|
26
|
+
properties: {
|
|
27
|
+
task: {
|
|
28
|
+
type: "string",
|
|
29
|
+
description: "What the sub-agent should do (emitted as its first user message)"
|
|
30
|
+
},
|
|
31
|
+
expected_output: {
|
|
32
|
+
type: "string",
|
|
33
|
+
description: "Description of the expected deliverable"
|
|
34
|
+
},
|
|
35
|
+
tools: {
|
|
36
|
+
type: "array",
|
|
37
|
+
items: {type: "string"},
|
|
38
|
+
description: "Tool names to grant the sub-agent. " \
|
|
39
|
+
"Omit for all standard tools. Empty array for pure reasoning (return_result only). " \
|
|
40
|
+
"Valid tools: #{AgentLoop::STANDARD_TOOLS_BY_NAME.keys.join(", ")}"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
required: %w[task expected_output]
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @param session [Session] the parent session spawning the sub-agent
|
|
48
|
+
def initialize(session:, **)
|
|
49
|
+
@session = session
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Creates a child session, emits the task as a user message, and
|
|
53
|
+
# queues background processing. Returns immediately (non-blocking).
|
|
54
|
+
#
|
|
55
|
+
# @param input [Hash<String, Object>] with "task", "expected_output", and optional "tools"
|
|
56
|
+
# @return [String] confirmation with child session ID
|
|
57
|
+
# @return [Hash{Symbol => String}] with :error key on validation failure
|
|
58
|
+
def execute(input)
|
|
59
|
+
task = input["task"].to_s.strip
|
|
60
|
+
expected_output = input["expected_output"].to_s.strip
|
|
61
|
+
|
|
62
|
+
return {error: "Task cannot be blank"} if task.empty?
|
|
63
|
+
return {error: "Expected output cannot be blank"} if expected_output.empty?
|
|
64
|
+
|
|
65
|
+
tools = normalize_tools(input["tools"])
|
|
66
|
+
|
|
67
|
+
error = validate_tools(tools)
|
|
68
|
+
return error if error
|
|
69
|
+
|
|
70
|
+
child = spawn_child(task, expected_output, tools)
|
|
71
|
+
"Sub-agent spawned (session #{child.id}). Result will arrive as a tool response."
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def spawn_child(task, expected_output, granted_tools)
|
|
77
|
+
child = Session.create!(
|
|
78
|
+
parent_session_id: @session.id,
|
|
79
|
+
prompt: "#{GENERIC_PROMPT}\n#{EXPECTED_DELIVERABLE_PREFIX}#{expected_output}",
|
|
80
|
+
granted_tools: granted_tools
|
|
81
|
+
)
|
|
82
|
+
Events::Bus.emit(Events::UserMessage.new(content: task, session_id: child.id))
|
|
83
|
+
AgentRequestJob.perform_later(child.id)
|
|
84
|
+
child
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Normalizes tool names to lowercase and removes duplicates.
|
|
88
|
+
# Returns non-array values unchanged for {#validate_tools} to catch.
|
|
89
|
+
#
|
|
90
|
+
# @param tools [Array, nil, Object] raw tools parameter from LLM
|
|
91
|
+
# @return [Array<String>, nil, Object] normalized tools
|
|
92
|
+
def normalize_tools(tools)
|
|
93
|
+
return nil unless tools
|
|
94
|
+
return tools unless tools.is_a?(Array)
|
|
95
|
+
|
|
96
|
+
tools.map { |tool| tool.to_s.downcase }.uniq
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# @param tools [Array<String>, nil, Object] normalized tools parameter
|
|
100
|
+
# @return [Hash{Symbol => String}, nil] error hash if invalid, nil if valid
|
|
101
|
+
def validate_tools(tools)
|
|
102
|
+
return nil unless tools
|
|
103
|
+
return {error: "tools must be an array"} unless tools.is_a?(Array)
|
|
104
|
+
|
|
105
|
+
unknown = tools - AgentLoop::STANDARD_TOOLS_BY_NAME.keys
|
|
106
|
+
return {error: "Unknown tool: #{unknown.first}"} if unknown.any?
|
|
107
|
+
|
|
108
|
+
nil
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tools
|
|
4
|
+
# Shared prompt fragments for tools that spawn sub-agent sessions.
|
|
5
|
+
# Included by {SpawnSubagent} and {SpawnSpecialist} to avoid duplication.
|
|
6
|
+
module SubagentPrompts
|
|
7
|
+
RETURN_INSTRUCTION = "Complete the assigned task, then call the return_result tool with your deliverable. " \
|
|
8
|
+
"Do not ask follow-up questions — work with the context you have."
|
|
9
|
+
|
|
10
|
+
EXPECTED_DELIVERABLE_PREFIX = "Expected deliverable: "
|
|
11
|
+
end
|
|
12
|
+
end
|
data/lib/tools/web_get.rb
CHANGED
|
@@ -4,13 +4,10 @@ require "httparty"
|
|
|
4
4
|
|
|
5
5
|
module Tools
|
|
6
6
|
# Fetches content from a URL via HTTP GET. Returns the response body
|
|
7
|
-
# as plain text, truncated to {
|
|
7
|
+
# as plain text, truncated to {Anima::Settings.max_web_response_bytes} to prevent memory issues.
|
|
8
8
|
#
|
|
9
9
|
# Only http and https schemes are allowed.
|
|
10
10
|
class WebGet < Base
|
|
11
|
-
MAX_RESPONSE_BYTES = 100_000
|
|
12
|
-
REQUEST_TIMEOUT = 10
|
|
13
|
-
|
|
14
11
|
def self.tool_name = "web_get"
|
|
15
12
|
|
|
16
13
|
def self.description = "Fetch content from a URL via HTTP GET and return the response body"
|
|
@@ -35,17 +32,18 @@ module Tools
|
|
|
35
32
|
private
|
|
36
33
|
|
|
37
34
|
def validate_and_fetch(url)
|
|
35
|
+
timeout = Anima::Settings.web_request_timeout
|
|
38
36
|
scheme = URI.parse(url).scheme
|
|
39
37
|
|
|
40
38
|
unless %w[http https].include?(scheme)
|
|
41
39
|
return {error: "Only http and https URLs are supported, got: #{scheme.inspect}"}
|
|
42
40
|
end
|
|
43
41
|
|
|
44
|
-
truncate_body(HTTParty.get(url, timeout:
|
|
42
|
+
truncate_body(HTTParty.get(url, timeout: timeout, follow_redirects: false).body.to_s)
|
|
45
43
|
rescue URI::InvalidURIError => error
|
|
46
44
|
{error: "Invalid URL: #{error.message}"}
|
|
47
45
|
rescue Net::OpenTimeout, Net::ReadTimeout
|
|
48
|
-
{error: "Request timed out after #{
|
|
46
|
+
{error: "Request timed out after #{timeout} seconds"}
|
|
49
47
|
rescue Errno::ECONNREFUSED
|
|
50
48
|
{error: "Connection refused: #{url}"}
|
|
51
49
|
rescue => error
|
|
@@ -53,10 +51,11 @@ module Tools
|
|
|
53
51
|
end
|
|
54
52
|
|
|
55
53
|
def truncate_body(body)
|
|
56
|
-
|
|
54
|
+
max_bytes = Anima::Settings.max_web_response_bytes
|
|
55
|
+
return body if body.bytesize <= max_bytes
|
|
57
56
|
|
|
58
|
-
body.byteslice(0,
|
|
59
|
-
"\n\n[Truncated: response exceeded #{
|
|
57
|
+
body.byteslice(0, max_bytes) +
|
|
58
|
+
"\n\n[Truncated: response exceeded #{max_bytes} bytes]"
|
|
60
59
|
end
|
|
61
60
|
end
|
|
62
61
|
end
|