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,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubino
|
|
4
|
+
module API
|
|
5
|
+
module Operations
|
|
6
|
+
module Memory
|
|
7
|
+
# GET /v1/memory/stats
|
|
8
|
+
# Returns the active backend's name and total fact count — the data the
|
|
9
|
+
# CLI /status line and the web dashboard's memory card need without
|
|
10
|
+
# paging the whole store.
|
|
11
|
+
class StatsOperation
|
|
12
|
+
def self.call(request)
|
|
13
|
+
new.call(request)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Accepts an alternate backend for tests.
|
|
17
|
+
def initialize(backend: nil)
|
|
18
|
+
@backend = backend || ::Rubino::Memory::Backends.build
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def call(_request)
|
|
22
|
+
[200, { backend: @backend.class.backend_name, count: @backend.count }]
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubino
|
|
4
|
+
module API
|
|
5
|
+
module Operations
|
|
6
|
+
# GET /v1/metrics — Prometheus text exposition format (text/plain v0.0.4).
|
|
7
|
+
# No auth required (allowlisted in Middleware::Auth::SKIP_PATHS).
|
|
8
|
+
#
|
|
9
|
+
# @return [[Integer, Hash, Array<String>]] raw Rack triple with the rendered registry.
|
|
10
|
+
class MetricsOperation
|
|
11
|
+
def self.call(_request)
|
|
12
|
+
body = ::Rubino::Metrics.render
|
|
13
|
+
[200, { "content-type" => "text/plain; version=0.0.4" }, [body]]
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubino
|
|
4
|
+
module API
|
|
5
|
+
module Operations
|
|
6
|
+
module Mode
|
|
7
|
+
# GET /v1/mode
|
|
8
|
+
# Returns the active mode and the list of valid modes so the web
|
|
9
|
+
# client can render a picker without hardcoding the set.
|
|
10
|
+
class ShowOperation
|
|
11
|
+
def self.call(request)
|
|
12
|
+
new.call(request)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def call(_request)
|
|
16
|
+
current = Rubino::Modes.current
|
|
17
|
+
[200, {
|
|
18
|
+
mode: current,
|
|
19
|
+
description: Rubino::Modes.description(current),
|
|
20
|
+
available: Rubino::Modes::ALL.map do |m|
|
|
21
|
+
{ mode: m, description: Rubino::Modes.description(m) }
|
|
22
|
+
end
|
|
23
|
+
}]
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubino
|
|
4
|
+
module API
|
|
5
|
+
module Operations
|
|
6
|
+
module Mode
|
|
7
|
+
# PUT /v1/mode
|
|
8
|
+
# Body: { "mode": "default" | "plan" | "yolo" }
|
|
9
|
+
#
|
|
10
|
+
# Switches the active mode and emits the same `mode_changed` UI event
|
|
11
|
+
# the CLI fires on `/mode plan`, so any in-flight SSE stream notices.
|
|
12
|
+
#
|
|
13
|
+
# @raise [Rubino::ValidationError] on missing/typo'd mode
|
|
14
|
+
class UpdateOperation
|
|
15
|
+
def self.call(request)
|
|
16
|
+
new.call(request)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def call(request)
|
|
20
|
+
attrs = request.validate!(Schemas::UpdateMode)
|
|
21
|
+
previous = Rubino::Modes.current
|
|
22
|
+
|
|
23
|
+
begin
|
|
24
|
+
Rubino::Modes.set(attrs[:mode])
|
|
25
|
+
rescue ArgumentError => e
|
|
26
|
+
# Modes.set already rejects unknowns; surface as a 422 the same
|
|
27
|
+
# way validation errors do. The dry-schema enum below normally
|
|
28
|
+
# catches this first; this is just defence in depth for an
|
|
29
|
+
# alternate caller.
|
|
30
|
+
raise ValidationError.new(e.message)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
current = Rubino::Modes.current
|
|
34
|
+
Rubino.ui&.mode_changed(current, previous: previous)
|
|
35
|
+
|
|
36
|
+
[200, { mode: current, previous: previous, description: Rubino::Modes.description(current) }]
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ruby_llm"
|
|
4
|
+
|
|
5
|
+
module Rubino
|
|
6
|
+
module API
|
|
7
|
+
module Operations
|
|
8
|
+
module Models
|
|
9
|
+
# GET /v1/models — returns the model catalog from ruby_llm.
|
|
10
|
+
# The source defaults to RubyLLM.models.all but accepts any callable
|
|
11
|
+
# returning an enumerable of model objects/hashes for tests.
|
|
12
|
+
class ListOperation
|
|
13
|
+
def self.call(request)
|
|
14
|
+
new.call(request)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Accepts an alternate model source (callable) for tests.
|
|
18
|
+
def initialize(model_source: nil)
|
|
19
|
+
@model_source = model_source || -> { RubyLLM.models.all }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def call(_request)
|
|
23
|
+
models = @model_source.call.map do |m|
|
|
24
|
+
{
|
|
25
|
+
id: model_id(m),
|
|
26
|
+
provider: m.respond_to?(:provider) ? m.provider.to_s : nil,
|
|
27
|
+
context_window: m.respond_to?(:context_window) ? m.context_window : nil
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
[200, models]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def model_id(model)
|
|
36
|
+
return model.id if model.respond_to?(:id)
|
|
37
|
+
return model[:id] if model.is_a?(Hash)
|
|
38
|
+
|
|
39
|
+
model.to_s
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubino
|
|
4
|
+
module API
|
|
5
|
+
module Operations
|
|
6
|
+
module OAuth
|
|
7
|
+
module Connections
|
|
8
|
+
# DELETE /v1/oauth/connections/:id
|
|
9
|
+
# Removes a stored OAuth connection (encrypted tokens included) and
|
|
10
|
+
# asks the provider to revoke the underlying token, so a DB dump +
|
|
11
|
+
# encryption-key compromise no longer yields indefinite provider-side
|
|
12
|
+
# access.
|
|
13
|
+
#
|
|
14
|
+
# Provider revoke is best-effort: a failure (network, 4xx) is logged
|
|
15
|
+
# and the local row is still destroyed — leaving a stale local row is
|
|
16
|
+
# strictly worse than missing the revoke, since the user thinks the
|
|
17
|
+
# connection is gone and we'd keep using the encrypted tokens.
|
|
18
|
+
#
|
|
19
|
+
# @return [[Integer, nil]] 204 No Content.
|
|
20
|
+
# @raise [Rubino::NotFoundError] when the connection does not exist.
|
|
21
|
+
class DisconnectOperation
|
|
22
|
+
def self.call(request)
|
|
23
|
+
new.call(request)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Accepts an alternate repository / registry / logger for tests.
|
|
27
|
+
def initialize(repository: nil, registry: ::Rubino::OAuth::Registry, logger: nil)
|
|
28
|
+
@repository = repository
|
|
29
|
+
@registry = registry
|
|
30
|
+
@logger = logger
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def call(request)
|
|
34
|
+
id = request.params.fetch("id")
|
|
35
|
+
connection = repository.find(id)
|
|
36
|
+
raise NotFoundError.new("oauth_connection", id) unless connection
|
|
37
|
+
|
|
38
|
+
revoke_remote(connection)
|
|
39
|
+
repository.destroy!(id)
|
|
40
|
+
[204, nil]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def revoke_remote(connection)
|
|
46
|
+
provider = @registry.fetch_or_nil(connection[:provider])
|
|
47
|
+
return unless provider&.respond_to?(:revoke)
|
|
48
|
+
|
|
49
|
+
# Prefer refresh_token (Google revokes the whole grant) and fall
|
|
50
|
+
# back to access_token (GitHub only knows about user tokens).
|
|
51
|
+
token = connection[:refresh_token] || connection[:access_token]
|
|
52
|
+
return unless token && !token.empty?
|
|
53
|
+
|
|
54
|
+
provider.revoke(token)
|
|
55
|
+
rescue StandardError => e
|
|
56
|
+
logger.warn(
|
|
57
|
+
event: "oauth.disconnect.revoke_failed",
|
|
58
|
+
provider: connection[:provider],
|
|
59
|
+
connection_id: connection[:id],
|
|
60
|
+
error_class: e.class.name,
|
|
61
|
+
error_message: e.message
|
|
62
|
+
)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def repository
|
|
66
|
+
@repository ||= ::Rubino::OAuth::ConnectionRepository.new
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def logger
|
|
70
|
+
@logger ||= ::Rubino.logger
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubino
|
|
4
|
+
module API
|
|
5
|
+
module Operations
|
|
6
|
+
module OAuth
|
|
7
|
+
module Connections
|
|
8
|
+
# GET /v1/oauth/connections
|
|
9
|
+
# Lists stored OAuth connections through Serializer, which strips
|
|
10
|
+
# tokens and other secret fields before they leave the API.
|
|
11
|
+
class ListOperation
|
|
12
|
+
def self.call(request)
|
|
13
|
+
new.call(request)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Accepts an alternate connection repository for tests.
|
|
17
|
+
def initialize(repository: nil)
|
|
18
|
+
@repository = repository
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def call(_request)
|
|
22
|
+
connections = repository.list.map { |c| Serializer.call(c) }
|
|
23
|
+
[200, connections]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def repository
|
|
29
|
+
@repository ||= ::Rubino::OAuth::ConnectionRepository.new
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubino
|
|
4
|
+
module API
|
|
5
|
+
module Operations
|
|
6
|
+
module OAuth
|
|
7
|
+
module Providers
|
|
8
|
+
# POST /v1/oauth/providers/:id/callback
|
|
9
|
+
#
|
|
10
|
+
# Client posts back code + state + code_verifier (plus expected_state
|
|
11
|
+
# it kept from connect). We constant-time-compare state, exchange the
|
|
12
|
+
# code, fetch account info, upsert by (provider, account_id) with
|
|
13
|
+
# encrypted tokens, and return the connection. Exchange outcomes
|
|
14
|
+
# bump oauth_token_exchanges_total {ok|error}.
|
|
15
|
+
#
|
|
16
|
+
# @return [[Integer, Hash]] 201 + serialized connection (tokens stripped).
|
|
17
|
+
# @raise [Rubino::NotFoundError] when no provider is registered for +:id+.
|
|
18
|
+
# @raise [Rubino::ValidationError] when the body fails Schemas::CallbackProvider or state mismatches.
|
|
19
|
+
# @raise [Rubino::UpstreamError] when the provider's token exchange raises.
|
|
20
|
+
class CallbackOperation
|
|
21
|
+
def self.call(request)
|
|
22
|
+
new.call(request)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Accepts an alternate provider registry and connection repository for tests.
|
|
26
|
+
def initialize(registry: ::Rubino::OAuth::Registry, repository: nil)
|
|
27
|
+
@registry = registry
|
|
28
|
+
@repository = repository
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def call(request)
|
|
32
|
+
id = request.params.fetch("id")
|
|
33
|
+
provider = @registry.fetch(id)
|
|
34
|
+
attrs = request.validate!(Schemas::CallbackProvider)
|
|
35
|
+
|
|
36
|
+
unless Rack::Utils.secure_compare(attrs[:state], attrs[:expected_state])
|
|
37
|
+
raise ValidationError, "state mismatch"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
token =
|
|
41
|
+
begin
|
|
42
|
+
provider.exchange_code(
|
|
43
|
+
code: attrs[:code],
|
|
44
|
+
redirect_uri: attrs[:redirect_uri],
|
|
45
|
+
code_verifier: attrs[:code_verifier]
|
|
46
|
+
)
|
|
47
|
+
rescue StandardError => e
|
|
48
|
+
::Rubino::Metrics.counter(:oauth_token_exchanges_total,
|
|
49
|
+
provider: provider.id, outcome: "error").increment
|
|
50
|
+
raise UpstreamError.new("token exchange failed: #{e.class.name}", service: provider.id)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
::Rubino::Metrics.counter(:oauth_token_exchanges_total,
|
|
54
|
+
provider: provider.id, outcome: "ok").increment
|
|
55
|
+
|
|
56
|
+
info = provider.fetch_account_info(token[:access_token])
|
|
57
|
+
|
|
58
|
+
connection = repository.upsert(
|
|
59
|
+
provider: provider.id,
|
|
60
|
+
account_id: info[:account_id],
|
|
61
|
+
account_email: info[:account_email],
|
|
62
|
+
access_token: token[:access_token],
|
|
63
|
+
refresh_token: token[:refresh_token],
|
|
64
|
+
expires_at: token[:expires_at],
|
|
65
|
+
scopes: token[:scopes],
|
|
66
|
+
metadata: info[:metadata] || {}
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
[201, Serializer.call(connection)]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def repository
|
|
75
|
+
@repository ||= ::Rubino::OAuth::ConnectionRepository.new
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubino
|
|
4
|
+
module API
|
|
5
|
+
module Operations
|
|
6
|
+
module OAuth
|
|
7
|
+
module Providers
|
|
8
|
+
# POST /v1/oauth/providers/:id/connect
|
|
9
|
+
#
|
|
10
|
+
# Builds a PKCE authorize request for the provider and returns
|
|
11
|
+
# { authorize_url, state, code_verifier, provider }. The client
|
|
12
|
+
# persists state + code_verifier between connect and callback;
|
|
13
|
+
# rubino stays stateless on the OAuth flow itself.
|
|
14
|
+
#
|
|
15
|
+
# @raise [Rubino::NotFoundError] when no provider is registered for +:id+.
|
|
16
|
+
# @raise [Rubino::ValidationError] when the body fails Schemas::ConnectProvider.
|
|
17
|
+
class ConnectOperation
|
|
18
|
+
def self.call(request)
|
|
19
|
+
new.call(request)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Accepts an alternate provider registry for tests.
|
|
23
|
+
def initialize(registry: ::Rubino::OAuth::Registry)
|
|
24
|
+
@registry = registry
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def call(request)
|
|
28
|
+
id = request.params.fetch("id")
|
|
29
|
+
provider = @registry.fetch(id)
|
|
30
|
+
attrs = request.validate!(Schemas::ConnectProvider)
|
|
31
|
+
|
|
32
|
+
flow = provider.build_authorize_request(
|
|
33
|
+
redirect_uri: attrs[:redirect_uri],
|
|
34
|
+
scopes: attrs[:scopes]
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
[200, flow.merge(provider: provider.id)]
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubino
|
|
4
|
+
module API
|
|
5
|
+
module Operations
|
|
6
|
+
module OAuth
|
|
7
|
+
module Providers
|
|
8
|
+
# GET /v1/oauth/providers
|
|
9
|
+
# Lists OAuth providers registered at boot, with their default scopes.
|
|
10
|
+
class ListOperation
|
|
11
|
+
def self.call(request)
|
|
12
|
+
new.call(request)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Accepts an alternate provider registry for tests.
|
|
16
|
+
def initialize(registry: ::Rubino::OAuth::Registry)
|
|
17
|
+
@registry = registry
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def call(_request)
|
|
21
|
+
providers = @registry.all.map do |p|
|
|
22
|
+
{
|
|
23
|
+
id: p.id,
|
|
24
|
+
display_name: p.class.display_name,
|
|
25
|
+
scopes: p.scopes
|
|
26
|
+
}
|
|
27
|
+
end
|
|
28
|
+
[200, providers]
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubino
|
|
4
|
+
module API
|
|
5
|
+
module Operations
|
|
6
|
+
module OAuth
|
|
7
|
+
# Strips secret fields from connection rows before they leave the API.
|
|
8
|
+
# Tokens never go on the wire — clients use them implicitly through
|
|
9
|
+
# tools, not directly.
|
|
10
|
+
module Serializer
|
|
11
|
+
PUBLIC_FIELDS = %i[id provider account_id account_email expires_at scopes metadata created_at
|
|
12
|
+
updated_at].freeze
|
|
13
|
+
|
|
14
|
+
def self.call(connection)
|
|
15
|
+
connection.slice(*PUBLIC_FIELDS)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubino
|
|
4
|
+
module API
|
|
5
|
+
module Operations
|
|
6
|
+
module Runs
|
|
7
|
+
# POST /v1/sessions/:id/runs
|
|
8
|
+
# Persists a new run for the session and hands it to the executor.
|
|
9
|
+
# The run is reported as "running" immediately; clients tail
|
|
10
|
+
# /v1/runs/:id/events for state transitions.
|
|
11
|
+
#
|
|
12
|
+
# @return [[Integer, Hash]] 201 + run payload.
|
|
13
|
+
# @raise [Rubino::NotFoundError] when the parent session does not exist.
|
|
14
|
+
# @raise [Rubino::ValidationError] when the body fails Schemas::CreateRun.
|
|
15
|
+
class CreateOperation
|
|
16
|
+
def self.call(request)
|
|
17
|
+
new.call(request)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Accepts alternate collaborators (session repo, run repo, executor) for tests.
|
|
21
|
+
def initialize(session_repository: nil, run_repository: nil, executor: nil)
|
|
22
|
+
@session_repo = session_repository || ::Rubino::Session::Repository.new
|
|
23
|
+
@run_repo = run_repository || ::Rubino::Run::Repository.new
|
|
24
|
+
@executor = executor || ::Rubino::Run::Executor.new
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def call(request)
|
|
28
|
+
session_id = request.params.fetch("id")
|
|
29
|
+
raise NotFoundError.new("session", session_id) unless @session_repo.find(session_id)
|
|
30
|
+
|
|
31
|
+
attrs = request.validate!(Schemas::CreateRun)
|
|
32
|
+
ensure_input_or_attachments!(attrs)
|
|
33
|
+
run = @run_repo.create(
|
|
34
|
+
session_id: session_id,
|
|
35
|
+
input_text: attrs[:input],
|
|
36
|
+
attachments: attrs[:attachments] || [],
|
|
37
|
+
skills: attrs[:skills] || [],
|
|
38
|
+
model: attrs[:model],
|
|
39
|
+
provider: attrs[:provider]
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
@executor.start(run)
|
|
43
|
+
|
|
44
|
+
[201, serialize(run)]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
# A run needs SOMETHING to act on: either text or at least one
|
|
50
|
+
# attachment (image-only runs are valid — the executor fills in a
|
|
51
|
+
# default prompt). The schema allows a blank/absent `input`, so this
|
|
52
|
+
# is where the "input present OR attachments present" rule lives.
|
|
53
|
+
# Reproduces the prior schema's 422 shape so an empty, attachment-less
|
|
54
|
+
# body still fails as `input: ["must be filled"]`.
|
|
55
|
+
def ensure_input_or_attachments!(attrs)
|
|
56
|
+
return if attrs[:input].to_s.strip != ""
|
|
57
|
+
return if Array(attrs[:attachments]).any?
|
|
58
|
+
|
|
59
|
+
raise ValidationError.new(
|
|
60
|
+
"invalid request body",
|
|
61
|
+
details: { errors: { input: ["must be filled"] } }
|
|
62
|
+
)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def serialize(run)
|
|
66
|
+
{
|
|
67
|
+
id: run[:id],
|
|
68
|
+
session_id: run[:session_id],
|
|
69
|
+
status: "running",
|
|
70
|
+
created_at: run[:created_at]
|
|
71
|
+
}
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|