riffer-rig 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +146 -0
- data/README.md +29 -23
- data/lib/riffer/rig/acp/host.rb +67 -24
- data/lib/riffer/rig/acp/mapper.rb +242 -0
- data/lib/riffer/rig/acp.rb +125 -122
- data/lib/riffer/rig/bundled/bash.rb +5 -1
- data/lib/riffer/rig/cli/flags.rb +59 -32
- data/lib/riffer/rig/cli/sessions.rb +52 -29
- data/lib/riffer/rig/cli.rb +24 -13
- data/lib/riffer/rig/command/context.rb +1 -1
- data/lib/riffer/rig/command.rb +4 -2
- data/lib/riffer/rig/commands/auth.rb +23 -23
- data/lib/riffer/rig/commands/cost.rb +59 -0
- data/lib/riffer/rig/commands/model.rb +24 -18
- data/lib/riffer/rig/commands/reasoning.rb +46 -0
- data/lib/riffer/rig/commands/reload.rb +1 -1
- data/lib/riffer/rig/commands/skill.rb +1 -1
- data/lib/riffer/rig/commands/trace/replay.rb +88 -0
- data/lib/riffer/rig/commands/trace.rb +25 -0
- data/lib/riffer/rig/credentials/provider_fields.rb +47 -0
- data/lib/riffer/rig/credentials.rb +94 -48
- data/lib/riffer/rig/env.rb +4 -2
- data/lib/riffer/rig/events/after_response.rb +4 -11
- data/lib/riffer/rig/events/after_tool_call.rb +15 -13
- data/lib/riffer/rig/events/before_prompt.rb +4 -11
- data/lib/riffer/rig/events/before_request.rb +4 -11
- data/lib/riffer/rig/events/before_tool_call.rb +12 -13
- data/lib/riffer/rig/events/command_output.rb +4 -11
- data/lib/riffer/rig/events/job_exit.rb +28 -0
- data/lib/riffer/rig/events/notify.rb +4 -11
- data/lib/riffer/rig/events/session_end.rb +4 -11
- data/lib/riffer/rig/events/session_start.rb +4 -11
- data/lib/riffer/rig/events/skill_activated.rb +4 -11
- data/lib/riffer/rig/events/turn_end.rb +4 -11
- data/lib/riffer/rig/extension.rb +5 -4
- data/lib/riffer/rig/headless/host.rb +2 -14
- data/lib/riffer/rig/headless/ndjson.rb +1 -1
- data/lib/riffer/rig/headless.rb +122 -26
- data/lib/riffer/rig/hosts/mirror.rb +8 -5
- data/lib/riffer/rig/hosts/non_interactive.rb +24 -0
- data/lib/riffer/rig/hosts/null.rb +2 -21
- data/lib/riffer/rig/loader/credentials.rb +23 -0
- data/lib/riffer/rig/loader/extension_source.rb +177 -0
- data/lib/riffer/rig/loader/options.rb +39 -0
- data/lib/riffer/rig/loader.rb +148 -327
- data/lib/riffer/rig/mcp/auth.rb +3 -1
- data/lib/riffer/rig/mcp/declaration.rb +1 -1
- data/lib/riffer/rig/provider_setup.rb +2 -2
- data/lib/riffer/rig/providers.rb +5 -0
- data/lib/riffer/rig/registrar.rb +4 -2
- data/lib/riffer/rig/runtime/abort.rb +66 -0
- data/lib/riffer/rig/runtime/concurrency.rb +71 -0
- data/lib/riffer/rig/runtime/error_message.rb +27 -0
- data/lib/riffer/rig/runtime/hooks.rb +6 -2
- data/lib/riffer/rig/runtime/job_completion.rb +31 -0
- data/lib/riffer/rig/runtime/job_envelope.rb +36 -0
- data/lib/riffer/rig/runtime/job_inbox.rb +74 -0
- data/lib/riffer/rig/runtime/job_notification.rb +31 -0
- data/lib/riffer/rig/runtime/mcp_registrar.rb +91 -0
- data/lib/riffer/rig/runtime/request_guardrail.rb +1 -1
- data/lib/riffer/rig/runtime/snapshot.rb +84 -0
- data/lib/riffer/rig/runtime/system_prompt.rb +56 -0
- data/lib/riffer/rig/runtime/tool_runtime.rb +47 -7
- data/lib/riffer/rig/runtime/usage.rb +19 -0
- data/lib/riffer/rig/runtime.rb +368 -250
- data/lib/riffer/rig/sdk.rb +29 -7
- data/lib/riffer/rig/settings/document.rb +5 -5
- data/lib/riffer/rig/settings/file.rb +34 -0
- data/lib/riffer/rig/settings/model_options.rb +44 -0
- data/lib/riffer/rig/settings.rb +4 -130
- data/lib/riffer/rig/stores/entries.rb +26 -0
- data/lib/riffer/rig/stores/header.rb +3 -17
- data/lib/riffer/rig/stores/jsonl.rb +8 -6
- data/lib/riffer/rig/stores/message_entry.rb +10 -10
- data/lib/riffer/rig/stores/model_entry.rb +10 -10
- data/lib/riffer/rig/stores/reasoning_entry.rb +31 -0
- data/lib/riffer/rig/stores/recorder.rb +45 -17
- data/lib/riffer/rig/stores/skill_entry.rb +10 -10
- data/lib/riffer/rig/stores/stamped.rb +9 -0
- data/lib/riffer/rig/stores/timestamp.rb +23 -0
- data/lib/riffer/rig/support/json_file.rb +53 -0
- data/lib/riffer/rig/support/typed_value/schema.rb +16 -0
- data/lib/riffer/rig/support/typed_value.rb +17 -0
- data/lib/riffer/rig/terminal/autocomplete.rb +133 -0
- data/lib/riffer/rig/terminal/banner.rb +48 -11
- data/lib/riffer/rig/terminal/blocking_prompter.rb +37 -0
- data/lib/riffer/rig/terminal/builtin.rb +16 -0
- data/lib/riffer/rig/terminal/columns.rb +63 -0
- data/lib/riffer/rig/terminal/console.rb +61 -0
- data/lib/riffer/rig/terminal/count.rb +14 -0
- data/lib/riffer/rig/terminal/echo.rb +26 -0
- data/lib/riffer/rig/terminal/frame.rb +464 -0
- data/lib/riffer/rig/terminal/frame_prompter.rb +65 -0
- data/lib/riffer/rig/terminal/help.rb +41 -0
- data/lib/riffer/rig/terminal/host.rb +7 -28
- data/lib/riffer/rig/terminal/idle_ticker.rb +28 -0
- data/lib/riffer/rig/terminal/job_counter.rb +33 -0
- data/lib/riffer/rig/terminal/job_exit_notice.rb +11 -0
- data/lib/riffer/rig/terminal/key.rb +38 -0
- data/lib/riffer/rig/terminal/key_decoder.rb +283 -0
- data/lib/riffer/rig/terminal/key_pipeline.rb +119 -0
- data/lib/riffer/rig/terminal/line_editor.rb +191 -0
- data/lib/riffer/rig/terminal/loading.rb +71 -0
- data/lib/riffer/rig/terminal/markdown/blocks.rb +96 -0
- data/lib/riffer/rig/terminal/markdown/flow.rb +77 -0
- data/lib/riffer/rig/terminal/markdown/inline.rb +85 -0
- data/lib/riffer/rig/terminal/markdown/span.rb +25 -0
- data/lib/riffer/rig/terminal/markdown/table.rb +166 -0
- data/lib/riffer/rig/terminal/markdown.rb +187 -0
- data/lib/riffer/rig/terminal/message_queue/message.rb +13 -0
- data/lib/riffer/rig/terminal/message_queue.rb +27 -0
- data/lib/riffer/rig/terminal/opening.rb +50 -0
- data/lib/riffer/rig/terminal/peek.rb +74 -0
- data/lib/riffer/rig/terminal/picker.rb +47 -22
- data/lib/riffer/rig/terminal/question.rb +37 -0
- data/lib/riffer/rig/terminal/quiet_display.rb +48 -0
- data/lib/riffer/rig/terminal/renderer.rb +19 -250
- data/lib/riffer/rig/terminal/repl.rb +250 -0
- data/lib/riffer/rig/terminal/replay/turn.rb +23 -0
- data/lib/riffer/rig/terminal/replay.rb +76 -0
- data/lib/riffer/rig/terminal/session.rb +2 -6
- data/lib/riffer/rig/terminal/spinner.rb +13 -0
- data/lib/riffer/rig/terminal/status_bar.rb +126 -0
- data/lib/riffer/rig/terminal/tally.rb +12 -0
- data/lib/riffer/rig/terminal/theme.rb +34 -1
- data/lib/riffer/rig/terminal/ticker.rb +109 -0
- data/lib/riffer/rig/terminal/ticker_display.rb +87 -0
- data/lib/riffer/rig/terminal/turn_buffer/stats.rb +19 -0
- data/lib/riffer/rig/terminal/turn_buffer.rb +133 -0
- data/lib/riffer/rig/terminal/turn_report.rb +100 -0
- data/lib/riffer/rig/terminal/wrapper.rb +59 -63
- data/lib/riffer/rig/terminal.rb +237 -110
- data/lib/riffer/rig/tools/bash/capture.rb +58 -0
- data/lib/riffer/rig/tools/bash/job.rb +84 -0
- data/lib/riffer/rig/tools/bash/job_log.rb +117 -0
- data/lib/riffer/rig/tools/bash/jobs.rb +197 -0
- data/lib/riffer/rig/tools/bash/kill.rb +24 -0
- data/lib/riffer/rig/tools/bash/kill_group.rb +15 -0
- data/lib/riffer/rig/tools/bash/monotonic_clock.rb +8 -0
- data/lib/riffer/rig/tools/bash/output.rb +30 -0
- data/lib/riffer/rig/tools/bash/truncation.rb +12 -0
- data/lib/riffer/rig/tools/bash.rb +49 -29
- data/lib/riffer/rig/tools/read.rb +2 -2
- data/lib/riffer/rig/trust.rb +9 -18
- data/lib/riffer/rig/version.rb +1 -1
- data/sig/generated/riffer/rig/acp/host.rbs +29 -8
- data/sig/generated/riffer/rig/acp/mapper.rbs +108 -0
- data/sig/generated/riffer/rig/acp.rbs +46 -53
- data/sig/generated/riffer/rig/cli/flags.rbs +20 -18
- data/sig/generated/riffer/rig/cli/sessions.rbs +25 -11
- data/sig/generated/riffer/rig/cli.rbs +6 -3
- data/sig/generated/riffer/rig/command.rbs +5 -3
- data/sig/generated/riffer/rig/commands/auth.rbs +3 -7
- data/sig/generated/riffer/rig/commands/cost.rbs +31 -0
- data/sig/generated/riffer/rig/commands/model.rbs +2 -0
- data/sig/generated/riffer/rig/commands/reasoning.rbs +16 -0
- data/sig/generated/riffer/rig/commands/trace/replay.rbs +53 -0
- data/sig/generated/riffer/rig/commands/trace.rbs +14 -0
- data/sig/generated/riffer/rig/credentials/provider_fields.rbs +26 -0
- data/sig/generated/riffer/rig/credentials.rbs +36 -15
- data/sig/generated/riffer/rig/env.rbs +3 -1
- data/sig/generated/riffer/rig/events/after_response.rbs +3 -7
- data/sig/generated/riffer/rig/events/after_tool_call.rbs +14 -9
- data/sig/generated/riffer/rig/events/before_prompt.rbs +3 -7
- data/sig/generated/riffer/rig/events/before_request.rbs +3 -7
- data/sig/generated/riffer/rig/events/before_tool_call.rbs +11 -9
- data/sig/generated/riffer/rig/events/command_output.rbs +3 -7
- data/sig/generated/riffer/rig/events/job_exit.rbs +24 -0
- data/sig/generated/riffer/rig/events/notify.rbs +3 -7
- data/sig/generated/riffer/rig/events/session_end.rbs +3 -7
- data/sig/generated/riffer/rig/events/session_start.rbs +3 -7
- data/sig/generated/riffer/rig/events/skill_activated.rbs +3 -7
- data/sig/generated/riffer/rig/events/turn_end.rbs +3 -7
- data/sig/generated/riffer/rig/extension.rbs +3 -3
- data/sig/generated/riffer/rig/headless/host.rbs +2 -10
- data/sig/generated/riffer/rig/headless.rbs +41 -10
- data/sig/generated/riffer/rig/hosts/mirror.rbs +7 -6
- data/sig/generated/riffer/rig/hosts/non_interactive.rbs +18 -0
- data/sig/generated/riffer/rig/hosts/null.rbs +2 -15
- data/sig/generated/riffer/rig/loader/credentials.rbs +12 -0
- data/sig/generated/riffer/rig/loader/extension_source.rbs +89 -0
- data/sig/generated/riffer/rig/loader/options.rbs +28 -0
- data/sig/generated/riffer/rig/loader.rbs +57 -133
- data/sig/generated/riffer/rig/provider_setup.rbs +1 -1
- data/sig/generated/riffer/rig/providers.rbs +3 -0
- data/sig/generated/riffer/rig/runtime/abort.rbs +36 -0
- data/sig/generated/riffer/rig/runtime/concurrency.rbs +41 -0
- data/sig/generated/riffer/rig/runtime/error_message.rbs +13 -0
- data/sig/generated/riffer/rig/runtime/hooks.rbs +3 -1
- data/sig/generated/riffer/rig/runtime/job_completion.rbs +28 -0
- data/sig/generated/riffer/rig/runtime/job_envelope.rbs +17 -0
- data/sig/generated/riffer/rig/runtime/job_inbox.rbs +40 -0
- data/sig/generated/riffer/rig/runtime/job_notification.rbs +22 -0
- data/sig/generated/riffer/rig/runtime/mcp_registrar.rbs +46 -0
- data/sig/generated/riffer/rig/runtime/snapshot.rbs +45 -0
- data/sig/generated/riffer/rig/runtime/system_prompt.rbs +37 -0
- data/sig/generated/riffer/rig/runtime/tool_runtime.rbs +25 -1
- data/sig/generated/riffer/rig/runtime/usage.rbs +11 -0
- data/sig/generated/riffer/rig/runtime.rbs +150 -96
- data/sig/generated/riffer/rig/sdk.rbs +11 -1
- data/sig/generated/riffer/rig/settings/document.rbs +2 -2
- data/sig/generated/riffer/rig/settings/file.rbs +24 -0
- data/sig/generated/riffer/rig/settings/model_options.rbs +25 -0
- data/sig/generated/riffer/rig/settings.rbs +0 -66
- data/sig/generated/riffer/rig/stores/entries.rbs +12 -0
- data/sig/generated/riffer/rig/stores/header.rbs +2 -5
- data/sig/generated/riffer/rig/stores/message_entry.rbs +9 -7
- data/sig/generated/riffer/rig/stores/model_entry.rbs +9 -7
- data/sig/generated/riffer/rig/stores/reasoning_entry.rbs +23 -0
- data/sig/generated/riffer/rig/stores/recorder.rbs +24 -8
- data/sig/generated/riffer/rig/stores/skill_entry.rbs +9 -7
- data/sig/generated/riffer/rig/stores/stamped.rbs +7 -0
- data/sig/generated/riffer/rig/stores/timestamp.rbs +13 -0
- data/sig/generated/riffer/rig/support/json_file.rbs +23 -0
- data/sig/generated/riffer/rig/support/typed_value/schema.rbs +14 -0
- data/sig/generated/riffer/rig/support/typed_value.rbs +13 -0
- data/sig/generated/riffer/rig/terminal/autocomplete.rbs +72 -0
- data/sig/generated/riffer/rig/terminal/banner.rbs +25 -4
- data/sig/generated/riffer/rig/terminal/blocking_prompter.rbs +23 -0
- data/sig/generated/riffer/rig/terminal/builtin.rbs +13 -0
- data/sig/generated/riffer/rig/terminal/columns.rbs +43 -0
- data/sig/generated/riffer/rig/terminal/console.rbs +38 -0
- data/sig/generated/riffer/rig/terminal/count.rbs +7 -0
- data/sig/generated/riffer/rig/terminal/echo.rbs +15 -0
- data/sig/generated/riffer/rig/terminal/frame.rbs +232 -0
- data/sig/generated/riffer/rig/terminal/frame_prompter.rbs +34 -0
- data/sig/generated/riffer/rig/terminal/help.rbs +22 -0
- data/sig/generated/riffer/rig/terminal/host.rbs +5 -11
- data/sig/generated/riffer/rig/terminal/idle_ticker.rbs +21 -0
- data/sig/generated/riffer/rig/terminal/job_counter.rbs +21 -0
- data/sig/generated/riffer/rig/terminal/job_exit_notice.rbs +7 -0
- data/sig/generated/riffer/rig/terminal/key.rbs +27 -0
- data/sig/generated/riffer/rig/terminal/key_decoder.rbs +110 -0
- data/sig/generated/riffer/rig/terminal/key_pipeline.rbs +60 -0
- data/sig/generated/riffer/rig/terminal/line_editor.rbs +111 -0
- data/sig/generated/riffer/rig/terminal/loading.rbs +42 -0
- data/sig/generated/riffer/rig/terminal/markdown/blocks.rbs +57 -0
- data/sig/generated/riffer/rig/terminal/markdown/flow.rbs +42 -0
- data/sig/generated/riffer/rig/terminal/markdown/inline.rbs +41 -0
- data/sig/generated/riffer/rig/terminal/markdown/span.rbs +20 -0
- data/sig/generated/riffer/rig/terminal/markdown/table.rbs +101 -0
- data/sig/generated/riffer/rig/terminal/markdown.rbs +112 -0
- data/sig/generated/riffer/rig/terminal/message_queue/message.rbs +10 -0
- data/sig/generated/riffer/rig/terminal/message_queue.rbs +17 -0
- data/sig/generated/riffer/rig/terminal/opening.rbs +33 -0
- data/sig/generated/riffer/rig/terminal/peek.rbs +37 -0
- data/sig/generated/riffer/rig/terminal/picker.rbs +21 -5
- data/sig/generated/riffer/rig/terminal/question.rbs +21 -0
- data/sig/generated/riffer/rig/terminal/quiet_display.rbs +33 -0
- data/sig/generated/riffer/rig/terminal/renderer.rbs +10 -122
- data/sig/generated/riffer/rig/terminal/repl.rbs +118 -0
- data/sig/generated/riffer/rig/terminal/replay/turn.rbs +20 -0
- data/sig/generated/riffer/rig/terminal/replay.rbs +29 -0
- data/sig/generated/riffer/rig/terminal/session.rbs +2 -7
- data/sig/generated/riffer/rig/terminal/spinner.rbs +9 -0
- data/sig/generated/riffer/rig/terminal/status_bar.rbs +88 -0
- data/sig/generated/riffer/rig/terminal/tally.rbs +7 -0
- data/sig/generated/riffer/rig/terminal/theme.rbs +23 -2
- data/sig/generated/riffer/rig/terminal/ticker.rbs +61 -0
- data/sig/generated/riffer/rig/terminal/ticker_display.rbs +45 -0
- data/sig/generated/riffer/rig/terminal/turn_buffer/stats.rbs +16 -0
- data/sig/generated/riffer/rig/terminal/turn_buffer.rbs +67 -0
- data/sig/generated/riffer/rig/terminal/turn_report.rbs +52 -0
- data/sig/generated/riffer/rig/terminal/wrapper.rbs +23 -14
- data/sig/generated/riffer/rig/terminal.rbs +78 -26
- data/sig/generated/riffer/rig/tools/bash/capture.rbs +39 -0
- data/sig/generated/riffer/rig/tools/bash/job.rbs +65 -0
- data/sig/generated/riffer/rig/tools/bash/job_log.rbs +60 -0
- data/sig/generated/riffer/rig/tools/bash/jobs.rbs +86 -0
- data/sig/generated/riffer/rig/tools/bash/kill.rbs +8 -0
- data/sig/generated/riffer/rig/tools/bash/kill_group.rbs +7 -0
- data/sig/generated/riffer/rig/tools/bash/monotonic_clock.rbs +6 -0
- data/sig/generated/riffer/rig/tools/bash/output.rbs +10 -0
- data/sig/generated/riffer/rig/tools/bash/truncation.rbs +7 -0
- data/sig/generated/riffer/rig/tools/bash.rbs +15 -13
- data/sig/generated/riffer/rig/trust.rbs +7 -8
- data/sig/manual/riffer/rig/acp/mapper.rbs +6 -0
- data/sig/manual/riffer/rig/commands/cost.rbs +6 -0
- data/sig/manual/riffer/rig/commands/reasoning.rbs +6 -0
- data/sig/manual/riffer/rig/commands/trace/replay.rbs +6 -0
- data/sig/manual/riffer/rig/commands/trace.rbs +6 -0
- data/sig/manual/riffer/rig/credentials/provider_fields.rbs +6 -0
- data/sig/manual/riffer/rig/hosts.rbs +1 -1
- data/sig/manual/riffer/rig/loader/credentials.rbs +6 -0
- data/sig/manual/riffer/rig/runtime/error_message.rbs +6 -0
- data/sig/manual/riffer/rig/runtime/job_envelope.rbs +6 -0
- data/sig/manual/riffer/rig/runtime/usage.rbs +6 -0
- data/sig/manual/riffer/rig/sdk.rbs +1 -1
- data/sig/manual/riffer/rig/settings/file.rbs +6 -0
- data/sig/manual/riffer/rig/settings/model_options.rbs +6 -0
- data/sig/manual/riffer/rig/stores/entries.rbs +6 -0
- data/sig/manual/riffer/rig/stores/timestamp.rbs +6 -0
- data/sig/manual/riffer/rig/stores.rbs +10 -2
- data/sig/manual/riffer/rig/support/json_file.rbs +6 -0
- data/sig/manual/riffer/rig/support.rbs +10 -0
- data/sig/manual/riffer/rig/terminal/autocomplete.rbs +8 -0
- data/sig/manual/riffer/rig/terminal/columns.rbs +6 -0
- data/sig/manual/riffer/rig/terminal/count.rbs +6 -0
- data/sig/manual/riffer/rig/terminal/echo.rbs +6 -0
- data/sig/manual/riffer/rig/terminal/help.rbs +6 -0
- data/sig/manual/riffer/rig/terminal/job_exit_notice.rbs +6 -0
- data/sig/manual/riffer/rig/terminal/markdown/blocks.rbs +6 -0
- data/sig/manual/riffer/rig/terminal/markdown/flow.rbs +6 -0
- data/sig/manual/riffer/rig/terminal/markdown/inline.rbs +6 -0
- data/sig/manual/riffer/rig/terminal/markdown/table.rbs +6 -0
- data/sig/manual/riffer/rig/terminal/markdown.rbs +6 -0
- data/sig/manual/riffer/rig/terminal/question.rbs +6 -0
- data/sig/manual/riffer/rig/terminal/replay.rbs +14 -0
- data/sig/manual/riffer/rig/terminal/spinner.rbs +6 -0
- data/sig/manual/riffer/rig/terminal/status_bar.rbs +6 -0
- data/sig/manual/riffer/rig/terminal/tally.rbs +6 -0
- data/sig/manual/riffer/rig/terminal/turn_report.rbs +14 -0
- data/sig/manual/riffer/rig/terminal.rbs +29 -4
- metadata +199 -13
- data/lib/riffer/rig/settings/pricing.rb +0 -40
- data/lib/riffer/rig/terminal/animator.rb +0 -139
- data/lib/riffer/rig/terminal/cursor.rb +0 -44
- data/lib/riffer/rig/terminal/smoother.rb +0 -126
- data/sig/generated/riffer/rig/settings/pricing.rbs +0 -31
- data/sig/generated/riffer/rig/terminal/animator.rbs +0 -68
- data/sig/generated/riffer/rig/terminal/cursor.rbs +0 -31
- data/sig/generated/riffer/rig/terminal/smoother.rbs +0 -59
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Riffer::Rig::ACP::Mapper
|
|
4
|
+
extend self
|
|
5
|
+
|
|
6
|
+
TOOL_KINDS = {
|
|
7
|
+
'read' => ::ACP::Types::ToolKind::READ,
|
|
8
|
+
'write' => ::ACP::Types::ToolKind::EDIT,
|
|
9
|
+
'edit' => ::ACP::Types::ToolKind::EDIT,
|
|
10
|
+
'bash' => ::ACP::Types::ToolKind::EXECUTE
|
|
11
|
+
}.freeze #: Hash[String, String]
|
|
12
|
+
|
|
13
|
+
CONFIRMED = 'yes' #: String
|
|
14
|
+
CONFIRM_OPTIONS = [
|
|
15
|
+
::ACP::Types::PermissionOption.new(
|
|
16
|
+
option_id: CONFIRMED, name: 'Yes', kind: ::ACP::Types::PermissionOptionKind::ALLOW_ONCE
|
|
17
|
+
),
|
|
18
|
+
::ACP::Types::PermissionOption.new(option_id: 'no', name: 'No', kind: ::ACP::Types::PermissionOptionKind::REJECT_ONCE)
|
|
19
|
+
].freeze #: Array[::ACP::Types::PermissionOption]
|
|
20
|
+
ANSWER_FIELD = 'answer' #: String
|
|
21
|
+
|
|
22
|
+
# Text and resource links are ACP's must-support prompt blocks; the client
|
|
23
|
+
# cannot send the others unless the agent advertised them.
|
|
24
|
+
# @rbs blocks: Array[::ACP::Types::ContentBlock::t]
|
|
25
|
+
# @rbs return: String
|
|
26
|
+
def prompt_text(blocks)
|
|
27
|
+
blocks.filter_map { |block| prompt_text_of(block) }.join("\n\n")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @rbs content: String
|
|
31
|
+
# @rbs return: ::ACP::Types::SessionUpdate::AgentMessageChunk
|
|
32
|
+
def message_chunk(content)
|
|
33
|
+
::ACP::Types::SessionUpdate::AgentMessageChunk.new(content: text_block(content))
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @rbs text: String
|
|
37
|
+
# @rbs return: ::ACP::Types::SessionUpdate::AgentMessageChunk
|
|
38
|
+
def notice(text)
|
|
39
|
+
# ACP has no update for a notice, and without blank lines around it a
|
|
40
|
+
# notice runs into the reply and into the next notice.
|
|
41
|
+
message_chunk("\n\n#{text}\n\n")
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# @rbs return: ::ACP::Types::SessionUpdate::AgentMessageChunk
|
|
45
|
+
def job_exit
|
|
46
|
+
# ACP has no update for a job exit notice, and without a break the replies
|
|
47
|
+
# either side of one read as a single message.
|
|
48
|
+
message_chunk("\n\n")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @rbs completion: Riffer::Rig::Runtime::JobCompletion
|
|
52
|
+
# @rbs return: ::ACP::Types::SessionUpdate::AgentMessageChunk
|
|
53
|
+
def job_completion(completion)
|
|
54
|
+
notice("Background job #{completion.job_id} #{ending(completion)}: #{completion.command}")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @rbs message: Hash[Symbol, untyped]
|
|
58
|
+
# @rbs return: ::ACP::Types::SessionUpdate::t?
|
|
59
|
+
def replay_of(message)
|
|
60
|
+
return job_exit if message[Riffer::Rig::Runtime::JobNotification::MARKER]
|
|
61
|
+
|
|
62
|
+
case message[:role]
|
|
63
|
+
when 'user' then ::ACP::Types::SessionUpdate::UserMessageChunk.new(content: text_block(message[:content].to_s))
|
|
64
|
+
when 'assistant' then message_chunk(message[:content].to_s) unless message[:content].to_s.strip.empty?
|
|
65
|
+
when 'tool' then replayed_tool_call(message)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# @rbs event: Riffer::StreamEvents::ToolCallDone
|
|
70
|
+
# @rbs return: ::ACP::Types::SessionUpdate::ToolCall
|
|
71
|
+
def tool_call(event)
|
|
72
|
+
::ACP::Types::SessionUpdate::ToolCall.new(
|
|
73
|
+
tool_call_id: event.call_id,
|
|
74
|
+
title: "#{event.name}(#{event.arguments})",
|
|
75
|
+
name: event.name,
|
|
76
|
+
kind: TOOL_KINDS.fetch(event.name, ::ACP::Types::ToolKind::OTHER),
|
|
77
|
+
status: ::ACP::Types::ToolCallStatus::IN_PROGRESS
|
|
78
|
+
)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# @rbs event: Riffer::Rig::Events::AfterToolCall
|
|
82
|
+
# @rbs return: ::ACP::Types::SessionUpdate::ToolCallUpdate
|
|
83
|
+
def tool_call_done(event)
|
|
84
|
+
result = event.result
|
|
85
|
+
::ACP::Types::SessionUpdate::ToolCallUpdate.new(
|
|
86
|
+
tool_call_id: event.call_id,
|
|
87
|
+
status: result.error? ? ::ACP::Types::ToolCallStatus::FAILED : ::ACP::Types::ToolCallStatus::COMPLETED,
|
|
88
|
+
content: tool_content(result.content)
|
|
89
|
+
)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# riffer's failure reasons (:error, :other, :guardrail_blocked) have no ACP
|
|
93
|
+
# counterpart; refusal is the closest signal that the turn did not complete.
|
|
94
|
+
# @rbs stop_reason: Symbol
|
|
95
|
+
# @rbs return: String
|
|
96
|
+
def stop_reason_of(stop_reason)
|
|
97
|
+
case stop_reason
|
|
98
|
+
when :completed then ::ACP::Types::StopReason::END_TURN
|
|
99
|
+
when Riffer::Rig::Runtime::INTERRUPT_CANCELLED then ::ACP::Types::StopReason::CANCELLED
|
|
100
|
+
when :max_steps then ::ACP::Types::StopReason::MAX_TURN_REQUESTS
|
|
101
|
+
else
|
|
102
|
+
stopped = Riffer::Rig::Runtime::STOPPED_WITHOUT_COMPLETING.include?(stop_reason)
|
|
103
|
+
stopped ? ::ACP::Types::StopReason::MAX_TOKENS : ::ACP::Types::StopReason::REFUSAL
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# @rbs options: Array[String]
|
|
108
|
+
# @rbs return: Array[::ACP::Types::PermissionOption]
|
|
109
|
+
def choices(options)
|
|
110
|
+
options.map do |option|
|
|
111
|
+
::ACP::Types::PermissionOption.new(
|
|
112
|
+
option_id: option, name: option, kind: ::ACP::Types::PermissionOptionKind::ALLOW_ONCE
|
|
113
|
+
)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# @rbs tool_call_id: String
|
|
118
|
+
# @rbs question: String
|
|
119
|
+
# @rbs return: ::ACP::Types::SessionUpdate::ToolCall
|
|
120
|
+
def permission_call(tool_call_id, question)
|
|
121
|
+
::ACP::Types::SessionUpdate::ToolCall.new(
|
|
122
|
+
tool_call_id: tool_call_id,
|
|
123
|
+
title: question,
|
|
124
|
+
kind: ::ACP::Types::ToolKind::OTHER,
|
|
125
|
+
status: ::ACP::Types::ToolCallStatus::PENDING
|
|
126
|
+
)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# @rbs session_id: String
|
|
130
|
+
# @rbs tool_call_id: String
|
|
131
|
+
# @rbs question: String
|
|
132
|
+
# @rbs options: Array[::ACP::Types::PermissionOption]
|
|
133
|
+
# @rbs return: ::ACP::Types::RequestPermissionRequest
|
|
134
|
+
def permission_request(session_id, tool_call_id, question, options)
|
|
135
|
+
::ACP::Types::RequestPermissionRequest.new(
|
|
136
|
+
session_id: session_id,
|
|
137
|
+
tool_call: ::ACP::Types::ToolCallUpdate.new(
|
|
138
|
+
tool_call_id: tool_call_id,
|
|
139
|
+
title: question,
|
|
140
|
+
kind: ::ACP::Types::ToolKind::OTHER,
|
|
141
|
+
status: ::ACP::Types::ToolCallStatus::PENDING
|
|
142
|
+
),
|
|
143
|
+
options: options
|
|
144
|
+
)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# @rbs response: ::ACP::Types::RequestPermissionResponse | ::ACP::RequestError
|
|
148
|
+
# @rbs return: String?
|
|
149
|
+
def selected_option(response)
|
|
150
|
+
return nil unless response.is_a?(::ACP::Types::RequestPermissionResponse)
|
|
151
|
+
|
|
152
|
+
outcome = response.outcome
|
|
153
|
+
outcome.option_id if outcome.is_a?(::ACP::Types::RequestPermissionOutcome::Selected)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# @rbs tool_call_id: String
|
|
157
|
+
# @rbs selected: String?
|
|
158
|
+
# @rbs return: ::ACP::Types::SessionUpdate::ToolCallUpdate
|
|
159
|
+
def permission_done(tool_call_id, selected)
|
|
160
|
+
::ACP::Types::SessionUpdate::ToolCallUpdate.new(
|
|
161
|
+
tool_call_id: tool_call_id,
|
|
162
|
+
status: selected ? ::ACP::Types::ToolCallStatus::COMPLETED : ::ACP::Types::ToolCallStatus::FAILED
|
|
163
|
+
)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# @rbs session_id: String
|
|
167
|
+
# @rbs question: String
|
|
168
|
+
# @rbs return: ::ACP::Types::CreateElicitationRequest
|
|
169
|
+
def elicitation_request(session_id, question)
|
|
170
|
+
::ACP::Types::CreateElicitationRequest.new(
|
|
171
|
+
message: question,
|
|
172
|
+
mode: ::ACP::Types::CreateElicitationRequest::Mode::Form.new(
|
|
173
|
+
requested_schema: ::ACP::Types::ElicitationSchema.new(
|
|
174
|
+
type: ::ACP::Types::ElicitationSchemaType::OBJECT,
|
|
175
|
+
properties: {
|
|
176
|
+
ANSWER_FIELD => ::ACP::Types::ElicitationPropertySchema::StringPropertySchema.new(title: question)
|
|
177
|
+
},
|
|
178
|
+
required: [ANSWER_FIELD]
|
|
179
|
+
),
|
|
180
|
+
scope: ::ACP::Types::ElicitationSessionScope.new(session_id: session_id)
|
|
181
|
+
)
|
|
182
|
+
)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# @rbs response: ::ACP::Types::CreateElicitationResponse | ::ACP::RequestError
|
|
186
|
+
# @rbs return: String?
|
|
187
|
+
def elicited_answer(response)
|
|
188
|
+
return nil unless response.is_a?(::ACP::Types::CreateElicitationResponse)
|
|
189
|
+
|
|
190
|
+
action = response.action
|
|
191
|
+
return nil unless action.is_a?(::ACP::Types::CreateElicitationResponse::Action::Accept)
|
|
192
|
+
|
|
193
|
+
answer = action.content&.fetch(ANSWER_FIELD, nil)
|
|
194
|
+
answer if answer.is_a?(String)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
private
|
|
198
|
+
|
|
199
|
+
# @rbs block: ::ACP::Types::ContentBlock::t
|
|
200
|
+
# @rbs return: String?
|
|
201
|
+
def prompt_text_of(block)
|
|
202
|
+
case block
|
|
203
|
+
when ::ACP::Types::ContentBlock::Text then block.text
|
|
204
|
+
when ::ACP::Types::ContentBlock::ResourceLink then "#{block.title || block.name} #{block.uri}"
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# @rbs text: String
|
|
209
|
+
# @rbs return: ::ACP::Types::ContentBlock::Text
|
|
210
|
+
def text_block(text)
|
|
211
|
+
::ACP::Types::ContentBlock::Text.new(text: text)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# @rbs completion: Riffer::Rig::Runtime::JobCompletion
|
|
215
|
+
# @rbs return: String
|
|
216
|
+
def ending(completion)
|
|
217
|
+
exit_status = completion.exit_status
|
|
218
|
+
exit_status ? "exited with status #{exit_status}" : "was killed by SIG#{completion.signal}"
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# The stored result is all a replayed call has: the arguments live in the
|
|
222
|
+
# assistant message's tool_calls, so the title is the name alone.
|
|
223
|
+
# @rbs message: Hash[Symbol, untyped]
|
|
224
|
+
# @rbs return: ::ACP::Types::SessionUpdate::ToolCall
|
|
225
|
+
def replayed_tool_call(message)
|
|
226
|
+
name = message[:name].to_s
|
|
227
|
+
::ACP::Types::SessionUpdate::ToolCall.new(
|
|
228
|
+
tool_call_id: message[:tool_call_id].to_s,
|
|
229
|
+
title: name,
|
|
230
|
+
name: name,
|
|
231
|
+
kind: TOOL_KINDS.fetch(name, ::ACP::Types::ToolKind::OTHER),
|
|
232
|
+
status: ::ACP::Types::ToolCallStatus::COMPLETED,
|
|
233
|
+
content: tool_content(message[:content].to_s)
|
|
234
|
+
)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# @rbs text: String
|
|
238
|
+
# @rbs return: Array[::ACP::Types::ToolCallContent::t]
|
|
239
|
+
def tool_content(text)
|
|
240
|
+
[::ACP::Types::ToolCallContent::Content.new(content: text_block(text))]
|
|
241
|
+
end
|
|
242
|
+
end
|
data/lib/riffer/rig/acp.rb
CHANGED
|
@@ -11,31 +11,29 @@ class Riffer::Rig::ACP
|
|
|
11
11
|
|
|
12
12
|
SESSION_EXTENSION = 'acp' #: String
|
|
13
13
|
|
|
14
|
-
TOOL_KINDS = {
|
|
15
|
-
'read' => ::ACP::Types::ToolKind::READ,
|
|
16
|
-
'write' => ::ACP::Types::ToolKind::EDIT,
|
|
17
|
-
'edit' => ::ACP::Types::ToolKind::EDIT,
|
|
18
|
-
'bash' => ::ACP::Types::ToolKind::EXECUTE
|
|
19
|
-
}.freeze #: Hash[String, String]
|
|
20
|
-
|
|
21
14
|
# @rbs @input: IO
|
|
22
15
|
# @rbs @output: IO
|
|
23
16
|
# @rbs @env: Riffer::Rig::Env | Riffer::Rig::Env::Invalid
|
|
24
17
|
# @rbs @home: String
|
|
18
|
+
# @rbs @cwd: String
|
|
25
19
|
# @rbs @build: ^(String, Riffer::Rig::Hosts::_Host, Hash[String, Riffer::Rig::Mcp::Declaration]) -> Riffer::Rig::Runtime
|
|
26
|
-
# @rbs @
|
|
20
|
+
# @rbs @adopt: (^(String) -> Riffer::Rig::Runtime?)?
|
|
21
|
+
# @rbs @client: ::ACP::AgentConnection::Client?
|
|
27
22
|
# @rbs @sessions: Hash[String, Riffer::Rig::Runtime]
|
|
28
23
|
# @rbs @hosts: Hash[String, Riffer::Rig::ACP::Host]
|
|
24
|
+
# @rbs @owned: Array[Riffer::Rig::Runtime]
|
|
29
25
|
# @rbs @lock: Thread::Mutex
|
|
30
26
|
|
|
31
27
|
# @rbs input: IO
|
|
32
28
|
# @rbs output: IO
|
|
33
29
|
# @rbs env: Riffer::Rig::Env | Riffer::Rig::Env::Invalid
|
|
34
30
|
# @rbs home: String
|
|
31
|
+
# @rbs cwd: String
|
|
35
32
|
# @rbs build: (^(String, Riffer::Rig::Hosts::_Host, Hash[String, Riffer::Rig::Mcp::Declaration]) -> Riffer::Rig::Runtime)?
|
|
33
|
+
# @rbs adopt: (^(String) -> Riffer::Rig::Runtime?)?
|
|
36
34
|
# @rbs return: Riffer::Rig::ACP
|
|
37
|
-
def self.for(input:, output:, env:, home:, build: nil)
|
|
38
|
-
new(input: input, output: output, env: env, home: home, build: build)
|
|
35
|
+
def self.for(input:, output:, env:, home:, cwd:, build: nil, adopt: nil)
|
|
36
|
+
new(input: input, output: output, env: env, home: home, cwd: cwd, build: build, adopt: adopt)
|
|
39
37
|
end
|
|
40
38
|
|
|
41
39
|
# Builds the Runtime a session runs on: the client's MCP servers flow
|
|
@@ -49,42 +47,50 @@ class Riffer::Rig::ACP
|
|
|
49
47
|
# @rbs home: String
|
|
50
48
|
# @rbs return: Riffer::Rig::Runtime
|
|
51
49
|
def self.build_runtime(cwd, host, servers, env:, home:)
|
|
52
|
-
extension = session_extension(servers)
|
|
53
50
|
Riffer::Rig::Loader.runtime(
|
|
54
|
-
cwd: cwd, host: host, env: env, home: home, extra_extensions:
|
|
51
|
+
cwd: cwd, host: host, env: env, home: home, extra_extensions: session_extensions(servers)
|
|
55
52
|
)
|
|
56
53
|
end
|
|
57
54
|
|
|
58
55
|
# @rbs servers: Hash[String, Riffer::Rig::Mcp::Declaration]
|
|
59
|
-
# @rbs return: Riffer::Rig::Extension
|
|
60
|
-
def self.
|
|
61
|
-
return
|
|
56
|
+
# @rbs return: Array[Riffer::Rig::Extension]
|
|
57
|
+
def self.session_extensions(servers)
|
|
58
|
+
return [] if servers.empty?
|
|
62
59
|
|
|
63
|
-
Riffer::Rig::Extension.new(SESSION_EXTENSION) do |rig|
|
|
60
|
+
extension = Riffer::Rig::Extension.new(SESSION_EXTENSION) do |rig|
|
|
64
61
|
servers.each { |name, server| rig.mcp(name, command: server.command, args: server.args, env: server.env) }
|
|
65
62
|
end
|
|
63
|
+
[extension]
|
|
66
64
|
end
|
|
67
65
|
|
|
68
66
|
# @rbs input: IO
|
|
69
67
|
# @rbs output: IO
|
|
70
68
|
# @rbs env: Riffer::Rig::Env | Riffer::Rig::Env::Invalid
|
|
71
69
|
# @rbs home: String
|
|
70
|
+
# @rbs cwd: String
|
|
72
71
|
# @rbs build: (^(String, Riffer::Rig::Hosts::_Host, Hash[String, Riffer::Rig::Mcp::Declaration]) -> Riffer::Rig::Runtime)?
|
|
72
|
+
# @rbs adopt: (^(String) -> Riffer::Rig::Runtime?)?
|
|
73
73
|
# @rbs return: void
|
|
74
|
-
def initialize(input:, output:, env:, home:, build: nil)
|
|
74
|
+
def initialize(input:, output:, env:, home:, cwd:, build: nil, adopt: nil)
|
|
75
75
|
@input = input
|
|
76
76
|
@output = output
|
|
77
77
|
@env = env
|
|
78
78
|
@home = home
|
|
79
|
-
@
|
|
79
|
+
@cwd = cwd
|
|
80
|
+
@build = build || lambda do |cwd, host, servers|
|
|
81
|
+
own(self.class.build_runtime(cwd, host, servers, env: @env, home: @home))
|
|
82
|
+
end
|
|
83
|
+
@adopt = adopt
|
|
80
84
|
@client = nil
|
|
81
85
|
@sessions = {}
|
|
82
86
|
@hosts = {}
|
|
87
|
+
@owned = []
|
|
83
88
|
@lock = Mutex.new
|
|
84
89
|
end
|
|
85
90
|
|
|
86
91
|
# Serves the connection until the client closes stdin; then the reader
|
|
87
92
|
# thread ends and run returns.
|
|
93
|
+
# @rbs &block: ? (::ACP::AgentConnection) -> void
|
|
88
94
|
# @rbs return: Integer
|
|
89
95
|
def run
|
|
90
96
|
connection = ::ACP::AgentConnection.new(
|
|
@@ -99,7 +105,16 @@ class Riffer::Rig::ACP
|
|
|
99
105
|
self
|
|
100
106
|
end
|
|
101
107
|
connection.start.join
|
|
108
|
+
yield connection if block_given?
|
|
102
109
|
0
|
|
110
|
+
ensure
|
|
111
|
+
# Background jobs run in process groups of their own, so only closing
|
|
112
|
+
# their Runtime stops them outliving this process. A turn still running is
|
|
113
|
+
# not waited for, so it is cancelled first to stop it starting another.
|
|
114
|
+
@lock.synchronize { @owned.dup }.each do |runtime|
|
|
115
|
+
runtime.cancel
|
|
116
|
+
runtime.close
|
|
117
|
+
end
|
|
103
118
|
end
|
|
104
119
|
|
|
105
120
|
# @rbs request: ::ACP::Types::NewSessionRequest
|
|
@@ -107,7 +122,7 @@ class Riffer::Rig::ACP
|
|
|
107
122
|
def new_session(request)
|
|
108
123
|
host = Riffer::Rig::ACP::Host.new(client: client)
|
|
109
124
|
servers = declarations(request.mcp_servers, host)
|
|
110
|
-
runtime = @build.call(request.cwd, host, servers)
|
|
125
|
+
runtime = announcing_job_exits(@build.call(request.cwd, host, servers), host)
|
|
111
126
|
@lock.synchronize do
|
|
112
127
|
@sessions[runtime.id] = runtime
|
|
113
128
|
@hosts[runtime.id] = host
|
|
@@ -123,41 +138,58 @@ class Riffer::Rig::ACP
|
|
|
123
138
|
runtime = session(request.session_id)
|
|
124
139
|
return ::ACP::RequestError.resource_not_found unless runtime
|
|
125
140
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
141
|
+
commands = available_commands(runtime).to_set(&:to_h)
|
|
142
|
+
# A command that runs no turn sends no turn_end.
|
|
143
|
+
stop_reason = :completed
|
|
144
|
+
dispatch(runtime, Riffer::Rig::ACP::Mapper.prompt_text(request.prompt)) do |event|
|
|
129
145
|
case event
|
|
130
146
|
when Riffer::StreamEvents::TextDelta
|
|
131
|
-
client.update(request.session_id, message_chunk(event.content))
|
|
147
|
+
client.update(request.session_id, Riffer::Rig::ACP::Mapper.message_chunk(event.content))
|
|
148
|
+
when Riffer::Rig::Events::CommandOutput
|
|
149
|
+
client.update(request.session_id, Riffer::Rig::ACP::Mapper.message_chunk(event.text))
|
|
132
150
|
when Riffer::StreamEvents::ToolCallDone
|
|
133
|
-
client.update(request.session_id, tool_call(event))
|
|
151
|
+
client.update(request.session_id, Riffer::Rig::ACP::Mapper.tool_call(event))
|
|
152
|
+
when Riffer::Rig::Events::AfterToolCall
|
|
153
|
+
client.update(request.session_id, Riffer::Rig::ACP::Mapper.tool_call_done(event))
|
|
154
|
+
when Riffer::Rig::Events::JobExit
|
|
155
|
+
client.update(request.session_id, Riffer::Rig::ACP::Mapper.job_exit)
|
|
134
156
|
when Riffer::Rig::Events::TurnEnd
|
|
135
157
|
stop_reason = event.stop_reason
|
|
136
158
|
end
|
|
137
159
|
end
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
160
|
+
# A reload in the dispatch, by /reload or the automatic check, can change
|
|
161
|
+
# the commands; its rebuild also reorders them, which is no change.
|
|
162
|
+
reloaded = available_commands(runtime)
|
|
163
|
+
client.available_commands(request.session_id, reloaded) unless reloaded.to_set(&:to_h) == commands
|
|
164
|
+
::ACP::Types::PromptResponse.new(stop_reason: Riffer::Rig::ACP::Mapper.stop_reason_of(stop_reason))
|
|
165
|
+
rescue Riffer::Rig::Runtime::BusyError
|
|
166
|
+
::ACP::RequestError.new(
|
|
167
|
+
code: ::ACP::RequestError::INVALID_REQUEST,
|
|
168
|
+
message: 'a prompt is already running on this session; wait for it to finish or cancel it'
|
|
169
|
+
)
|
|
170
|
+
rescue StandardError => e
|
|
171
|
+
::ACP::RequestError.new(
|
|
172
|
+
code: ::ACP::RequestError::INTERNAL_ERROR, message: Riffer::Rig::Runtime::ErrorMessage.of(e)
|
|
173
|
+
)
|
|
141
174
|
end
|
|
142
175
|
|
|
143
|
-
# The host takes the session id before the build, so a build-time notify
|
|
144
|
-
# still reaches the client as an update for this session.
|
|
145
176
|
# @rbs request: ::ACP::Types::LoadSessionRequest
|
|
146
177
|
# @rbs return: (::ACP::Types::LoadSessionResponse | ::ACP::RequestError)
|
|
147
178
|
def load_session(request)
|
|
148
179
|
host = Riffer::Rig::ACP::Host.new(client: client)
|
|
149
|
-
host.session_id = request.session_id
|
|
150
180
|
loader = Riffer::Rig::Loader.new(cwd: request.cwd, host: host, env: @env, home: @home)
|
|
151
|
-
runtime =
|
|
181
|
+
runtime = adopted(request.session_id) || resumed(loader, request, host)
|
|
152
182
|
return ::ACP::RequestError.resource_not_found unless runtime
|
|
153
183
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
184
|
+
# A client need not answer a question while its own session/load is
|
|
185
|
+
# pending, and acp-sdk never times a request out, so the host takes the
|
|
186
|
+
# session id only once the build can no longer ask one.
|
|
187
|
+
host.session_id = request.session_id
|
|
188
|
+
@lock.synchronize { @sessions[request.session_id] = runtime }
|
|
158
189
|
loader.messages(request.session_id)
|
|
159
|
-
.filter_map { |message| replay_of(message) }
|
|
190
|
+
.filter_map { |message| Riffer::Rig::ACP::Mapper.replay_of(message) }
|
|
160
191
|
.each { |update| client.update(request.session_id, update) }
|
|
192
|
+
client.available_commands(request.session_id, available_commands(runtime))
|
|
161
193
|
::ACP::Types::LoadSessionResponse.new
|
|
162
194
|
rescue StandardError => e
|
|
163
195
|
::ACP::RequestError.new(code: ::ACP::RequestError::INTERNAL_ERROR, message: e.message)
|
|
@@ -167,7 +199,7 @@ class Riffer::Rig::ACP
|
|
|
167
199
|
# @rbs return: (::ACP::Types::ListSessionsResponse | ::ACP::RequestError)
|
|
168
200
|
def list_sessions(request)
|
|
169
201
|
loader = Riffer::Rig::Loader.new(
|
|
170
|
-
cwd: request.cwd ||
|
|
202
|
+
cwd: request.cwd || @cwd,
|
|
171
203
|
host: Riffer::Rig::Hosts::Null.new,
|
|
172
204
|
env: @env,
|
|
173
205
|
home: @home
|
|
@@ -186,7 +218,8 @@ class Riffer::Rig::ACP
|
|
|
186
218
|
::ACP::RequestError.new(code: ::ACP::RequestError::INTERNAL_ERROR, message: e.message)
|
|
187
219
|
end
|
|
188
220
|
|
|
189
|
-
# Runs on the transport's reader thread, so it
|
|
221
|
+
# Runs on the transport's reader thread, so it asks the turn to stop and
|
|
222
|
+
# never waits for it.
|
|
190
223
|
# @rbs notification: ::ACP::Types::CancelNotification
|
|
191
224
|
# @rbs return: void
|
|
192
225
|
def cancel(notification)
|
|
@@ -211,7 +244,7 @@ class Riffer::Rig::ACP
|
|
|
211
244
|
|
|
212
245
|
private
|
|
213
246
|
|
|
214
|
-
# @rbs return: ::ACP::AgentConnection::
|
|
247
|
+
# @rbs return: ::ACP::AgentConnection::Client
|
|
215
248
|
def client
|
|
216
249
|
@client or raise 'ACP agent used before ::ACP::AgentConnection.start built it'
|
|
217
250
|
end
|
|
@@ -222,6 +255,59 @@ class Riffer::Rig::ACP
|
|
|
222
255
|
@lock.synchronize { @sessions[id] }
|
|
223
256
|
end
|
|
224
257
|
|
|
258
|
+
# @rbs runtime: Riffer::Rig::Runtime
|
|
259
|
+
# @rbs text: String
|
|
260
|
+
# @rbs &: (::Riffer::StreamEvents::Base | Riffer::Rig::Events::_Event) -> void
|
|
261
|
+
# @rbs return: void
|
|
262
|
+
def dispatch(runtime, text, &)
|
|
263
|
+
command = Riffer::Rig::Command::INVOCATION.match(text)
|
|
264
|
+
command ? runtime.run_command(command[1].to_s, command[2].to_s, &) : runtime.prompt(text, &)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# @rbs id: String
|
|
268
|
+
# @rbs return: Riffer::Rig::Runtime?
|
|
269
|
+
def adopted(id)
|
|
270
|
+
runtime = @adopt&.call(id)
|
|
271
|
+
# The Loader that built the Runtime holds this same host, so it is
|
|
272
|
+
# re-pointed in place rather than replaced.
|
|
273
|
+
case (host = runtime&.host&.host)
|
|
274
|
+
when Riffer::Rig::ACP::Host then host.client = client
|
|
275
|
+
end
|
|
276
|
+
runtime
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# @rbs loader: Riffer::Rig::Loader
|
|
280
|
+
# @rbs request: ::ACP::Types::LoadSessionRequest
|
|
281
|
+
# @rbs host: Riffer::Rig::ACP::Host
|
|
282
|
+
# @rbs return: Riffer::Rig::Runtime?
|
|
283
|
+
def resumed(loader, request, host)
|
|
284
|
+
runtime = loader.resume(
|
|
285
|
+
request.session_id,
|
|
286
|
+
extra_extensions: self.class.session_extensions(declarations(request.mcp_servers, host))
|
|
287
|
+
)
|
|
288
|
+
runtime && own(announcing_job_exits(runtime, host))
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# @rbs runtime: Riffer::Rig::Runtime
|
|
292
|
+
# @rbs return: Riffer::Rig::Runtime
|
|
293
|
+
def own(runtime)
|
|
294
|
+
@lock.synchronize { @owned << runtime }
|
|
295
|
+
runtime
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# @rbs runtime: Riffer::Rig::Runtime
|
|
299
|
+
# @rbs host: Riffer::Rig::ACP::Host
|
|
300
|
+
# @rbs return: Riffer::Rig::Runtime
|
|
301
|
+
def announcing_job_exits(runtime, host)
|
|
302
|
+
# A prompt in flight marks the exit on its own stream when it tells the
|
|
303
|
+
# model. One starting meanwhile collects the exit from the inbox whose
|
|
304
|
+
# lock this subscriber holds, so it cannot stream ahead of this update.
|
|
305
|
+
runtime.on_job_exit do |completion|
|
|
306
|
+
host.update(Riffer::Rig::ACP::Mapper.job_completion(completion)) unless runtime.busy?
|
|
307
|
+
end
|
|
308
|
+
runtime
|
|
309
|
+
end
|
|
310
|
+
|
|
225
311
|
# @rbs servers: Array[::ACP::Types::McpServer::t]
|
|
226
312
|
# @rbs host: Riffer::Rig::ACP::Host
|
|
227
313
|
# @rbs return: Hash[String, Riffer::Rig::Mcp::Declaration]
|
|
@@ -260,89 +346,6 @@ class Riffer::Rig::ACP
|
|
|
260
346
|
server.env.to_h { |variable| [variable.name, variable.value] }
|
|
261
347
|
end
|
|
262
348
|
|
|
263
|
-
# Text and resource links are ACP's must-support prompt blocks; the client
|
|
264
|
-
# cannot send the others unless the agent advertised them.
|
|
265
|
-
# @rbs blocks: Array[::ACP::Types::ContentBlock::t]
|
|
266
|
-
# @rbs return: String
|
|
267
|
-
def prompt_text(blocks)
|
|
268
|
-
blocks.filter_map { |block| prompt_text_of(block) }.join("\n\n")
|
|
269
|
-
end
|
|
270
|
-
|
|
271
|
-
# @rbs block: ::ACP::Types::ContentBlock::t
|
|
272
|
-
# @rbs return: String?
|
|
273
|
-
def prompt_text_of(block)
|
|
274
|
-
case block
|
|
275
|
-
when ::ACP::Types::ContentBlock::Text then block.text
|
|
276
|
-
when ::ACP::Types::ContentBlock::ResourceLink then "#{block.title || block.name} #{block.uri}"
|
|
277
|
-
end
|
|
278
|
-
end
|
|
279
|
-
|
|
280
|
-
# @rbs content: String
|
|
281
|
-
# @rbs return: ::ACP::Types::SessionUpdate::AgentMessageChunk
|
|
282
|
-
def message_chunk(content)
|
|
283
|
-
::ACP::Types::SessionUpdate::AgentMessageChunk.new(content: text_block(content))
|
|
284
|
-
end
|
|
285
|
-
|
|
286
|
-
# @rbs text: String
|
|
287
|
-
# @rbs return: ::ACP::Types::ContentBlock::Text
|
|
288
|
-
def text_block(text)
|
|
289
|
-
::ACP::Types::ContentBlock::Text.new(text: text)
|
|
290
|
-
end
|
|
291
|
-
|
|
292
|
-
# @rbs message: Hash[Symbol, untyped]
|
|
293
|
-
# @rbs return: ::ACP::Types::SessionUpdate::t?
|
|
294
|
-
def replay_of(message)
|
|
295
|
-
case message[:role]
|
|
296
|
-
when 'user' then ::ACP::Types::SessionUpdate::UserMessageChunk.new(content: text_block(message[:content].to_s))
|
|
297
|
-
when 'assistant' then message_chunk(message[:content].to_s)
|
|
298
|
-
when 'tool' then replayed_tool_call(message)
|
|
299
|
-
end
|
|
300
|
-
end
|
|
301
|
-
|
|
302
|
-
# The stored result is all a replayed call has: the arguments live in the
|
|
303
|
-
# assistant message's tool_calls, so the title is the name alone.
|
|
304
|
-
# @rbs message: Hash[Symbol, untyped]
|
|
305
|
-
# @rbs return: ::ACP::Types::SessionUpdate::ToolCall
|
|
306
|
-
def replayed_tool_call(message)
|
|
307
|
-
name = message[:name].to_s
|
|
308
|
-
::ACP::Types::SessionUpdate::ToolCall.new(
|
|
309
|
-
tool_call_id: message[:tool_call_id].to_s,
|
|
310
|
-
title: name,
|
|
311
|
-
name: name,
|
|
312
|
-
kind: TOOL_KINDS.fetch(name, ::ACP::Types::ToolKind::OTHER),
|
|
313
|
-
status: ::ACP::Types::ToolCallStatus::COMPLETED,
|
|
314
|
-
content: [::ACP::Types::ToolCallContent::Content.new(content: text_block(message[:content].to_s))]
|
|
315
|
-
)
|
|
316
|
-
end
|
|
317
|
-
|
|
318
|
-
# riffer's stream has no completion boundary for a tool call, so a call goes
|
|
319
|
-
# out once, in progress, when its arguments are complete.
|
|
320
|
-
# @rbs event: Riffer::StreamEvents::ToolCallDone
|
|
321
|
-
# @rbs return: ::ACP::Types::SessionUpdate::ToolCall
|
|
322
|
-
def tool_call(event)
|
|
323
|
-
::ACP::Types::SessionUpdate::ToolCall.new(
|
|
324
|
-
tool_call_id: event.call_id,
|
|
325
|
-
title: "#{event.name}(#{event.arguments})",
|
|
326
|
-
name: event.name,
|
|
327
|
-
kind: TOOL_KINDS.fetch(event.name, ::ACP::Types::ToolKind::OTHER),
|
|
328
|
-
status: ::ACP::Types::ToolCallStatus::IN_PROGRESS
|
|
329
|
-
)
|
|
330
|
-
end
|
|
331
|
-
|
|
332
|
-
# riffer's failure reasons (:error, :other, :guardrail_blocked) have no ACP
|
|
333
|
-
# counterpart; refusal is the closest signal that the turn did not complete.
|
|
334
|
-
# @rbs stop_reason: Symbol?
|
|
335
|
-
# @rbs return: String
|
|
336
|
-
def stop_reason_of(stop_reason)
|
|
337
|
-
case stop_reason
|
|
338
|
-
when :completed then ::ACP::Types::StopReason::END_TURN
|
|
339
|
-
when Riffer::Rig::Runtime::INTERRUPT_CANCELLED then ::ACP::Types::StopReason::CANCELLED
|
|
340
|
-
when :max_steps then ::ACP::Types::StopReason::MAX_TURN_REQUESTS
|
|
341
|
-
when :context_window, :length, :content_filter, :malformed_output then ::ACP::Types::StopReason::MAX_TOKENS
|
|
342
|
-
else ::ACP::Types::StopReason::REFUSAL
|
|
343
|
-
end
|
|
344
|
-
end
|
|
345
|
-
|
|
346
349
|
# @rbs runtime: Riffer::Rig::Runtime
|
|
347
350
|
# @rbs return: Array[::ACP::Types::AvailableCommand]
|
|
348
351
|
def available_commands(runtime)
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Riffer::Rig::Bundled
|
|
4
|
-
Bash = Riffer::Rig::Extension.new('bash')
|
|
4
|
+
Bash = Riffer::Rig::Extension.new('bash') do |rig|
|
|
5
|
+
rig.tool Riffer::Rig::Tools::Bash
|
|
6
|
+
rig.tool Riffer::Rig::Tools::Bash::Output
|
|
7
|
+
rig.tool Riffer::Rig::Tools::Bash::Kill
|
|
8
|
+
end #: Riffer::Rig::Extension
|
|
5
9
|
end
|