octo-agent 0.11.2
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 +7 -0
- data/.clacky/skills/commit/SKILL.md +423 -0
- data/.clacky/skills/gem-release/SKILL.md +199 -0
- data/.clacky/skills/gem-release/scripts/release.sh +304 -0
- data/.clacky/skills/oss-upload/SKILL.md +47 -0
- data/.octorules +106 -0
- data/.rspec +3 -0
- data/.rubocop.yml +8 -0
- data/CHANGELOG.md +76 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/CONTRIBUTING.md +92 -0
- data/Dockerfile +28 -0
- data/LICENSE.txt +22 -0
- data/POSITIONING.md +46 -0
- data/README.md +134 -0
- data/README_CN.md +134 -0
- data/Rakefile +34 -0
- data/benchmark/fixtures/sample_project/Gemfile +3 -0
- data/benchmark/fixtures/sample_project/lib/api_handler.rb +32 -0
- data/benchmark/fixtures/sample_project/lib/order_calculator.rb +23 -0
- data/benchmark/fixtures/sample_project/lib/user_renderer.rb +20 -0
- data/benchmark/fixtures/sample_project/spec/order_calculator_spec.rb +20 -0
- data/benchmark/results/EVALUATION_REPORT.md +165 -0
- data/benchmark/results/baseline_20260511_174424.json +128 -0
- data/benchmark/results/report_20260511_175256.json +271 -0
- data/benchmark/results/report_20260511_175444.json +271 -0
- data/benchmark/results/treatment_20260511_175103.json +130 -0
- data/benchmark/runner.rb +441 -0
- data/bin/octo +7 -0
- data/docs/agent-first-ui-design.md +77 -0
- data/docs/billing-system.md +318 -0
- data/docs/channel-architecture.md +235 -0
- data/docs/engineering-article.md +343 -0
- data/docs/session-skill-invocation.md +69 -0
- data/docs/time_machine_design.md +247 -0
- data/docs/ui2-architecture.md +124 -0
- data/homebrew/README.md +96 -0
- data/homebrew/openocto.rb +24 -0
- data/lib/octo/agent/hook_manager.rb +61 -0
- data/lib/octo/agent/llm_caller.rb +800 -0
- data/lib/octo/agent/memory_updater.rb +246 -0
- data/lib/octo/agent/message_compressor.rb +225 -0
- data/lib/octo/agent/message_compressor_helper.rb +869 -0
- data/lib/octo/agent/next_message_suggester.rb +215 -0
- data/lib/octo/agent/session_serializer.rb +685 -0
- data/lib/octo/agent/skill_auto_creator.rb +114 -0
- data/lib/octo/agent/skill_evolution.rb +61 -0
- data/lib/octo/agent/skill_manager.rb +466 -0
- data/lib/octo/agent/skill_reflector.rb +89 -0
- data/lib/octo/agent/system_prompt_builder.rb +101 -0
- data/lib/octo/agent/time_machine.rb +214 -0
- data/lib/octo/agent/tool_executor.rb +454 -0
- data/lib/octo/agent/tool_registry.rb +150 -0
- data/lib/octo/agent.rb +2180 -0
- data/lib/octo/agent_config.rb +989 -0
- data/lib/octo/agent_profile.rb +112 -0
- data/lib/octo/anthropic_stream_aggregator.rb +137 -0
- data/lib/octo/background_task_registry.rb +324 -0
- data/lib/octo/banner.rb +34 -0
- data/lib/octo/bedrock_stream_aggregator.rb +137 -0
- data/lib/octo/block_font.rb +331 -0
- data/lib/octo/cli.rb +968 -0
- data/lib/octo/client.rb +623 -0
- data/lib/octo/default_agents/SOUL.md +3 -0
- data/lib/octo/default_agents/USER.md +1 -0
- data/lib/octo/default_agents/base_prompt.md +66 -0
- data/lib/octo/default_agents/coding/profile.yml +2 -0
- data/lib/octo/default_agents/coding/system_prompt.md +67 -0
- data/lib/octo/default_agents/general/profile.yml +2 -0
- data/lib/octo/default_agents/general/system_prompt.md +16 -0
- data/lib/octo/default_parsers/doc_parser.rb +69 -0
- data/lib/octo/default_parsers/docx_parser.rb +188 -0
- data/lib/octo/default_parsers/pdf_parser.rb +120 -0
- data/lib/octo/default_parsers/pdf_parser_ocr.py +103 -0
- data/lib/octo/default_parsers/pdf_parser_plumber.py +62 -0
- data/lib/octo/default_parsers/pptx_parser.rb +140 -0
- data/lib/octo/default_parsers/xlsx_parser.rb +121 -0
- data/lib/octo/default_skills/browser-setup/SKILL.md +426 -0
- data/lib/octo/default_skills/channel-manager/SKILL.md +623 -0
- data/lib/octo/default_skills/channel-manager/dingtalk_setup.rb +191 -0
- data/lib/octo/default_skills/channel-manager/discord_setup.rb +199 -0
- data/lib/octo/default_skills/channel-manager/feishu_setup.rb +574 -0
- data/lib/octo/default_skills/channel-manager/import_lark_skills.rb +97 -0
- data/lib/octo/default_skills/channel-manager/install_feishu_skills.rb +105 -0
- data/lib/octo/default_skills/channel-manager/weixin_setup.rb +274 -0
- data/lib/octo/default_skills/code-explorer/SKILL.md +36 -0
- data/lib/octo/default_skills/cron-task-creator/SKILL.md +257 -0
- data/lib/octo/default_skills/cron-task-creator/evals/evals.json +38 -0
- data/lib/octo/default_skills/onboard/SKILL.md +578 -0
- data/lib/octo/default_skills/onboard/scripts/import_external_skills.rb +413 -0
- data/lib/octo/default_skills/onboard/scripts/install_builtin_skills.rb +97 -0
- data/lib/octo/default_skills/persist-memory/SKILL.md +59 -0
- data/lib/octo/default_skills/personal-website/SKILL.md +113 -0
- data/lib/octo/default_skills/personal-website/publish.rb +235 -0
- data/lib/octo/default_skills/product-help/SKILL.md +123 -0
- data/lib/octo/default_skills/product-help/docs/agent-config.md +74 -0
- data/lib/octo/default_skills/product-help/docs/best-practices.md +49 -0
- data/lib/octo/default_skills/product-help/docs/browser-tool.md +53 -0
- data/lib/octo/default_skills/product-help/docs/built-in-skills.md +43 -0
- data/lib/octo/default_skills/product-help/docs/cli-reference.md +82 -0
- data/lib/octo/default_skills/product-help/docs/create-your-first-skill.md +47 -0
- data/lib/octo/default_skills/product-help/docs/faq.md +98 -0
- data/lib/octo/default_skills/product-help/docs/how-to-use-a-skill.md +58 -0
- data/lib/octo/default_skills/product-help/docs/installation.md +59 -0
- data/lib/octo/default_skills/product-help/docs/memory-system.md +61 -0
- data/lib/octo/default_skills/product-help/docs/octorules.md +62 -0
- data/lib/octo/default_skills/product-help/docs/session-management.md +63 -0
- data/lib/octo/default_skills/product-help/docs/skill-basics.md +55 -0
- data/lib/octo/default_skills/product-help/docs/skill-frontmatter.md +61 -0
- data/lib/octo/default_skills/product-help/docs/web-server.md +49 -0
- data/lib/octo/default_skills/product-help/docs/what-is-octo.md +37 -0
- data/lib/octo/default_skills/product-help/docs/windows-installation.md +36 -0
- data/lib/octo/default_skills/product-help/docs/writing-tips.md +53 -0
- data/lib/octo/default_skills/recall-memory/SKILL.md +65 -0
- data/lib/octo/default_skills/skill-add/SKILL.md +59 -0
- data/lib/octo/default_skills/skill-add/scripts/install_from_zip.rb +295 -0
- data/lib/octo/default_skills/skill-creator/SKILL.md +602 -0
- data/lib/octo/default_skills/skill-creator/agents/analyzer.md +274 -0
- data/lib/octo/default_skills/skill-creator/agents/comparator.md +202 -0
- data/lib/octo/default_skills/skill-creator/agents/grader.md +223 -0
- data/lib/octo/default_skills/skill-creator/eval-viewer/generate_review.py +471 -0
- data/lib/octo/default_skills/skill-creator/eval-viewer/viewer.html +1325 -0
- data/lib/octo/default_skills/skill-creator/references/schemas.md +430 -0
- data/lib/octo/default_skills/skill-creator/scripts/__init__.py +0 -0
- data/lib/octo/default_skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- data/lib/octo/default_skills/skill-creator/scripts/generate_report.py +326 -0
- data/lib/octo/default_skills/skill-creator/scripts/improve_description.py +310 -0
- data/lib/octo/default_skills/skill-creator/scripts/quick_validate.py +103 -0
- data/lib/octo/default_skills/skill-creator/scripts/run_eval.py +317 -0
- data/lib/octo/default_skills/skill-creator/scripts/run_loop.py +331 -0
- data/lib/octo/default_skills/skill-creator/scripts/utils.py +47 -0
- data/lib/octo/default_skills/skill-creator/scripts/validate_skill_frontmatter.rb +143 -0
- data/lib/octo/idle_compression_timer.rb +115 -0
- data/lib/octo/json_ui_controller.rb +204 -0
- data/lib/octo/message_format/anthropic.rb +409 -0
- data/lib/octo/message_format/bedrock.rb +361 -0
- data/lib/octo/message_format/open_ai.rb +222 -0
- data/lib/octo/message_history.rb +373 -0
- data/lib/octo/openai_stream_aggregator.rb +130 -0
- data/lib/octo/plain_ui_controller.rb +166 -0
- data/lib/octo/providers.rb +534 -0
- data/lib/octo/server/browser_manager.rb +397 -0
- data/lib/octo/server/channel/adapters/base.rb +82 -0
- data/lib/octo/server/channel/adapters/dingtalk/adapter.rb +314 -0
- data/lib/octo/server/channel/adapters/dingtalk/api_client.rb +391 -0
- data/lib/octo/server/channel/adapters/dingtalk/stream_client.rb +203 -0
- data/lib/octo/server/channel/adapters/discord/adapter.rb +229 -0
- data/lib/octo/server/channel/adapters/discord/api_client.rb +107 -0
- data/lib/octo/server/channel/adapters/discord/gateway_client.rb +270 -0
- data/lib/octo/server/channel/adapters/feishu/adapter.rb +320 -0
- data/lib/octo/server/channel/adapters/feishu/bot.rb +478 -0
- data/lib/octo/server/channel/adapters/feishu/file_processor.rb +36 -0
- data/lib/octo/server/channel/adapters/feishu/message_parser.rb +129 -0
- data/lib/octo/server/channel/adapters/feishu/ws_client.rb +423 -0
- data/lib/octo/server/channel/adapters/telegram/adapter.rb +375 -0
- data/lib/octo/server/channel/adapters/telegram/api_client.rb +205 -0
- data/lib/octo/server/channel/adapters/wecom/adapter.rb +148 -0
- data/lib/octo/server/channel/adapters/wecom/media_downloader.rb +115 -0
- data/lib/octo/server/channel/adapters/wecom/ws_client.rb +395 -0
- data/lib/octo/server/channel/adapters/weixin/adapter.rb +692 -0
- data/lib/octo/server/channel/adapters/weixin/api_client.rb +402 -0
- data/lib/octo/server/channel/channel_config.rb +178 -0
- data/lib/octo/server/channel/channel_manager.rb +468 -0
- data/lib/octo/server/channel/channel_ui_controller.rb +224 -0
- data/lib/octo/server/channel.rb +33 -0
- data/lib/octo/server/discover.rb +77 -0
- data/lib/octo/server/epipe_safe_io.rb +105 -0
- data/lib/octo/server/http_server.rb +3554 -0
- data/lib/octo/server/scheduler.rb +317 -0
- data/lib/octo/server/server_master.rb +325 -0
- data/lib/octo/server/session_registry.rb +431 -0
- data/lib/octo/server/web_ui_controller.rb +487 -0
- data/lib/octo/session_manager.rb +385 -0
- data/lib/octo/skill.rb +466 -0
- data/lib/octo/skill_loader.rb +328 -0
- data/lib/octo/tools/base.rb +118 -0
- data/lib/octo/tools/browser.rb +625 -0
- data/lib/octo/tools/edit.rb +165 -0
- data/lib/octo/tools/file_reader.rb +549 -0
- data/lib/octo/tools/glob.rb +162 -0
- data/lib/octo/tools/grep.rb +356 -0
- data/lib/octo/tools/invoke_skill.rb +96 -0
- data/lib/octo/tools/list_tasks.rb +54 -0
- data/lib/octo/tools/redo_task.rb +41 -0
- data/lib/octo/tools/request_user_feedback.rb +84 -0
- data/lib/octo/tools/security.rb +333 -0
- data/lib/octo/tools/terminal/output_cleaner.rb +63 -0
- data/lib/octo/tools/terminal/persistent_session.rb +268 -0
- data/lib/octo/tools/terminal/safe_rm.sh +106 -0
- data/lib/octo/tools/terminal/session_manager.rb +213 -0
- data/lib/octo/tools/terminal.rb +1828 -0
- data/lib/octo/tools/todo_manager.rb +374 -0
- data/lib/octo/tools/trash_manager.rb +388 -0
- data/lib/octo/tools/undo_task.rb +35 -0
- data/lib/octo/tools/web_fetch.rb +242 -0
- data/lib/octo/tools/web_search.rb +260 -0
- data/lib/octo/tools/write.rb +77 -0
- data/lib/octo/ui2/block_font.rb +10 -0
- data/lib/octo/ui2/components/base_component.rb +163 -0
- data/lib/octo/ui2/components/command_suggestions.rb +290 -0
- data/lib/octo/ui2/components/common_component.rb +96 -0
- data/lib/octo/ui2/components/inline_input.rb +226 -0
- data/lib/octo/ui2/components/input_area.rb +1338 -0
- data/lib/octo/ui2/components/message_component.rb +99 -0
- data/lib/octo/ui2/components/modal_component.rb +419 -0
- data/lib/octo/ui2/components/todo_area.rb +149 -0
- data/lib/octo/ui2/components/tool_component.rb +107 -0
- data/lib/octo/ui2/components/welcome_banner.rb +139 -0
- data/lib/octo/ui2/layout_manager.rb +807 -0
- data/lib/octo/ui2/line_editor.rb +363 -0
- data/lib/octo/ui2/markdown_renderer.rb +100 -0
- data/lib/octo/ui2/output_buffer.rb +370 -0
- data/lib/octo/ui2/progress_handle.rb +362 -0
- data/lib/octo/ui2/progress_indicator.rb +55 -0
- data/lib/octo/ui2/screen_buffer.rb +273 -0
- data/lib/octo/ui2/terminal_detector.rb +119 -0
- data/lib/octo/ui2/theme_manager.rb +85 -0
- data/lib/octo/ui2/themes/base_theme.rb +105 -0
- data/lib/octo/ui2/themes/hacker_theme.rb +62 -0
- data/lib/octo/ui2/themes/minimal_theme.rb +56 -0
- data/lib/octo/ui2/thinking_verbs.rb +26 -0
- data/lib/octo/ui2/ui_controller.rb +1625 -0
- data/lib/octo/ui2/view_renderer.rb +177 -0
- data/lib/octo/ui2.rb +40 -0
- data/lib/octo/ui_interface.rb +154 -0
- data/lib/octo/utils/arguments_parser.rb +191 -0
- data/lib/octo/utils/browser_detector.rb +195 -0
- data/lib/octo/utils/encoding.rb +92 -0
- data/lib/octo/utils/environment_detector.rb +140 -0
- data/lib/octo/utils/file_ignore_helper.rb +170 -0
- data/lib/octo/utils/file_processor.rb +601 -0
- data/lib/octo/utils/gitignore_parser.rb +154 -0
- data/lib/octo/utils/limit_stack.rb +152 -0
- data/lib/octo/utils/logger.rb +124 -0
- data/lib/octo/utils/login_shell.rb +72 -0
- data/lib/octo/utils/model_pricing.rb +646 -0
- data/lib/octo/utils/parser_manager.rb +165 -0
- data/lib/octo/utils/path_helper.rb +15 -0
- data/lib/octo/utils/scripts_manager.rb +59 -0
- data/lib/octo/utils/string_matcher.rb +158 -0
- data/lib/octo/utils/trash_directory.rb +112 -0
- data/lib/octo/utils/workspace_rules.rb +46 -0
- data/lib/octo/version.rb +5 -0
- data/lib/octo/web/app.css +7141 -0
- data/lib/octo/web/app.js +543 -0
- data/lib/octo/web/apple-touch-icon.png +0 -0
- data/lib/octo/web/auth.js +150 -0
- data/lib/octo/web/channels.js +276 -0
- data/lib/octo/web/datepicker.js +205 -0
- data/lib/octo/web/favicon.png +0 -0
- data/lib/octo/web/i18n.js +1073 -0
- data/lib/octo/web/icon-512.png +0 -0
- data/lib/octo/web/icon-dark.svg +25 -0
- data/lib/octo/web/icon.svg +29 -0
- data/lib/octo/web/index.html +871 -0
- data/lib/octo/web/marked.min.js +69 -0
- data/lib/octo/web/onboard.js +491 -0
- data/lib/octo/web/profile.js +442 -0
- data/lib/octo/web/sessions.js +4421 -0
- data/lib/octo/web/settings.js +913 -0
- data/lib/octo/web/sidebar.js +32 -0
- data/lib/octo/web/skills.js +885 -0
- data/lib/octo/web/tasks.js +297 -0
- data/lib/octo/web/theme.js +105 -0
- data/lib/octo/web/trash.js +343 -0
- data/lib/octo/web/vendor/hljs/highlight.min.js +1244 -0
- data/lib/octo/web/vendor/hljs/hljs-theme.css +95 -0
- data/lib/octo/web/vendor/katex/auto-render.min.js +1 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/katex.min.css +1 -0
- data/lib/octo/web/vendor/katex/katex.min.js +1 -0
- data/lib/octo/web/version.js +449 -0
- data/lib/octo/web/weixin-qr.html +209 -0
- data/lib/octo/web/ws-dispatcher.js +357 -0
- data/lib/octo/web/ws.js +128 -0
- data/lib/octo.rb +145 -0
- data/scripts/build/build.sh +329 -0
- data/scripts/build/lib/apt.sh +56 -0
- data/scripts/build/lib/brew.sh +89 -0
- data/scripts/build/lib/colors.sh +17 -0
- data/scripts/build/lib/gem.sh +95 -0
- data/scripts/build/lib/mise.sh +125 -0
- data/scripts/build/lib/network.sh +157 -0
- data/scripts/build/lib/os.sh +57 -0
- data/scripts/build/lib/shell.sh +37 -0
- data/scripts/build/src/install.sh.cc +174 -0
- data/scripts/build/src/install_browser.sh.cc +101 -0
- data/scripts/build/src/install_full.sh.cc +290 -0
- data/scripts/build/src/install_rails_deps.sh.cc +145 -0
- data/scripts/build/src/install_system_deps.sh.cc +123 -0
- data/scripts/build/src/uninstall.sh.cc +101 -0
- data/scripts/install.ps1 +532 -0
- data/scripts/install.sh +567 -0
- data/scripts/install_browser.sh +479 -0
- data/scripts/install_full.sh +838 -0
- data/scripts/install_rails_deps.sh +746 -0
- data/scripts/install_system_deps.sh +518 -0
- data/scripts/uninstall.sh +287 -0
- data/sig/octo.rbs +4 -0
- metadata +614 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "components/message_component"
|
|
4
|
+
require_relative "components/tool_component"
|
|
5
|
+
require_relative "components/common_component"
|
|
6
|
+
require_relative "markdown_renderer"
|
|
7
|
+
|
|
8
|
+
module Octo
|
|
9
|
+
module UI2
|
|
10
|
+
# ViewRenderer coordinates all UI components and provides a unified rendering interface
|
|
11
|
+
class ViewRenderer
|
|
12
|
+
def initialize
|
|
13
|
+
@message_component = Components::MessageComponent.new
|
|
14
|
+
@tool_component = Components::ToolComponent.new
|
|
15
|
+
@common_component = Components::CommonComponent.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Render a user message
|
|
19
|
+
# @param content [String] Message content
|
|
20
|
+
# @param timestamp [Time, nil] Optional timestamp
|
|
21
|
+
# @param files [Array<Hash>] Optional file hashes { name:, mime_type:, ... }
|
|
22
|
+
# @return [String] Rendered message
|
|
23
|
+
def render_user_message(content, timestamp: nil, files: [])
|
|
24
|
+
@message_component.render(
|
|
25
|
+
role: "user",
|
|
26
|
+
content: content,
|
|
27
|
+
timestamp: timestamp,
|
|
28
|
+
files: files
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Render an assistant message
|
|
33
|
+
# @param content [String] Message content
|
|
34
|
+
# @param timestamp [Time, nil] Optional timestamp
|
|
35
|
+
# @return [String] Rendered message
|
|
36
|
+
def render_assistant_message(content, timestamp: nil)
|
|
37
|
+
# Render markdown if content contains markdown syntax
|
|
38
|
+
rendered_content = if MarkdownRenderer.markdown?(content)
|
|
39
|
+
MarkdownRenderer.render(content)
|
|
40
|
+
else
|
|
41
|
+
content
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
@message_component.render(
|
|
45
|
+
role: "assistant",
|
|
46
|
+
content: rendered_content,
|
|
47
|
+
timestamp: timestamp
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Render a system message
|
|
52
|
+
# @param content [String] Message content
|
|
53
|
+
# @param timestamp [Time, nil] Optional timestamp
|
|
54
|
+
# @param prefix_newline [Boolean] Whether to add newline before message
|
|
55
|
+
# @return [String] Rendered message
|
|
56
|
+
def render_system_message(content, timestamp: nil, prefix_newline: true)
|
|
57
|
+
@message_component.render(
|
|
58
|
+
role: "system",
|
|
59
|
+
content: content,
|
|
60
|
+
timestamp: timestamp,
|
|
61
|
+
prefix_newline: prefix_newline
|
|
62
|
+
)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Render a tool call
|
|
66
|
+
# @param tool_name [String] Tool name
|
|
67
|
+
# @param formatted_call [String] Formatted call description
|
|
68
|
+
# @return [String] Rendered tool call
|
|
69
|
+
def render_tool_call(tool_name:, formatted_call:)
|
|
70
|
+
@tool_component.render(
|
|
71
|
+
type: :call,
|
|
72
|
+
tool_name: tool_name,
|
|
73
|
+
formatted_call: formatted_call
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Render a tool result
|
|
78
|
+
# @param result [String] Tool result
|
|
79
|
+
# @return [String] Rendered tool result
|
|
80
|
+
def render_tool_result(result:)
|
|
81
|
+
@tool_component.render(
|
|
82
|
+
type: :result,
|
|
83
|
+
result: result
|
|
84
|
+
)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Render a tool error
|
|
88
|
+
# @param error [String] Error message
|
|
89
|
+
# @return [String] Rendered tool error
|
|
90
|
+
def render_tool_error(error:)
|
|
91
|
+
@tool_component.render(
|
|
92
|
+
type: :error,
|
|
93
|
+
error: error
|
|
94
|
+
)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Render a tool denied message
|
|
98
|
+
# @param tool_name [String] Tool name
|
|
99
|
+
# @return [String] Rendered tool denied
|
|
100
|
+
def render_tool_denied(tool_name:)
|
|
101
|
+
@tool_component.render(
|
|
102
|
+
type: :denied,
|
|
103
|
+
tool_name: tool_name
|
|
104
|
+
)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Render a tool planned message
|
|
108
|
+
# @param tool_name [String] Tool name
|
|
109
|
+
# @return [String] Rendered tool planned
|
|
110
|
+
def render_tool_planned(tool_name:)
|
|
111
|
+
@tool_component.render(
|
|
112
|
+
type: :planned,
|
|
113
|
+
tool_name: tool_name
|
|
114
|
+
)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Render thinking indicator
|
|
118
|
+
# @return [String] Thinking indicator
|
|
119
|
+
def render_thinking
|
|
120
|
+
@common_component.render_thinking
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Render progress message (stopped state, gray)
|
|
124
|
+
# @param message [String] Progress message
|
|
125
|
+
# @return [String] Progress indicator
|
|
126
|
+
def render_progress(message)
|
|
127
|
+
@common_component.render_progress(message)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Render working message (active state, yellow)
|
|
131
|
+
# @param message [String] Progress message
|
|
132
|
+
# @return [String] Working indicator
|
|
133
|
+
def render_working(message)
|
|
134
|
+
@common_component.render_working(message)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Render success message
|
|
138
|
+
# @param message [String] Success message
|
|
139
|
+
# @return [String] Success message
|
|
140
|
+
def render_success(message)
|
|
141
|
+
@common_component.render_success(message)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Render error message
|
|
145
|
+
# @param message [String] Error message
|
|
146
|
+
# @return [String] Error message
|
|
147
|
+
def render_error(message)
|
|
148
|
+
@common_component.render_error(message)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Render warning message
|
|
152
|
+
# @param message [String] Warning message
|
|
153
|
+
# @return [String] Warning message
|
|
154
|
+
def render_warning(message)
|
|
155
|
+
@common_component.render_warning(message)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Render task completion summary
|
|
159
|
+
# @param iterations [Integer] Number of iterations
|
|
160
|
+
# @param duration [Float] Duration in seconds
|
|
161
|
+
# @param cache_tokens [Integer] Cache read tokens
|
|
162
|
+
# @param cache_requests [Integer] Total cache requests count
|
|
163
|
+
# @param cache_hits [Integer] Cache hit requests count
|
|
164
|
+
# @return [String] Formatted completion summary
|
|
165
|
+
def render_task_complete(iterations:, duration: nil, cache_tokens: nil, cache_requests: nil, cache_hits: nil)
|
|
166
|
+
@common_component.render_task_complete(
|
|
167
|
+
iterations: iterations,
|
|
168
|
+
duration: duration,
|
|
169
|
+
cache_tokens: cache_tokens,
|
|
170
|
+
cache_requests: cache_requests,
|
|
171
|
+
cache_hits: cache_hits
|
|
172
|
+
)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
data/lib/octo/ui2.rb
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# UI2 - MVC-based terminal UI system for Octo
|
|
4
|
+
# Provides split-screen interface with scrollable output and fixed input
|
|
5
|
+
|
|
6
|
+
require_relative "ui2/thinking_verbs"
|
|
7
|
+
require_relative "ui2/progress_indicator"
|
|
8
|
+
require_relative "ui2/terminal_detector"
|
|
9
|
+
require_relative "ui2/theme_manager"
|
|
10
|
+
require_relative "ui2/screen_buffer"
|
|
11
|
+
require_relative "ui2/layout_manager"
|
|
12
|
+
require_relative "ui2/view_renderer"
|
|
13
|
+
require_relative "ui2/ui_controller"
|
|
14
|
+
|
|
15
|
+
require_relative "ui2/components/base_component"
|
|
16
|
+
require_relative "ui2/components/input_area"
|
|
17
|
+
require_relative "ui2/components/message_component"
|
|
18
|
+
require_relative "ui2/components/tool_component"
|
|
19
|
+
require_relative "ui2/components/common_component"
|
|
20
|
+
require_relative "ui2/components/welcome_banner"
|
|
21
|
+
require_relative "ui2/components/modal_component"
|
|
22
|
+
|
|
23
|
+
module Octo
|
|
24
|
+
module UI2
|
|
25
|
+
# Version of the UI2 system
|
|
26
|
+
VERSION = "1.0.0"
|
|
27
|
+
|
|
28
|
+
# Quick start: Create a UI controller and run
|
|
29
|
+
# @param config [Hash] Optional configuration (working_dir, mode, model)
|
|
30
|
+
# @example
|
|
31
|
+
# controller = Octo::UI2::UIController.new
|
|
32
|
+
# controller.on_input { |input| puts "Got: #{input}" }
|
|
33
|
+
# controller.start
|
|
34
|
+
def self.start(config = {}, &block)
|
|
35
|
+
controller = UIController.new(config)
|
|
36
|
+
controller.on_input(&block) if block_given?
|
|
37
|
+
controller.start
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Octo
|
|
4
|
+
# UIInterface defines the standard interface between Agent/CLI and UI implementations.
|
|
5
|
+
# All UI controllers (UIController, JsonUIController) must implement these methods.
|
|
6
|
+
module UIInterface
|
|
7
|
+
# === Output display ===
|
|
8
|
+
# @param content [String] text portion of the assistant reply (file:// links stripped)
|
|
9
|
+
# @param files [Array<Hash>] extracted file refs: [{ name:, path:, inline: }]
|
|
10
|
+
def show_assistant_message(content, files:); end
|
|
11
|
+
def show_tool_call(name, args); end
|
|
12
|
+
def show_tool_result(result, ui_payload: nil); end
|
|
13
|
+
def show_tool_stdout(lines); end
|
|
14
|
+
def show_tool_error(error); end
|
|
15
|
+
def show_tool_args(formatted_args); end
|
|
16
|
+
def show_file_write_preview(path, is_new_file:); end
|
|
17
|
+
def show_file_edit_preview(path); end
|
|
18
|
+
def show_file_error(error_message); end
|
|
19
|
+
def show_shell_preview(command); end
|
|
20
|
+
def show_diff(old_content, new_content, max_lines: 50); end
|
|
21
|
+
def show_token_usage(token_data); end
|
|
22
|
+
def show_complete(iterations:, duration: nil, cache_stats: nil, awaiting_user_feedback: false); end
|
|
23
|
+
def append_output(content); end
|
|
24
|
+
|
|
25
|
+
# === Status messages ===
|
|
26
|
+
def show_info(message, prefix_newline: true); end
|
|
27
|
+
def show_warning(message); end
|
|
28
|
+
def show_error(message); end
|
|
29
|
+
def show_success(message); end
|
|
30
|
+
def log(message, level: :info); end
|
|
31
|
+
|
|
32
|
+
# === Progress ===
|
|
33
|
+
# Unified progress indicator with type-based display customization.
|
|
34
|
+
# progress_type: "thinking" | "retrying" | "idle_compress" | custom
|
|
35
|
+
# phase: "active" | "done"
|
|
36
|
+
# metadata: extensible hash (e.g., {attempt: 3, total: 10} for retries)
|
|
37
|
+
def show_progress(message = nil, prefix_newline: true, progress_type: "thinking", phase: "active", metadata: {}); end
|
|
38
|
+
|
|
39
|
+
# Update the live "thinking" progress with streamed token counts.
|
|
40
|
+
# This is *purely decorative*: it must NEVER start a new progress
|
|
41
|
+
# indicator. If no thinking progress is currently active (e.g. during
|
|
42
|
+
# idle compression, where only a quiet "Compressing..." progress is
|
|
43
|
+
# live), the call is a no-op. UI2 overrides this; other UIs delegate
|
|
44
|
+
# to show_progress.
|
|
45
|
+
def stream_thinking_progress(input_tokens:, output_tokens:)
|
|
46
|
+
show_progress(
|
|
47
|
+
progress_type: "thinking",
|
|
48
|
+
phase: "active",
|
|
49
|
+
metadata: { input_tokens: input_tokens, output_tokens: output_tokens }
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# === Progress (v2: owned handles) ===
|
|
54
|
+
#
|
|
55
|
+
# Start a new progress indicator and return an owned handle. The caller
|
|
56
|
+
# is responsible for finishing it — use +with_progress+ (below) whenever
|
|
57
|
+
# possible to get ensure-based auto-close.
|
|
58
|
+
#
|
|
59
|
+
# @param message [String, nil] Initial progress message (nil picks a random thinking verb).
|
|
60
|
+
# @param style [Symbol] :primary (foreground, yellow, bumps sessionbar)
|
|
61
|
+
# or :quiet (background, gray, no sessionbar change).
|
|
62
|
+
# @param quiet_on_fast_finish [Boolean] When true, a finish under
|
|
63
|
+
# FAST_FINISH_THRESHOLD_SECONDS removes the progress line entirely
|
|
64
|
+
# (preferred for per-tool wrappers so fast tools don't leave a
|
|
65
|
+
# permanent "Executing foo… (0s)" log line). The default
|
|
66
|
+
# implementation ignores this flag — it only affects the native
|
|
67
|
+
# UI2::UIController + ProgressHandle path.
|
|
68
|
+
# @return [#update, #finish, #cancel] a ProgressHandle-like object.
|
|
69
|
+
#
|
|
70
|
+
# Default implementation degrades gracefully to the old show_progress API
|
|
71
|
+
# so UI implementations that haven't migrated still behave correctly.
|
|
72
|
+
def start_progress(message: nil, style: :primary, quiet_on_fast_finish: false)
|
|
73
|
+
_ = quiet_on_fast_finish # default impl doesn't honor fast-collapse
|
|
74
|
+
progress_type = style == :primary ? "thinking" : "idle_compress"
|
|
75
|
+
show_progress(message, progress_type: progress_type, phase: "active")
|
|
76
|
+
LegacyProgressHandleAdapter.new(self, progress_type: progress_type)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Run the given block with a progress indicator active. The handle is
|
|
80
|
+
# always finished in an +ensure+ block — exceptions (including
|
|
81
|
+
# AgentInterrupted) cannot leave the ticker or entry orphaned.
|
|
82
|
+
#
|
|
83
|
+
# @yieldparam handle the progress handle
|
|
84
|
+
def with_progress(message: nil, style: :primary, quiet_on_fast_finish: false)
|
|
85
|
+
handle = start_progress(
|
|
86
|
+
message: message,
|
|
87
|
+
style: style,
|
|
88
|
+
quiet_on_fast_finish: quiet_on_fast_finish
|
|
89
|
+
)
|
|
90
|
+
begin
|
|
91
|
+
yield handle
|
|
92
|
+
ensure
|
|
93
|
+
handle.finish
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Minimal adapter that lets UIs without a native ProgressHandle still
|
|
98
|
+
# participate in the new +with_progress+ API by delegating to the old
|
|
99
|
+
# +show_progress(phase: ...)+ contract. UI2::UIController overrides
|
|
100
|
+
# +start_progress+ directly with a native ProgressHandle, so this
|
|
101
|
+
# adapter is only used by plain/json/web/channel UIs.
|
|
102
|
+
class LegacyProgressHandleAdapter
|
|
103
|
+
def initialize(ui, progress_type:)
|
|
104
|
+
@ui = ui
|
|
105
|
+
@progress_type = progress_type
|
|
106
|
+
@closed = false
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def update(message: nil, metadata: nil)
|
|
110
|
+
return if @closed
|
|
111
|
+
@ui.show_progress(message, progress_type: @progress_type, phase: "active", metadata: metadata || {})
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def finish(final_message: nil)
|
|
115
|
+
return if @closed
|
|
116
|
+
@closed = true
|
|
117
|
+
@ui.show_progress(final_message, progress_type: @progress_type, phase: "done")
|
|
118
|
+
end
|
|
119
|
+
alias_method :cancel, :finish
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# === State updates ===
|
|
123
|
+
def update_sessionbar(tasks: nil, status: nil, latency: nil); end
|
|
124
|
+
def update_todos(todos); end
|
|
125
|
+
|
|
126
|
+
def update_background_tasks(running: 0, tasks: []); end
|
|
127
|
+
def show_background_task_notice(command: nil, handle_id: nil, status: "success"); end
|
|
128
|
+
|
|
129
|
+
def set_working_status; end
|
|
130
|
+
def set_idle_status; end
|
|
131
|
+
|
|
132
|
+
# Broadcast the count of user messages currently sitting in @inbox waiting
|
|
133
|
+
# for the next iteration-boundary drain. Web renders a small "{{n}} messages
|
|
134
|
+
# waiting" hint above the input. Emitted by Agent on two occasions:
|
|
135
|
+
# - enqueue_user_message returned :running (msg will wait behind an in-flight run)
|
|
136
|
+
# - drain_inbox_into_history! consumed items (count typically drops to 0)
|
|
137
|
+
# CLI / JSON / channel UIs no-op by default.
|
|
138
|
+
#
|
|
139
|
+
# @param pending [Integer] number of user_msg items still queued
|
|
140
|
+
def update_user_message_queue_status(pending: 0); end
|
|
141
|
+
|
|
142
|
+
# === Blocking interaction ===
|
|
143
|
+
def request_confirmation(message, default: true); end
|
|
144
|
+
|
|
145
|
+
# === Input control (CLI layer) ===
|
|
146
|
+
def clear_input; end
|
|
147
|
+
def set_input_tips(message, type: :info); end
|
|
148
|
+
def show_next_message_suggestion(text); end
|
|
149
|
+
def hide_next_message_suggestion; end
|
|
150
|
+
|
|
151
|
+
# === Lifecycle ===
|
|
152
|
+
def stop; end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Octo
|
|
6
|
+
module Utils
|
|
7
|
+
class ArgumentsParser
|
|
8
|
+
# Parse and validate tool call arguments with JSON repair capability
|
|
9
|
+
def self.parse_and_validate(call, tool_registry)
|
|
10
|
+
# 1. Try standard parsing
|
|
11
|
+
begin
|
|
12
|
+
args = JSON.parse(call[:arguments], symbolize_names: true)
|
|
13
|
+
|
|
14
|
+
# Check if any key contains XML tags (< or >) indicating contamination
|
|
15
|
+
# Even though JSON.parse succeeded, the keys might be malformed
|
|
16
|
+
has_xml_contamination = args.keys.any? { |k| k.to_s.include?('<') || k.to_s.include?('>') }
|
|
17
|
+
|
|
18
|
+
if has_xml_contamination
|
|
19
|
+
# Force repair even though JSON.parse succeeded
|
|
20
|
+
raise JSON::ParserError.new("Keys contain XML contamination")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
return validate_required_params(call, args, tool_registry)
|
|
24
|
+
rescue JSON::ParserError => e
|
|
25
|
+
# Continue to repair
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# 2. Try simple repair
|
|
29
|
+
repaired = repair_json(call[:arguments])
|
|
30
|
+
|
|
31
|
+
begin
|
|
32
|
+
args = JSON.parse(repaired, symbolize_names: true)
|
|
33
|
+
return validate_required_params(call, args, tool_registry)
|
|
34
|
+
rescue JSON::ParserError, MissingRequiredParamsError => e
|
|
35
|
+
# 3. Repair failed or missing params, return helpful error
|
|
36
|
+
raise_helpful_error(call, tool_registry, e)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# Simple JSON repair: complete brackets and quotes, and remove XML contamination
|
|
42
|
+
def self.repair_json(json_str)
|
|
43
|
+
|
|
44
|
+
result = json_str.strip
|
|
45
|
+
# Step 0: Convert literal \n (backslash+n) to real newlines
|
|
46
|
+
result = result.gsub(/\\n/, "\n")
|
|
47
|
+
# Step 0.5: Unescape quotes in JSON keys and values (\" -> ")
|
|
48
|
+
# This handles cases like {"end_line\":550 or name=\"path\"
|
|
49
|
+
result = result.gsub(/\\"/, '"')
|
|
50
|
+
# Step 1: Remove XML-style parameter tags that Claude might mix in
|
|
51
|
+
# Pattern 1: </parameter> closing tags - remove completely
|
|
52
|
+
result = result.gsub(/<\/parameter>/, '')
|
|
53
|
+
|
|
54
|
+
# Pattern 2: <parameter name="key"> or <parameter name="key": opening tags -> convert to JSON key
|
|
55
|
+
# Example: \n<parameter name="end_line"> 330 -> , "end_line": 330
|
|
56
|
+
# Also handles: \n<parameter name="end_line": 330 -> , "end_line": 330
|
|
57
|
+
# result = result.gsub(/<parameter\s+name="([^"\\]+)":\s*/) { |match| ", \"#{$1}\": " }
|
|
58
|
+
# result = result.gsub(/<parameter\s+name="([^"\\]+)">/) { |match| ", \"#{$1}\":" }
|
|
59
|
+
result = result.gsub(/<parameter\s+name=\\?"([^"\\]+)\\?"[>:]?\s*/) { |match| ", \"#{$1}\": " }
|
|
60
|
+
|
|
61
|
+
# Pattern 3: Remove any remaining XML-like tags
|
|
62
|
+
result = result.gsub(/<[^>]+>/, '')
|
|
63
|
+
|
|
64
|
+
# Step 2: Clean up newlines with commas
|
|
65
|
+
# Example: 315\n, "end_line" -> 315, "end_line"
|
|
66
|
+
result = result.gsub(/\n\s*,/, ',')
|
|
67
|
+
result = result.gsub(/\n,/, ',')
|
|
68
|
+
result = result.gsub(/,\s*\n/, ',')
|
|
69
|
+
|
|
70
|
+
# Step 3: Clean up formatting issues
|
|
71
|
+
# Remove multiple consecutive commas
|
|
72
|
+
result = result.gsub(/,+/, ',')
|
|
73
|
+
# Remove trailing commas before closing braces/brackets
|
|
74
|
+
result = result.gsub(/,\s*}/, '}')
|
|
75
|
+
result = result.gsub(/,\s*\]/, ']')
|
|
76
|
+
# Remove leading commas after opening braces/brackets
|
|
77
|
+
result = result.gsub(/\{\s*,/, '{')
|
|
78
|
+
result = result.gsub(/\[\s*,/, '[')
|
|
79
|
+
|
|
80
|
+
# Step 4: Complete unclosed strings
|
|
81
|
+
result += '"' if result.count('"').odd?
|
|
82
|
+
|
|
83
|
+
# Step 5: Complete unclosed braces
|
|
84
|
+
depth = 0
|
|
85
|
+
result.each_char { |c| depth += 1 if c == '{'; depth -= 1 if c == '}' }
|
|
86
|
+
result += '}' * depth if depth > 0
|
|
87
|
+
|
|
88
|
+
result
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Validate required parameters and filter unknown parameters
|
|
92
|
+
def self.validate_required_params(call, args, tool_registry)
|
|
93
|
+
tool = tool_registry.get(call[:name])
|
|
94
|
+
required = tool.parameters&.dig(:required) || []
|
|
95
|
+
properties = tool.parameters&.dig(:properties) || {}
|
|
96
|
+
|
|
97
|
+
missing = required.reject { |param|
|
|
98
|
+
args.key?(param.to_sym) || args.key?(param.to_s)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if missing.any?
|
|
102
|
+
raise MissingRequiredParamsError.new(call[:name], missing, args.keys)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Filter out unknown parameters to prevent errors when LLM sends extra arguments
|
|
106
|
+
known_params = properties.keys.map(&:to_sym) + properties.keys.map(&:to_s)
|
|
107
|
+
filtered_args = args.select { |key, _| known_params.include?(key) }
|
|
108
|
+
|
|
109
|
+
filtered_args
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Generate error message with tool definition
|
|
113
|
+
def self.raise_helpful_error(call, tool_registry, original_error)
|
|
114
|
+
tool = tool_registry.get(call[:name])
|
|
115
|
+
error_msg = build_error_message(call, tool, original_error)
|
|
116
|
+
raise BadArgumentsError, error_msg
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def self.build_error_message(call, tool, original_error)
|
|
120
|
+
# Extract tool information
|
|
121
|
+
required_params = tool.parameters&.dig(:required) || []
|
|
122
|
+
|
|
123
|
+
# Try to parse provided parameters from incomplete JSON
|
|
124
|
+
provided_params = extract_provided_params(call[:arguments])
|
|
125
|
+
|
|
126
|
+
# Build clear error message
|
|
127
|
+
msg = []
|
|
128
|
+
msg << "Failed to parse arguments for tool '#{call[:name]}'."
|
|
129
|
+
msg << ""
|
|
130
|
+
msg << "Error: #{original_error.message}"
|
|
131
|
+
msg << ""
|
|
132
|
+
|
|
133
|
+
if provided_params.any?
|
|
134
|
+
msg << "Provided parameters: #{provided_params.join(', ')}"
|
|
135
|
+
else
|
|
136
|
+
msg << "No valid parameters could be extracted."
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
msg << "Required parameters: #{required_params.join(', ')}"
|
|
140
|
+
msg << ""
|
|
141
|
+
msg << "Tool definition:"
|
|
142
|
+
msg << format_tool_definition(tool)
|
|
143
|
+
msg << ""
|
|
144
|
+
msg << "Suggestions:"
|
|
145
|
+
msg << "- If the parameter value is too large (e.g., large file content), consider breaking it into smaller operations"
|
|
146
|
+
msg << "- Ensure all required parameters are provided"
|
|
147
|
+
msg << "- Simplify complex parameter values"
|
|
148
|
+
|
|
149
|
+
msg.join("\n")
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Extract parameter names from incomplete JSON
|
|
153
|
+
def self.extract_provided_params(json_str)
|
|
154
|
+
# Simple extraction: find all "key": patterns
|
|
155
|
+
json_str.scan(/"(\w+)"\s*:/).flatten.uniq
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Format tool definition (concise version)
|
|
159
|
+
def self.format_tool_definition(tool)
|
|
160
|
+
lines = []
|
|
161
|
+
lines << " Name: #{tool.name}"
|
|
162
|
+
lines << " Description: #{tool.description}"
|
|
163
|
+
|
|
164
|
+
if tool.parameters[:properties]
|
|
165
|
+
lines << " Parameters:"
|
|
166
|
+
tool.parameters[:properties].each do |param, spec|
|
|
167
|
+
required_mark = tool.parameters[:required]&.include?(param.to_s) ? " (required)" : ""
|
|
168
|
+
lines << " - #{param}#{required_mark}: #{spec[:description]}"
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
lines.join("\n")
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Raised when tool call arguments are malformed or missing required params.
|
|
177
|
+
class BadArgumentsError < StandardError; end
|
|
178
|
+
|
|
179
|
+
# Custom exception for missing required parameters
|
|
180
|
+
class MissingRequiredParamsError < BadArgumentsError
|
|
181
|
+
attr_reader :tool_name, :missing_params, :provided_params
|
|
182
|
+
|
|
183
|
+
def initialize(tool_name, missing_params, provided_params)
|
|
184
|
+
@tool_name = tool_name
|
|
185
|
+
@missing_params = missing_params
|
|
186
|
+
@provided_params = provided_params
|
|
187
|
+
super("Missing required parameters: #{missing_params.join(', ')}")
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|