riffer-rig 0.11.0 → 0.13.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 +140 -0
- data/README.md +102 -39
- data/lib/riffer/rig/acp/host.rb +126 -0
- data/lib/riffer/rig/acp/mapper.rb +242 -0
- data/lib/riffer/rig/acp.rb +356 -0
- data/lib/riffer/rig/bundled/bash.rb +5 -1
- data/lib/riffer/rig/bundled/mcp.rb +6 -1
- data/lib/riffer/rig/cli/flags.rb +75 -22
- data/lib/riffer/rig/cli/sessions.rb +147 -0
- data/lib/riffer/rig/cli.rb +34 -82
- 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/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 +1 -1
- 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 +11 -15
- data/lib/riffer/rig/headless/ndjson.rb +49 -0
- data/lib/riffer/rig/headless.rb +140 -31
- 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 +35 -0
- data/lib/riffer/rig/loader.rb +118 -322
- data/lib/riffer/rig/mcp/auth.rb +61 -0
- data/lib/riffer/rig/mcp/declaration.rb +29 -5
- data/lib/riffer/rig/provider_setup.rb +2 -2
- data/lib/riffer/rig/providers.rb +5 -0
- data/lib/riffer/rig/registrar.rb +12 -4
- 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 +80 -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 +333 -213
- data/lib/riffer/rig/sdk.rb +29 -7
- data/lib/riffer/rig/settings/file.rb +27 -0
- data/lib/riffer/rig/settings/model_options.rb +64 -0
- data/lib/riffer/rig/settings.rb +0 -129
- data/lib/riffer/rig/stores/entries.rb +23 -0
- data/lib/riffer/rig/stores/header.rb +3 -17
- data/lib/riffer/rig/stores/jsonl.rb +7 -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/recorder.rb +34 -13
- 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 +49 -0
- data/lib/riffer/rig/terminal/echo.rb +26 -0
- data/lib/riffer/rig/terminal/equalizer.rb +20 -0
- data/lib/riffer/rig/terminal/frame.rb +339 -0
- data/lib/riffer/rig/terminal/frame_prompter.rb +63 -0
- data/lib/riffer/rig/terminal/help.rb +41 -0
- data/lib/riffer/rig/terminal/host.rb +7 -28
- 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 +219 -0
- data/lib/riffer/rig/terminal/key_pipeline.rb +111 -0
- data/lib/riffer/rig/terminal/line_editor.rb +183 -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 +169 -0
- 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 +242 -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 +29 -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 +189 -0
- data/lib/riffer/rig/terminal/ticker_display.rb +91 -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 +46 -62
- data/lib/riffer/rig/terminal.rb +277 -93
- 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 +42 -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/lib/riffer/rig.rb +1 -1
- data/sig/generated/riffer/rig/acp/host.rbs +74 -0
- data/sig/generated/riffer/rig/acp/mapper.rbs +108 -0
- data/sig/generated/riffer/rig/acp.rbs +162 -0
- data/sig/generated/riffer/rig/cli/flags.rbs +24 -4
- data/sig/generated/riffer/rig/cli/sessions.rbs +86 -0
- data/sig/generated/riffer/rig/cli.rbs +7 -32
- 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/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/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 +6 -11
- data/sig/generated/riffer/rig/headless/ndjson.rbs +27 -0
- data/sig/generated/riffer/rig/headless.rbs +49 -9
- 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 +25 -0
- data/sig/generated/riffer/rig/loader.rbs +39 -132
- data/sig/generated/riffer/rig/mcp/auth.rbs +32 -0
- data/sig/generated/riffer/rig/mcp/declaration.rbs +23 -4
- data/sig/generated/riffer/rig/provider_setup.rbs +1 -1
- data/sig/generated/riffer/rig/providers.rbs +3 -0
- data/sig/generated/riffer/rig/registrar.rbs +6 -2
- 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 +42 -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 +131 -80
- data/sig/generated/riffer/rig/sdk.rbs +11 -1
- data/sig/generated/riffer/rig/settings/file.rbs +19 -0
- data/sig/generated/riffer/rig/settings/model_options.rbs +31 -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/recorder.rbs +16 -4
- 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 +32 -0
- data/sig/generated/riffer/rig/terminal/echo.rbs +15 -0
- data/sig/generated/riffer/rig/terminal/equalizer.rbs +12 -0
- data/sig/generated/riffer/rig/terminal/frame.rbs +169 -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/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 +81 -0
- data/sig/generated/riffer/rig/terminal/key_pipeline.rbs +54 -0
- data/sig/generated/riffer/rig/terminal/line_editor.rbs +108 -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 +82 -0
- 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 +116 -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 +27 -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 +107 -0
- data/sig/generated/riffer/rig/terminal/ticker_display.rbs +48 -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 +16 -14
- data/sig/generated/riffer/rig/terminal.rbs +101 -28
- 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/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/events.rbs +1 -0
- data/sig/manual/riffer/rig/headless/ndjson.rbs +6 -0
- data/sig/manual/riffer/rig/headless.rbs +7 -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/mcp/auth.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 +8 -0
- 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/echo.rbs +6 -0
- data/sig/manual/riffer/rig/terminal/equalizer.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/tally.rbs +6 -0
- data/sig/manual/riffer/rig/terminal/turn_report.rbs +14 -0
- data/sig/manual/riffer/rig/terminal.rbs +37 -0
- metadata +214 -9
- 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/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
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'acp/sdk'
|
|
4
|
+
require 'time'
|
|
5
|
+
|
|
6
|
+
# The ACP tier: Runtime events reach the client only through this adapter's
|
|
7
|
+
# mapping, so nothing else in rig may translate an event into an ACP type.
|
|
8
|
+
class Riffer::Rig::ACP
|
|
9
|
+
AGENT_NAME = 'riffer' #: String
|
|
10
|
+
AGENT_TITLE = 'Riffer' #: String
|
|
11
|
+
|
|
12
|
+
SESSION_EXTENSION = 'acp' #: String
|
|
13
|
+
|
|
14
|
+
# @rbs @input: IO
|
|
15
|
+
# @rbs @output: IO
|
|
16
|
+
# @rbs @env: Riffer::Rig::Env | Riffer::Rig::Env::Invalid
|
|
17
|
+
# @rbs @home: String
|
|
18
|
+
# @rbs @cwd: String
|
|
19
|
+
# @rbs @build: ^(String, Riffer::Rig::Hosts::_Host, Hash[String, Riffer::Rig::Mcp::Declaration]) -> Riffer::Rig::Runtime
|
|
20
|
+
# @rbs @adopt: (^(String) -> Riffer::Rig::Runtime?)?
|
|
21
|
+
# @rbs @client: ::ACP::AgentConnection::Client?
|
|
22
|
+
# @rbs @sessions: Hash[String, Riffer::Rig::Runtime]
|
|
23
|
+
# @rbs @hosts: Hash[String, Riffer::Rig::ACP::Host]
|
|
24
|
+
# @rbs @owned: Array[Riffer::Rig::Runtime]
|
|
25
|
+
# @rbs @lock: Thread::Mutex
|
|
26
|
+
|
|
27
|
+
# @rbs input: IO
|
|
28
|
+
# @rbs output: IO
|
|
29
|
+
# @rbs env: Riffer::Rig::Env | Riffer::Rig::Env::Invalid
|
|
30
|
+
# @rbs home: String
|
|
31
|
+
# @rbs cwd: String
|
|
32
|
+
# @rbs build: (^(String, Riffer::Rig::Hosts::_Host, Hash[String, Riffer::Rig::Mcp::Declaration]) -> Riffer::Rig::Runtime)?
|
|
33
|
+
# @rbs adopt: (^(String) -> Riffer::Rig::Runtime?)?
|
|
34
|
+
# @rbs return: Riffer::Rig::ACP
|
|
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)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Builds the Runtime a session runs on: the client's MCP servers flow
|
|
40
|
+
# through the rig.mcp seam as a synthetic extension, so a reload rebuild
|
|
41
|
+
# re-declares them and a same-name declaration from an extension replaces
|
|
42
|
+
# them with the usual notify.
|
|
43
|
+
# @rbs cwd: String
|
|
44
|
+
# @rbs host: Riffer::Rig::Hosts::_Host
|
|
45
|
+
# @rbs servers: Hash[String, Riffer::Rig::Mcp::Declaration]
|
|
46
|
+
# @rbs env: Riffer::Rig::Env | Riffer::Rig::Env::Invalid
|
|
47
|
+
# @rbs home: String
|
|
48
|
+
# @rbs return: Riffer::Rig::Runtime
|
|
49
|
+
def self.build_runtime(cwd, host, servers, env:, home:)
|
|
50
|
+
Riffer::Rig::Loader.runtime(
|
|
51
|
+
cwd: cwd, host: host, env: env, home: home, extra_extensions: session_extensions(servers)
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# @rbs servers: Hash[String, Riffer::Rig::Mcp::Declaration]
|
|
56
|
+
# @rbs return: Array[Riffer::Rig::Extension]
|
|
57
|
+
def self.session_extensions(servers)
|
|
58
|
+
return [] if servers.empty?
|
|
59
|
+
|
|
60
|
+
extension = Riffer::Rig::Extension.new(SESSION_EXTENSION) do |rig|
|
|
61
|
+
servers.each { |name, server| rig.mcp(name, command: server.command, args: server.args, env: server.env) }
|
|
62
|
+
end
|
|
63
|
+
[extension]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# @rbs input: IO
|
|
67
|
+
# @rbs output: IO
|
|
68
|
+
# @rbs env: Riffer::Rig::Env | Riffer::Rig::Env::Invalid
|
|
69
|
+
# @rbs home: String
|
|
70
|
+
# @rbs cwd: String
|
|
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
|
+
# @rbs return: void
|
|
74
|
+
def initialize(input:, output:, env:, home:, cwd:, build: nil, adopt: nil)
|
|
75
|
+
@input = input
|
|
76
|
+
@output = output
|
|
77
|
+
@env = env
|
|
78
|
+
@home = home
|
|
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
|
|
84
|
+
@client = nil
|
|
85
|
+
@sessions = {}
|
|
86
|
+
@hosts = {}
|
|
87
|
+
@owned = []
|
|
88
|
+
@lock = Mutex.new
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Serves the connection until the client closes stdin; then the reader
|
|
92
|
+
# thread ends and run returns.
|
|
93
|
+
# @rbs &block: ? (::ACP::AgentConnection) -> void
|
|
94
|
+
# @rbs return: Integer
|
|
95
|
+
def run
|
|
96
|
+
connection = ::ACP::AgentConnection.new(
|
|
97
|
+
transport: ::ACP::Transport::Stdio.new(input: @input, output: @output),
|
|
98
|
+
capabilities: ::ACP::Types::AgentCapabilities.new(
|
|
99
|
+
load_session: true,
|
|
100
|
+
session_capabilities: ::ACP::Types::SessionCapabilities.new(list: ::ACP::Types::SessionListCapabilities.new)
|
|
101
|
+
),
|
|
102
|
+
agent_info: ::ACP::Types::Implementation.new(name: AGENT_NAME, version: Riffer::Rig::VERSION, title: AGENT_TITLE)
|
|
103
|
+
) do |client|
|
|
104
|
+
@client = client
|
|
105
|
+
self
|
|
106
|
+
end
|
|
107
|
+
connection.start.join
|
|
108
|
+
yield connection if block_given?
|
|
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
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# @rbs request: ::ACP::Types::NewSessionRequest
|
|
121
|
+
# @rbs return: (::ACP::Types::NewSessionResponse | ::ACP::RequestError)
|
|
122
|
+
def new_session(request)
|
|
123
|
+
host = Riffer::Rig::ACP::Host.new(client: client)
|
|
124
|
+
servers = declarations(request.mcp_servers, host)
|
|
125
|
+
runtime = announcing_job_exits(@build.call(request.cwd, host, servers), host)
|
|
126
|
+
@lock.synchronize do
|
|
127
|
+
@sessions[runtime.id] = runtime
|
|
128
|
+
@hosts[runtime.id] = host
|
|
129
|
+
end
|
|
130
|
+
::ACP::Types::NewSessionResponse.new(session_id: runtime.id)
|
|
131
|
+
rescue StandardError => e
|
|
132
|
+
::ACP::RequestError.new(code: ::ACP::RequestError::INTERNAL_ERROR, message: e.message)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# @rbs request: ::ACP::Types::PromptRequest
|
|
136
|
+
# @rbs return: (::ACP::Types::PromptResponse | ::ACP::RequestError)
|
|
137
|
+
def prompt(request)
|
|
138
|
+
runtime = session(request.session_id)
|
|
139
|
+
return ::ACP::RequestError.resource_not_found unless runtime
|
|
140
|
+
|
|
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|
|
|
145
|
+
case event
|
|
146
|
+
when Riffer::StreamEvents::TextDelta
|
|
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))
|
|
150
|
+
when Riffer::StreamEvents::ToolCallDone
|
|
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)
|
|
156
|
+
when Riffer::Rig::Events::TurnEnd
|
|
157
|
+
stop_reason = event.stop_reason
|
|
158
|
+
end
|
|
159
|
+
end
|
|
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
|
+
)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# @rbs request: ::ACP::Types::LoadSessionRequest
|
|
177
|
+
# @rbs return: (::ACP::Types::LoadSessionResponse | ::ACP::RequestError)
|
|
178
|
+
def load_session(request)
|
|
179
|
+
host = Riffer::Rig::ACP::Host.new(client: client)
|
|
180
|
+
loader = Riffer::Rig::Loader.new(cwd: request.cwd, host: host, env: @env, home: @home)
|
|
181
|
+
runtime = adopted(request.session_id) || resumed(loader, request, host)
|
|
182
|
+
return ::ACP::RequestError.resource_not_found unless runtime
|
|
183
|
+
|
|
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 }
|
|
189
|
+
loader.messages(request.session_id)
|
|
190
|
+
.filter_map { |message| Riffer::Rig::ACP::Mapper.replay_of(message) }
|
|
191
|
+
.each { |update| client.update(request.session_id, update) }
|
|
192
|
+
client.available_commands(request.session_id, available_commands(runtime))
|
|
193
|
+
::ACP::Types::LoadSessionResponse.new
|
|
194
|
+
rescue StandardError => e
|
|
195
|
+
::ACP::RequestError.new(code: ::ACP::RequestError::INTERNAL_ERROR, message: e.message)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# @rbs request: ::ACP::Types::ListSessionsRequest
|
|
199
|
+
# @rbs return: (::ACP::Types::ListSessionsResponse | ::ACP::RequestError)
|
|
200
|
+
def list_sessions(request)
|
|
201
|
+
loader = Riffer::Rig::Loader.new(
|
|
202
|
+
cwd: request.cwd || @cwd,
|
|
203
|
+
host: Riffer::Rig::Hosts::Null.new,
|
|
204
|
+
env: @env,
|
|
205
|
+
home: @home
|
|
206
|
+
)
|
|
207
|
+
headers = request.cwd ? loader.list : loader.list(all: true)
|
|
208
|
+
sessions = headers.map do |header|
|
|
209
|
+
::ACP::Types::SessionInfo.new(
|
|
210
|
+
session_id: header.id,
|
|
211
|
+
cwd: header.cwd,
|
|
212
|
+
title: header.title,
|
|
213
|
+
updated_at: header.updated&.utc&.iso8601
|
|
214
|
+
)
|
|
215
|
+
end
|
|
216
|
+
::ACP::Types::ListSessionsResponse.new(sessions: sessions)
|
|
217
|
+
rescue StandardError => e
|
|
218
|
+
::ACP::RequestError.new(code: ::ACP::RequestError::INTERNAL_ERROR, message: e.message)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Runs on the transport's reader thread, so it asks the turn to stop and
|
|
222
|
+
# never waits for it.
|
|
223
|
+
# @rbs notification: ::ACP::Types::CancelNotification
|
|
224
|
+
# @rbs return: void
|
|
225
|
+
def cancel(notification)
|
|
226
|
+
session(notification.session_id)&.cancel
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Runs after the session/new reply, so the client knows the id before any
|
|
230
|
+
# update for it arrives: the build-time notifies flush here, then the
|
|
231
|
+
# commands go out.
|
|
232
|
+
# @rbs response: ::ACP::Types::NewSessionResponse
|
|
233
|
+
# @rbs return: void
|
|
234
|
+
def session_created(response)
|
|
235
|
+
id = response.session_id
|
|
236
|
+
# @type var found: [Riffer::Rig::ACP::Host?, Riffer::Rig::Runtime?]
|
|
237
|
+
found = @lock.synchronize { [@hosts[id], @sessions[id]] }
|
|
238
|
+
host, runtime = found
|
|
239
|
+
return unless host && runtime
|
|
240
|
+
|
|
241
|
+
host.session_id = id
|
|
242
|
+
client.available_commands(id, available_commands(runtime))
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
private
|
|
246
|
+
|
|
247
|
+
# @rbs return: ::ACP::AgentConnection::Client
|
|
248
|
+
def client
|
|
249
|
+
@client or raise 'ACP agent used before ::ACP::AgentConnection.start built it'
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# @rbs id: String
|
|
253
|
+
# @rbs return: Riffer::Rig::Runtime?
|
|
254
|
+
def session(id)
|
|
255
|
+
@lock.synchronize { @sessions[id] }
|
|
256
|
+
end
|
|
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
|
+
|
|
311
|
+
# @rbs servers: Array[::ACP::Types::McpServer::t]
|
|
312
|
+
# @rbs host: Riffer::Rig::ACP::Host
|
|
313
|
+
# @rbs return: Hash[String, Riffer::Rig::Mcp::Declaration]
|
|
314
|
+
def declarations(servers, host)
|
|
315
|
+
servers.filter_map { |server| declaration_of(server, host) }.to_h
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# @rbs server: ::ACP::Types::McpServer::t
|
|
319
|
+
# @rbs host: Riffer::Rig::ACP::Host
|
|
320
|
+
# @rbs return: [String, Riffer::Rig::Mcp::Declaration]?
|
|
321
|
+
def declaration_of(server, host)
|
|
322
|
+
unless server.is_a?(::ACP::Types::McpServerStdio)
|
|
323
|
+
host.notify("Skipped MCP server #{server_name(server)}: only stdio servers are supported", level: :warning)
|
|
324
|
+
return nil
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
[
|
|
328
|
+
server.name,
|
|
329
|
+
Riffer::Rig::Mcp::Declaration.new(command: server.command, args: server.args, env: stdio_env(server))
|
|
330
|
+
]
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
# @rbs server: ::ACP::Types::McpServer::t
|
|
334
|
+
# @rbs return: String
|
|
335
|
+
def server_name(server)
|
|
336
|
+
case server
|
|
337
|
+
when ::ACP::Types::McpServer::Http, ::ACP::Types::McpServer::Sse then server.name
|
|
338
|
+
when Hash then server['name'].to_s
|
|
339
|
+
else 'unnamed'
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
# @rbs server: ::ACP::Types::McpServerStdio
|
|
344
|
+
# @rbs return: Hash[String, String]
|
|
345
|
+
def stdio_env(server)
|
|
346
|
+
server.env.to_h { |variable| [variable.name, variable.value] }
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
# @rbs runtime: Riffer::Rig::Runtime
|
|
350
|
+
# @rbs return: Array[::ACP::Types::AvailableCommand]
|
|
351
|
+
def available_commands(runtime)
|
|
352
|
+
runtime.commands.map do |command|
|
|
353
|
+
::ACP::Types::AvailableCommand.new(name: command.name, description: command.description)
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
end
|
|
@@ -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
|
|
@@ -3,7 +3,12 @@
|
|
|
3
3
|
module Riffer::Rig::Bundled
|
|
4
4
|
Mcp = Riffer::Rig::Extension.new('mcp') do |rig|
|
|
5
5
|
rig.settings[:servers].to_h.each do |name, server|
|
|
6
|
-
rig.mcp(
|
|
6
|
+
rig.mcp(
|
|
7
|
+
name.to_s,
|
|
8
|
+
url: server.fetch(:url),
|
|
9
|
+
headers: server[:headers].to_h.transform_keys(&:to_s),
|
|
10
|
+
auth: Hash(server[:auth])
|
|
11
|
+
)
|
|
7
12
|
end
|
|
8
13
|
end #: Riffer::Rig::Extension
|
|
9
14
|
end
|