rubino-agent 0.3.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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +115 -0
- data/.rubocop_todo.yml +955 -0
- data/.ruby-version +1 -0
- data/AGENTS.md +97 -0
- data/CHANGELOG.md +344 -0
- data/CONTRIBUTING.md +69 -0
- data/LICENSE +21 -0
- data/README.md +200 -0
- data/Rakefile +8 -0
- data/docs/agents.md +190 -0
- data/docs/api/v1.md +414 -0
- data/docs/architecture.md +177 -0
- data/docs/commands.md +375 -0
- data/docs/configuration.md +590 -0
- data/docs/getting-started.md +143 -0
- data/docs/jobs.md +332 -0
- data/docs/mcp.md +128 -0
- data/docs/memory.md +98 -0
- data/docs/models-and-keys.md +173 -0
- data/docs/oauth-providers.md +145 -0
- data/docs/plugins.md +195 -0
- data/docs/security.md +145 -0
- data/docs/skills.md +322 -0
- data/docs/tools.md +395 -0
- data/docs/troubleshooting.md +73 -0
- data/exe/rubino +9 -0
- data/install.sh +275 -0
- data/lib/rubino/active_skill.rb +50 -0
- data/lib/rubino/agent/agent_registry.rb +120 -0
- data/lib/rubino/agent/backoff_policy.rb +116 -0
- data/lib/rubino/agent/definition.rb +128 -0
- data/lib/rubino/agent/degenerate_recovery.rb +271 -0
- data/lib/rubino/agent/fallback_chain.rb +194 -0
- data/lib/rubino/agent/iteration_budget.rb +50 -0
- data/lib/rubino/agent/loop.rb +617 -0
- data/lib/rubino/agent/model_call_runner.rb +383 -0
- data/lib/rubino/agent/prompts/build.txt +69 -0
- data/lib/rubino/agent/prompts/compaction.txt +20 -0
- data/lib/rubino/agent/prompts/explore.txt +19 -0
- data/lib/rubino/agent/prompts/general.txt +20 -0
- data/lib/rubino/agent/prompts/plan.txt +31 -0
- data/lib/rubino/agent/response_validator.rb +70 -0
- data/lib/rubino/agent/router.rb +65 -0
- data/lib/rubino/agent/runner.rb +195 -0
- data/lib/rubino/agent/tool_executor.rb +402 -0
- data/lib/rubino/agent/truncation_continuation.rb +137 -0
- data/lib/rubino/api/middleware/auth.rb +43 -0
- data/lib/rubino/api/middleware/error_handler.rb +65 -0
- data/lib/rubino/api/middleware/json_parser.rb +100 -0
- data/lib/rubino/api/middleware/observability.rb +59 -0
- data/lib/rubino/api/middleware/rate_limit.rb +136 -0
- data/lib/rubino/api/operations/approvals/decide_operation.rb +49 -0
- data/lib/rubino/api/operations/clarifications/decide_operation.rb +44 -0
- data/lib/rubino/api/operations/cron_jobs/create_operation.rb +46 -0
- data/lib/rubino/api/operations/cron_jobs/delete_operation.rb +36 -0
- data/lib/rubino/api/operations/cron_jobs/list_operation.rb +55 -0
- data/lib/rubino/api/operations/cron_jobs/pause_operation.rb +34 -0
- data/lib/rubino/api/operations/cron_jobs/resume_operation.rb +34 -0
- data/lib/rubino/api/operations/cron_jobs/schedule_validation.rb +30 -0
- data/lib/rubino/api/operations/cron_jobs/show_operation.rb +32 -0
- data/lib/rubino/api/operations/cron_jobs/trigger_operation.rb +38 -0
- data/lib/rubino/api/operations/cron_jobs/update_operation.rb +42 -0
- data/lib/rubino/api/operations/files/read_operation.rb +40 -0
- data/lib/rubino/api/operations/files/upload_operation.rb +175 -0
- data/lib/rubino/api/operations/health_operation.rb +46 -0
- data/lib/rubino/api/operations/memory/delete_operation.rb +32 -0
- data/lib/rubino/api/operations/memory/index_operation.rb +80 -0
- data/lib/rubino/api/operations/memory/stats_operation.rb +28 -0
- data/lib/rubino/api/operations/metrics_operation.rb +18 -0
- data/lib/rubino/api/operations/mode/show_operation.rb +29 -0
- data/lib/rubino/api/operations/mode/update_operation.rb +42 -0
- data/lib/rubino/api/operations/models/list_operation.rb +45 -0
- data/lib/rubino/api/operations/oauth/connections/disconnect_operation.rb +77 -0
- data/lib/rubino/api/operations/oauth/connections/list_operation.rb +36 -0
- data/lib/rubino/api/operations/oauth/providers/callback_operation.rb +82 -0
- data/lib/rubino/api/operations/oauth/providers/connect_operation.rb +44 -0
- data/lib/rubino/api/operations/oauth/providers/list_operation.rb +35 -0
- data/lib/rubino/api/operations/oauth/serializer.rb +21 -0
- data/lib/rubino/api/operations/runs/create_operation.rb +77 -0
- data/lib/rubino/api/operations/runs/events_operation.rb +195 -0
- data/lib/rubino/api/operations/runs/stop_operation.rb +34 -0
- data/lib/rubino/api/operations/sessions/create_operation.rb +46 -0
- data/lib/rubino/api/operations/sessions/delete_operation.rb +33 -0
- data/lib/rubino/api/operations/sessions/index_operation.rb +82 -0
- data/lib/rubino/api/operations/sessions/retry_operation.rb +45 -0
- data/lib/rubino/api/operations/sessions/show_operation.rb +59 -0
- data/lib/rubino/api/operations/sessions/undo_operation.rb +38 -0
- data/lib/rubino/api/operations/skills/list_operation.rb +34 -0
- data/lib/rubino/api/operations/skills/toggle_operation.rb +40 -0
- data/lib/rubino/api/operations/tasks/index_operation.rb +30 -0
- data/lib/rubino/api/operations/tasks/serializer.rb +60 -0
- data/lib/rubino/api/operations/tasks/show_operation.rb +33 -0
- data/lib/rubino/api/operations/tasks/stop_operation.rb +47 -0
- data/lib/rubino/api/request.rb +54 -0
- data/lib/rubino/api/responses.rb +64 -0
- data/lib/rubino/api/router.rb +72 -0
- data/lib/rubino/api/schemas.rb +103 -0
- data/lib/rubino/api/server.rb +102 -0
- data/lib/rubino/api/tls.rb +108 -0
- data/lib/rubino/attachments/classification.rb +16 -0
- data/lib/rubino/attachments/classify.rb +171 -0
- data/lib/rubino/attachments/defang.rb +47 -0
- data/lib/rubino/attachments/policy.rb +36 -0
- data/lib/rubino/attachments/preamble.rb +120 -0
- data/lib/rubino/boot/encryption_key.rb +32 -0
- data/lib/rubino/cli/chat/bang_shell.rb +257 -0
- data/lib/rubino/cli/chat/completion_builder.rb +290 -0
- data/lib/rubino/cli/chat/idle_card_host.rb +69 -0
- data/lib/rubino/cli/chat/image_inbox.rb +168 -0
- data/lib/rubino/cli/chat/session_resolver.rb +176 -0
- data/lib/rubino/cli/chat_command.rb +1674 -0
- data/lib/rubino/cli/commands.rb +250 -0
- data/lib/rubino/cli/config_command.rb +96 -0
- data/lib/rubino/cli/doctor_command.rb +251 -0
- data/lib/rubino/cli/jobs_command.rb +60 -0
- data/lib/rubino/cli/memory_command.rb +135 -0
- data/lib/rubino/cli/onboarding_wizard.rb +207 -0
- data/lib/rubino/cli/server_command.rb +139 -0
- data/lib/rubino/cli/session_command.rb +125 -0
- data/lib/rubino/cli/setup_command.rb +107 -0
- data/lib/rubino/cli/skills_command.rb +85 -0
- data/lib/rubino/cli/tools_command.rb +81 -0
- data/lib/rubino/cli/trust_gate.rb +71 -0
- data/lib/rubino/commands/built_ins.rb +46 -0
- data/lib/rubino/commands/command.rb +116 -0
- data/lib/rubino/commands/executor.rb +550 -0
- data/lib/rubino/commands/handlers/agents.rb +510 -0
- data/lib/rubino/commands/handlers/config.rb +88 -0
- data/lib/rubino/commands/handlers/help.rb +148 -0
- data/lib/rubino/commands/handlers/jobs.rb +71 -0
- data/lib/rubino/commands/handlers/mcp.rb +229 -0
- data/lib/rubino/commands/handlers/memory.rb +200 -0
- data/lib/rubino/commands/handlers/sessions.rb +207 -0
- data/lib/rubino/commands/handlers/skills.rb +195 -0
- data/lib/rubino/commands/handlers/status.rb +211 -0
- data/lib/rubino/commands/loader.rb +90 -0
- data/lib/rubino/config/configuration.rb +455 -0
- data/lib/rubino/config/defaults.rb +569 -0
- data/lib/rubino/config/loader.rb +115 -0
- data/lib/rubino/config/reasoning_prefs.rb +67 -0
- data/lib/rubino/config/writer.rb +72 -0
- data/lib/rubino/context/compressor.rb +149 -0
- data/lib/rubino/context/environment_inspector.rb +176 -0
- data/lib/rubino/context/file_discovery.rb +45 -0
- data/lib/rubino/context/message_boundary.rb +39 -0
- data/lib/rubino/context/prompt_assembler.rb +382 -0
- data/lib/rubino/context/summary_builder.rb +159 -0
- data/lib/rubino/context/token_budget.rb +68 -0
- data/lib/rubino/context/tool_pair_sanitizer.rb +70 -0
- data/lib/rubino/database/connection.rb +77 -0
- data/lib/rubino/database/migrations/001_create_initial_schema.rb +156 -0
- data/lib/rubino/database/migrations/002_create_runs.rb +45 -0
- data/lib/rubino/database/migrations/003_create_skill_states.rb +15 -0
- data/lib/rubino/database/migrations/004_create_cron_jobs.rb +36 -0
- data/lib/rubino/database/migrations/005_create_oauth_connections.rb +27 -0
- data/lib/rubino/database/migrations/006_create_webhook_deliveries.rb +34 -0
- data/lib/rubino/database/migrations/007_create_messages_fts.rb +59 -0
- data/lib/rubino/database/migrations/008_create_memory_facts.rb +75 -0
- data/lib/rubino/database/migrations/009_create_memory_graph.rb +55 -0
- data/lib/rubino/database/migrations/010_add_owner_pid_to_sessions.rb +20 -0
- data/lib/rubino/database/migrator.rb +48 -0
- data/lib/rubino/documents/converters/csv.rb +79 -0
- data/lib/rubino/documents/converters/docx.rb +129 -0
- data/lib/rubino/documents/converters/html.rb +28 -0
- data/lib/rubino/documents/converters/json.rb +35 -0
- data/lib/rubino/documents/converters/pdf.rb +59 -0
- data/lib/rubino/documents/converters/plain.rb +68 -0
- data/lib/rubino/documents/converters/pptx.rb +64 -0
- data/lib/rubino/documents/converters/xlsx.rb +62 -0
- data/lib/rubino/documents/converters/xml.rb +45 -0
- data/lib/rubino/documents/html.rb +71 -0
- data/lib/rubino/documents/registry.rb +68 -0
- data/lib/rubino/documents/table.rb +63 -0
- data/lib/rubino/documents.rb +50 -0
- data/lib/rubino/errors.rb +119 -0
- data/lib/rubino/files/workspace.rb +93 -0
- data/lib/rubino/interaction/cancel_token.rb +43 -0
- data/lib/rubino/interaction/clipboard_image.rb +84 -0
- data/lib/rubino/interaction/event_bus.rb +48 -0
- data/lib/rubino/interaction/events.rb +101 -0
- data/lib/rubino/interaction/image_input.rb +127 -0
- data/lib/rubino/interaction/input_queue.rb +117 -0
- data/lib/rubino/interaction/lifecycle.rb +299 -0
- data/lib/rubino/interaction/probe.rb +65 -0
- data/lib/rubino/interaction/state.rb +56 -0
- data/lib/rubino/jobs/cron_job_repository.rb +75 -0
- data/lib/rubino/jobs/handlers/cleanup_sessions_job.rb +32 -0
- data/lib/rubino/jobs/handlers/compact_session_job.rb +21 -0
- data/lib/rubino/jobs/handlers/distill_skill_job.rb +186 -0
- data/lib/rubino/jobs/handlers/extract_memory_job.rb +37 -0
- data/lib/rubino/jobs/handlers/summarize_session_job.rb +21 -0
- data/lib/rubino/jobs/queue.rb +184 -0
- data/lib/rubino/jobs/registry.rb +45 -0
- data/lib/rubino/jobs/runner.rb +79 -0
- data/lib/rubino/jobs/scheduler.rb +138 -0
- data/lib/rubino/jobs/webhook_delivery.rb +225 -0
- data/lib/rubino/jobs/worker.rb +59 -0
- data/lib/rubino/llm/adapter_factory.rb +47 -0
- data/lib/rubino/llm/adapter_response.rb +65 -0
- data/lib/rubino/llm/auxiliary_client.rb +61 -0
- data/lib/rubino/llm/bedrock_bearer_client.rb +235 -0
- data/lib/rubino/llm/content_builder.rb +55 -0
- data/lib/rubino/llm/credential_check.rb +93 -0
- data/lib/rubino/llm/error_classifier.rb +364 -0
- data/lib/rubino/llm/fake_provider.rb +292 -0
- data/lib/rubino/llm/inline_think_filter.rb +58 -0
- data/lib/rubino/llm/model_catalog.rb +29 -0
- data/lib/rubino/llm/provider_resolver.rb +48 -0
- data/lib/rubino/llm/reasoning_manager.rb +100 -0
- data/lib/rubino/llm/request.rb +56 -0
- data/lib/rubino/llm/ruby_llm_adapter.rb +794 -0
- data/lib/rubino/llm/scenario_loader.rb +68 -0
- data/lib/rubino/llm/scenario_selector.rb +80 -0
- data/lib/rubino/llm/scenarios/agent-creates-cron-failure.yml +29 -0
- data/lib/rubino/llm/scenarios/agent-creates-cron.yml +36 -0
- data/lib/rubino/llm/scenarios/analysis.yml +501 -0
- data/lib/rubino/llm/scenarios/complex-analysis.yml +598 -0
- data/lib/rubino/llm/scenarios/failure.yml +65 -0
- data/lib/rubino/llm/scenarios/happy-path.yml +24 -0
- data/lib/rubino/llm/scenarios/provider-quota-completed.yml +14 -0
- data/lib/rubino/llm/scenarios/wide-table.yml +121 -0
- data/lib/rubino/llm/scenarios/with-approvals.yml +50 -0
- data/lib/rubino/llm/scenarios/with-artifacts.yml +98 -0
- data/lib/rubino/llm/scenarios/with-clarify.yml +32 -0
- data/lib/rubino/llm/scenarios/with-reasoning.yml +175 -0
- data/lib/rubino/llm/scenarios/with-uploads.yml +104 -0
- data/lib/rubino/llm/thinking_support.rb +84 -0
- data/lib/rubino/llm/tool_bridge.rb +89 -0
- data/lib/rubino/logger.rb +99 -0
- data/lib/rubino/mcp/manager.rb +180 -0
- data/lib/rubino/mcp/mcp_tool_wrapper.rb +69 -0
- data/lib/rubino/mcp.rb +57 -0
- data/lib/rubino/memory/backend.rb +104 -0
- data/lib/rubino/memory/backends/default.rb +101 -0
- data/lib/rubino/memory/backends/sqlite.rb +653 -0
- data/lib/rubino/memory/backends.rb +53 -0
- data/lib/rubino/memory/deduplicator.rb +74 -0
- data/lib/rubino/memory/extractor.rb +85 -0
- data/lib/rubino/memory/flusher.rb +31 -0
- data/lib/rubino/memory/retriever.rb +50 -0
- data/lib/rubino/memory/sqlite_extraction_prompt.rb +70 -0
- data/lib/rubino/memory/sqlite_graph.rb +154 -0
- data/lib/rubino/memory/store.rb +228 -0
- data/lib/rubino/memory/threat_scanner.rb +68 -0
- data/lib/rubino/metrics.rb +175 -0
- data/lib/rubino/modes.rb +93 -0
- data/lib/rubino/oauth/connection_repository.rb +95 -0
- data/lib/rubino/oauth/provider/github.rb +75 -0
- data/lib/rubino/oauth/provider/google.rb +59 -0
- data/lib/rubino/oauth/provider.rb +149 -0
- data/lib/rubino/oauth/registry.rb +86 -0
- data/lib/rubino/oauth/token_encryptor.rb +87 -0
- data/lib/rubino/plugins/registry.rb +75 -0
- data/lib/rubino/plugins.rb +86 -0
- data/lib/rubino/run/approval_gate.rb +243 -0
- data/lib/rubino/run/attachment_downloader.rb +166 -0
- data/lib/rubino/run/event_store.rb +74 -0
- data/lib/rubino/run/executor.rb +383 -0
- data/lib/rubino/run/gate_registry.rb +39 -0
- data/lib/rubino/run/recorder.rb +69 -0
- data/lib/rubino/run/repository.rb +118 -0
- data/lib/rubino/run/session_approval_cache.rb +118 -0
- data/lib/rubino/security/allowlist_persister.rb +55 -0
- data/lib/rubino/security/approval_policy.rb +227 -0
- data/lib/rubino/security/command_allowlist.rb +24 -0
- data/lib/rubino/security/dangerous_patterns.rb +118 -0
- data/lib/rubino/security/deny_persister.rb +73 -0
- data/lib/rubino/security/doom_loop_detector.rb +43 -0
- data/lib/rubino/security/hardline_guard.rb +105 -0
- data/lib/rubino/security/pattern_matcher.rb +62 -0
- data/lib/rubino/security/prefix_deriver.rb +124 -0
- data/lib/rubino/security/readonly_commands.rb +211 -0
- data/lib/rubino/session/exporter.rb +101 -0
- data/lib/rubino/session/message.rb +77 -0
- data/lib/rubino/session/repository.rb +295 -0
- data/lib/rubino/session/store.rb +198 -0
- data/lib/rubino/session/summary_store.rb +65 -0
- data/lib/rubino/skills/prompt_index.rb +85 -0
- data/lib/rubino/skills/registry.rb +208 -0
- data/lib/rubino/skills/skill.rb +176 -0
- data/lib/rubino/skills/skill_tool.rb +215 -0
- data/lib/rubino/skills/state_repository.rb +37 -0
- data/lib/rubino/skills/toggle.rb +26 -0
- data/lib/rubino/tools/answer_child_tool.rb +83 -0
- data/lib/rubino/tools/ask_parent_tool.rb +232 -0
- data/lib/rubino/tools/attach_file_tool.rb +120 -0
- data/lib/rubino/tools/background_tasks.rb +520 -0
- data/lib/rubino/tools/base.rb +222 -0
- data/lib/rubino/tools/custom_tool_loader.rb +119 -0
- data/lib/rubino/tools/edit_tool.rb +122 -0
- data/lib/rubino/tools/git_tool.rb +71 -0
- data/lib/rubino/tools/github_tool.rb +233 -0
- data/lib/rubino/tools/glob_tool.rb +69 -0
- data/lib/rubino/tools/grep_tool.rb +206 -0
- data/lib/rubino/tools/memory_tool.rb +184 -0
- data/lib/rubino/tools/multi_edit_tool.rb +110 -0
- data/lib/rubino/tools/patch_tool.rb +260 -0
- data/lib/rubino/tools/probe_tool.rb +175 -0
- data/lib/rubino/tools/question_tool.rb +128 -0
- data/lib/rubino/tools/read_attachment_tool.rb +180 -0
- data/lib/rubino/tools/read_tool.rb +212 -0
- data/lib/rubino/tools/read_tracker.rb +98 -0
- data/lib/rubino/tools/registry.rb +166 -0
- data/lib/rubino/tools/result.rb +113 -0
- data/lib/rubino/tools/ruby_tool.rb +0 -0
- data/lib/rubino/tools/session_search_tool.rb +103 -0
- data/lib/rubino/tools/shell_input_tool.rb +96 -0
- data/lib/rubino/tools/shell_kill_tool.rb +76 -0
- data/lib/rubino/tools/shell_output_tool.rb +72 -0
- data/lib/rubino/tools/shell_registry.rb +158 -0
- data/lib/rubino/tools/shell_tail_tool.rb +118 -0
- data/lib/rubino/tools/shell_tool.rb +330 -0
- data/lib/rubino/tools/steer_tool.rb +118 -0
- data/lib/rubino/tools/subagent_probe.rb +89 -0
- data/lib/rubino/tools/summarize_file_tool.rb +182 -0
- data/lib/rubino/tools/task_result_tool.rb +90 -0
- data/lib/rubino/tools/task_stop_tool.rb +80 -0
- data/lib/rubino/tools/task_tool.rb +622 -0
- data/lib/rubino/tools/test_tool.rb +454 -0
- data/lib/rubino/tools/todo_tool.rb +93 -0
- data/lib/rubino/tools/tool_call_repository.rb +33 -0
- data/lib/rubino/tools/vision_tool.rb +85 -0
- data/lib/rubino/tools/webfetch_tool.rb +153 -0
- data/lib/rubino/tools/websearch_tool.rb +179 -0
- data/lib/rubino/tools/write_tool.rb +61 -0
- data/lib/rubino/trust.rb +88 -0
- data/lib/rubino/ui/api.rb +296 -0
- data/lib/rubino/ui/base.rb +252 -0
- data/lib/rubino/ui/bottom_composer.rb +1599 -0
- data/lib/rubino/ui/cli.rb +1987 -0
- data/lib/rubino/ui/completion_menu.rb +321 -0
- data/lib/rubino/ui/completion_source.rb +284 -0
- data/lib/rubino/ui/escape_reader.rb +169 -0
- data/lib/rubino/ui/indented_io.rb +88 -0
- data/lib/rubino/ui/input_history.rb +108 -0
- data/lib/rubino/ui/live_region.rb +183 -0
- data/lib/rubino/ui/markdown_renderer.rb +506 -0
- data/lib/rubino/ui/notifier.rb +163 -0
- data/lib/rubino/ui/null.rb +195 -0
- data/lib/rubino/ui/paste_store.rb +176 -0
- data/lib/rubino/ui/printer_base.rb +79 -0
- data/lib/rubino/ui/probe_wait_indicator.rb +75 -0
- data/lib/rubino/ui/queued_indicators.rb +66 -0
- data/lib/rubino/ui/status_bar.rb +100 -0
- data/lib/rubino/ui/stdout_proxy.rb +161 -0
- data/lib/rubino/ui/streaming_markdown.rb +186 -0
- data/lib/rubino/ui/subagent_cards.rb +134 -0
- data/lib/rubino/ui/subagent_view.rb +255 -0
- data/lib/rubino/ui.rb +21 -0
- data/lib/rubino/update_check.rb +187 -0
- data/lib/rubino/util/duration.rb +23 -0
- data/lib/rubino/util/hyperlink.rb +105 -0
- data/lib/rubino/util/output.rb +145 -0
- data/lib/rubino/util/secrets_mask.rb +83 -0
- data/lib/rubino/version.rb +5 -0
- data/lib/rubino/workspace.rb +85 -0
- data/lib/rubino-agent.rb +5 -0
- data/lib/rubino.rb +318 -0
- data/mise.toml +2 -0
- data/rubino-agent.gemspec +103 -0
- data/skills/ruby-expert/SKILL.md +67 -0
- data/skills/ruby-expert/references/concurrency.md +357 -0
- data/skills/ruby-expert/references/datetime-and-encoding.md +363 -0
- data/skills/ruby-expert/references/errors-and-types.md +460 -0
- data/skills/ruby-expert/references/gem-authoring.md +459 -0
- data/skills/ruby-expert/references/language-idioms.md +465 -0
- data/skills/ruby-expert/references/metaprogramming.md +339 -0
- data/skills/ruby-expert/references/oo-design.md +553 -0
- data/skills/ruby-expert/references/performance.md +383 -0
- data/skills/ruby-expert/references/rails.md +424 -0
- data/skills/ruby-expert/references/security.md +404 -0
- data/skills/ruby-expert/references/testing.md +473 -0
- data/skills/ruby-expert/references/tooling.md +466 -0
- metadata +856 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubino
|
|
4
|
+
module LLM
|
|
5
|
+
# Streaming filter that splits text into :content and :thinking events by
|
|
6
|
+
# recognising inline <think>...</think> sentinels emitted by MiniMax,
|
|
7
|
+
# DeepSeek-R1, Qwen, and similar reasoning models that don't expose a
|
|
8
|
+
# dedicated reasoning channel.
|
|
9
|
+
#
|
|
10
|
+
# Holds back up to TAG_MAX_LEN-1 chars across chunks so a tag split between
|
|
11
|
+
# chunks (e.g. "<thi" + "nk>") still gets matched. Call #flush at end of
|
|
12
|
+
# stream to drain any tail.
|
|
13
|
+
class InlineThinkFilter
|
|
14
|
+
OPEN_RE = /<think>/i
|
|
15
|
+
CLOSE_RE = %r{</think>}i
|
|
16
|
+
TAG_MAX_LEN = "</think>".length
|
|
17
|
+
|
|
18
|
+
def initialize
|
|
19
|
+
@inside = false
|
|
20
|
+
@pending = +""
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def feed(chunk)
|
|
24
|
+
@pending << chunk
|
|
25
|
+
loop do
|
|
26
|
+
re, sentinel = @inside ? [CLOSE_RE, :thinking] : [OPEN_RE, :content]
|
|
27
|
+
match = @pending.match(re)
|
|
28
|
+
|
|
29
|
+
if match
|
|
30
|
+
idx = match.begin(0)
|
|
31
|
+
tag_len = match[0].length
|
|
32
|
+
emit = @pending.slice!(0, idx)
|
|
33
|
+
@pending.slice!(0, tag_len)
|
|
34
|
+
yield sentinel, emit unless emit.empty?
|
|
35
|
+
@inside = !@inside
|
|
36
|
+
else
|
|
37
|
+
# Hold back last (TAG_MAX_LEN-1) chars in case the next chunk
|
|
38
|
+
# completes a tag that began at the tail of @pending.
|
|
39
|
+
safe_len = @pending.length - (TAG_MAX_LEN - 1)
|
|
40
|
+
if safe_len.positive?
|
|
41
|
+
emit = @pending.slice!(0, safe_len)
|
|
42
|
+
yield sentinel, emit unless emit.empty?
|
|
43
|
+
end
|
|
44
|
+
break
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def flush
|
|
50
|
+
return if @pending.empty?
|
|
51
|
+
|
|
52
|
+
sentinel = @inside ? :thinking : :content
|
|
53
|
+
yield sentinel, @pending
|
|
54
|
+
@pending = +""
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubino
|
|
4
|
+
module LLM
|
|
5
|
+
# Enumerates the model ids the ruby_llm registry knows for a provider —
|
|
6
|
+
# the source behind `/model` (bare listing + dropdown completion). Custom
|
|
7
|
+
# backends (minimax/gateway/anthropic-compatible proxies) are not registry
|
|
8
|
+
# providers, so they enumerate to [] and `/model` degrades to the
|
|
9
|
+
# current-model + usage-hint view; no hardcoded global list is invented.
|
|
10
|
+
module ModelCatalog
|
|
11
|
+
# ProviderResolver speaks "google"; the ruby_llm registry files the same
|
|
12
|
+
# models under "gemini". Only mismatch between the two vocabularies.
|
|
13
|
+
REGISTRY_ALIASES = { "google" => "gemini" }.freeze
|
|
14
|
+
|
|
15
|
+
module_function
|
|
16
|
+
|
|
17
|
+
# Model ids for +provider+, [] when the registry can't enumerate it.
|
|
18
|
+
def ids_for(provider)
|
|
19
|
+
return [] if provider.to_s.empty?
|
|
20
|
+
|
|
21
|
+
require "ruby_llm"
|
|
22
|
+
registry_name = REGISTRY_ALIASES.fetch(provider.to_s, provider.to_s)
|
|
23
|
+
RubyLLM.models.by_provider(registry_name).map(&:id)
|
|
24
|
+
rescue StandardError
|
|
25
|
+
[]
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubino
|
|
4
|
+
module LLM
|
|
5
|
+
# Resolves provider from model name or explicit configuration.
|
|
6
|
+
#
|
|
7
|
+
# Single place that interprets "auto": an explicit (non-"auto") provider is
|
|
8
|
+
# returned verbatim; nil/"auto" first honours the Bedrock-bearer override
|
|
9
|
+
# (Mantle short-term key → anthropic provider) and then falls back to
|
|
10
|
+
# pattern-matching the model id. AdapterFactory resolves once here and hands
|
|
11
|
+
# the concrete provider to RubyLLMAdapter, which no longer re-resolves.
|
|
12
|
+
class ProviderResolver
|
|
13
|
+
PROVIDER_PATTERNS = {
|
|
14
|
+
"fake" => /\Afake/i,
|
|
15
|
+
"openai" => /\A(openai|gpt|o1|o3|o4)/i,
|
|
16
|
+
"anthropic" => /\A(anthropic(?!\.)|claude)/i,
|
|
17
|
+
"google" => /\A(google|gemini)/i,
|
|
18
|
+
"bedrock" => /\A(anthropic\.|amazon\.|meta\.|mistral\.|cohere\.|ai21\.)/i,
|
|
19
|
+
"deepseek" => /\Adeepseek/i,
|
|
20
|
+
"mistral" => /\A(mistral|mixtral)/i,
|
|
21
|
+
"minimax" => /\A(minimax|abab)/i,
|
|
22
|
+
"qwen" => /\Aqwen/i
|
|
23
|
+
}.freeze
|
|
24
|
+
|
|
25
|
+
def self.resolve(model_id, explicit_provider: nil)
|
|
26
|
+
return explicit_provider if explicit_provider && explicit_provider != "auto"
|
|
27
|
+
|
|
28
|
+
# Bedrock bearer-token mode (Mantle short-term key: API key set, no
|
|
29
|
+
# secret) always routes through the Anthropic provider regardless of
|
|
30
|
+
# model id. Part of the "auto" interpretation, kept here so it lives in
|
|
31
|
+
# exactly one place (was duplicated in RubyLLMAdapter#resolve_provider).
|
|
32
|
+
return "anthropic" if bedrock_bearer_env?
|
|
33
|
+
|
|
34
|
+
PROVIDER_PATTERNS.each do |provider, pattern|
|
|
35
|
+
return provider if model_id.to_s.match?(pattern)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
"openai" # Default fallback
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# True when the environment carries a Bedrock bearer token (API key set,
|
|
42
|
+
# no secret key) — the Mantle short-term credential mode.
|
|
43
|
+
def self.bedrock_bearer_env?
|
|
44
|
+
!ENV["BEDROCK_API_KEY"].to_s.empty? && ENV["BEDROCK_SECRET_KEY"].to_s.empty?
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubino
|
|
4
|
+
module LLM
|
|
5
|
+
# Renders the reasoning/thinking configuration to the Anthropic-compat wire
|
|
6
|
+
# params (manual mode). This is the Ruby port of the reference reasoning_config →
|
|
7
|
+
# `thinking` mapping: on the manual path
|
|
8
|
+
# (MiniMax /anthropic, older Anthropic, bedrock) thinking is enabled with a
|
|
9
|
+
# token budget, which FORCES temperature=1 and bumps max_tokens so the budget
|
|
10
|
+
# fits under it with text headroom to still answer.
|
|
11
|
+
#
|
|
12
|
+
# The numbers (budget 8000 "medium", text headroom 4096, 16384 ceiling) are
|
|
13
|
+
# sourced from config (model.thinking_budget / model.max_tokens_text_headroom
|
|
14
|
+
# / model.max_tokens) by the adapter and passed in — this object holds no
|
|
15
|
+
# magic numbers of its own; it only mirrors the reference combination rules.
|
|
16
|
+
#
|
|
17
|
+
# One source of truth: the adapter calls #render exactly once per chat build
|
|
18
|
+
# to derive the params, and applies them; the inline Slice 0(c) logic that
|
|
19
|
+
# used to live in RubyLLMAdapter#apply_generation_params now lives here.
|
|
20
|
+
class ReasoningManager
|
|
21
|
+
# The rendered wire params. +thinking+ is the Anthropic manual-mode block
|
|
22
|
+
# (nil when disabled), +temperature+ is forced to 1 with thinking on (else
|
|
23
|
+
# the configured value, possibly nil ⇒ provider default), +max_tokens+ is
|
|
24
|
+
# the ceiling grown to fit budget + headroom (nil ⇒ leave provider default).
|
|
25
|
+
Rendered = Struct.new(:thinking, :temperature, :max_tokens, keyword_init: true) do
|
|
26
|
+
def thinking_enabled?
|
|
27
|
+
!thinking.nil?
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Render the reasoning config to wire params.
|
|
32
|
+
#
|
|
33
|
+
# budget : Integer — thinking token budget; 0/nil disables thinking
|
|
34
|
+
# temperature : Float|nil — configured sampling temperature (ignored when
|
|
35
|
+
# thinking is enabled — Anthropic requires 1 then)
|
|
36
|
+
# max_tokens : Integer|nil — configured output ceiling; nil ⇒ leave the
|
|
37
|
+
# provider default UNLESS thinking forces a floor
|
|
38
|
+
# text_headroom : Integer — visible-output tokens reserved on top of budget
|
|
39
|
+
# apply_max_tokens: Bool — only the anthropic-family path raises the ceiling;
|
|
40
|
+
# openai/ollama/etc. leave token limits to the provider
|
|
41
|
+
#
|
|
42
|
+
# Mirrors anthropic_adapter.py:2238–2241:
|
|
43
|
+
# kwargs["thinking"] = {type: enabled, budget_tokens: budget}
|
|
44
|
+
# kwargs["temperature"] = 1
|
|
45
|
+
# kwargs["max_tokens"] = max(effective_max_tokens, budget + headroom)
|
|
46
|
+
def render(budget:, temperature: nil, max_tokens: nil,
|
|
47
|
+
text_headroom: 4096, apply_max_tokens: true)
|
|
48
|
+
budget = budget.to_i
|
|
49
|
+
enabled = budget.positive?
|
|
50
|
+
|
|
51
|
+
Rendered.new(
|
|
52
|
+
thinking: enabled ? { type: :enabled, budget_tokens: budget } : nil,
|
|
53
|
+
temperature: render_temperature(enabled, temperature),
|
|
54
|
+
max_tokens: apply_max_tokens ? render_max_tokens(enabled, budget, max_tokens, text_headroom) : nil
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Echo-back seam (reference reapply_reasoning_echo_for_provider): in the
|
|
59
|
+
# reference, prior-turn assistant
|
|
60
|
+
# reasoning_content is re-padded onto the api copy for providers that
|
|
61
|
+
# REQUIRE it back (DeepSeek/Kimi/MiMo thinking mode) or it 400s.
|
|
62
|
+
#
|
|
63
|
+
# DOCUMENTED NO-OP SEAM (per the boundary spike): the installed
|
|
64
|
+
# ruby_llm 1.15 RubyLLM::Message exposes +thinking+ as a read-only attr
|
|
65
|
+
# (no setter, only an initializer keyword) and our anthropic-compat target
|
|
66
|
+
# (MiniMax /anthropic) does NOT require reasoning echo-back — only the
|
|
67
|
+
# OpenAI-compat require-side providers do, which we do not yet target.
|
|
68
|
+
# So there is no transport to carry reasoning back through cleanly today;
|
|
69
|
+
# fabricating one would be wrong. This stays a no-op until a require-side
|
|
70
|
+
# provider lands (Slice 7 fallback may surface one) and ruby_llm offers a
|
|
71
|
+
# reachable reasoning field on replayed messages.
|
|
72
|
+
#
|
|
73
|
+
# Returns +history+ unchanged.
|
|
74
|
+
def carry(history)
|
|
75
|
+
# TODO(slice-7+): when a require-side provider (DeepSeek/Kimi/MiMo) is
|
|
76
|
+
# supported and ruby_llm exposes a settable reasoning field on replayed
|
|
77
|
+
# assistant messages, fold prior-turn reasoning back here.
|
|
78
|
+
history
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def render_temperature(enabled, temperature)
|
|
84
|
+
return 1 if enabled
|
|
85
|
+
|
|
86
|
+
temperature
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def render_max_tokens(enabled, budget, max_tokens, text_headroom)
|
|
90
|
+
ceiling = max_tokens
|
|
91
|
+
floor = budget + text_headroom.to_i
|
|
92
|
+
ceiling = [ceiling.to_i, floor].max if enabled && ceiling
|
|
93
|
+
ceiling = floor if enabled && ceiling.nil?
|
|
94
|
+
return nil unless ceiling&.positive?
|
|
95
|
+
|
|
96
|
+
ceiling
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubino
|
|
4
|
+
module LLM
|
|
5
|
+
# The single value object the conversation loop hands the LLM boundary on
|
|
6
|
+
# each model call. Pure data — it carries everything a provider needs to
|
|
7
|
+
# issue one request, so the loop never threads positional args through the
|
|
8
|
+
# adapter. Mirrors the reference per-call request shape feeding the
|
|
9
|
+
# normalize_response seam: build a request,
|
|
10
|
+
# call the boundary, read back a normalized response.
|
|
11
|
+
#
|
|
12
|
+
# Fields:
|
|
13
|
+
# messages : [{role:, content:, tool_calls?, tool_call_id?}] — api copy
|
|
14
|
+
# tools : [tool schema] — may be [] (e.g. max-iter toolless summary)
|
|
15
|
+
# temperature : Float | nil — nil ⇒ provider default; forced to 1 w/ thinking
|
|
16
|
+
# max_tokens : Integer | nil — bumped on thinking + truncation continuation
|
|
17
|
+
# thinking : {enabled:, effort:|budget:} | nil — rendered to wire later
|
|
18
|
+
# prefill : String | nil — assistant-turn seed for prefill-to-continue
|
|
19
|
+
# image_paths : [path] — native attachments, first call of a turn only
|
|
20
|
+
# stream : Bool — loop decides (interactive turn ⇒ false)
|
|
21
|
+
class Request
|
|
22
|
+
attr_reader :messages, :tools, :temperature, :max_tokens, :thinking,
|
|
23
|
+
:prefill, :image_paths, :stream
|
|
24
|
+
|
|
25
|
+
def initialize(messages:, tools: nil, temperature: nil, max_tokens: nil,
|
|
26
|
+
thinking: nil, prefill: nil, image_paths: nil, stream: false)
|
|
27
|
+
@messages = messages || []
|
|
28
|
+
@tools = tools || []
|
|
29
|
+
@temperature = temperature
|
|
30
|
+
@max_tokens = max_tokens
|
|
31
|
+
@thinking = thinking
|
|
32
|
+
@prefill = prefill
|
|
33
|
+
@image_paths = image_paths || []
|
|
34
|
+
@stream = stream ? true : false
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# True when the loop asked the boundary to stream this call.
|
|
38
|
+
def stream?
|
|
39
|
+
@stream
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def to_h
|
|
43
|
+
{
|
|
44
|
+
messages: @messages,
|
|
45
|
+
tools: @tools,
|
|
46
|
+
temperature: @temperature,
|
|
47
|
+
max_tokens: @max_tokens,
|
|
48
|
+
thinking: @thinking,
|
|
49
|
+
prefill: @prefill,
|
|
50
|
+
image_paths: @image_paths,
|
|
51
|
+
stream: @stream
|
|
52
|
+
}
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|