ask-sandbox-providers 0.1.4 → 0.1.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3a0d6fd353895bba424a5a181ccf55e6c0da0273dc9a411f65db972830c99516
4
- data.tar.gz: 84eb48ca0657db9cf00232559e4ab977fe31c1bb7c530881f59e1f9b0ef70bd1
3
+ metadata.gz: 8883fc8f89fd8afef751cd38bbd2c22180fc85191176d63a3ff78059e5c638c5
4
+ data.tar.gz: 710bb7bbda30c7062ab3c3c512693ee56737b5bae3b2c44f80273f1b2d643a26
5
5
  SHA512:
6
- metadata.gz: c7145f2991c6989fbdd58c70fe301a66a05d2848fe0244059c6d082c7cb75e36089271f1ef02b8d903c538945f6db813b8efbe498513b778663ba5c07623f154
7
- data.tar.gz: bd310ad9bb7a8f5ff51e03911856d96348fc4091b824f09e53eca3b3eb32ec0c062e3a1850c0d810caa4399c324771be057ee1d8cfceb9f98d70b965bae8ef2c
6
+ metadata.gz: 9884fc2a731826b9357577e04f24ed60ccdab668712892f422539af1618a5bf9d2630f8ca675a29fd108bbb4edc4937e5f85c459ac965afdde9dbc367a795901
7
+ data.tar.gz: 17e5fe56218d7d264dfcbd84786c6bb9e0fa80be2393a4113706889ee9c78c429c6fc83e1913c6b75f0cb198c3c18b3b9dff4f42c35a93129494e6a5b7d58861
data/CHANGELOG.md CHANGED
@@ -25,6 +25,13 @@
25
25
  - Infrastructure: rubocop, overcommit, bin/setup, CI matrix, gemspec test.
26
26
  # Changelog
27
27
 
28
+ ## Unreleased
29
+
30
+ ### Added
31
+
32
+ - `Ask::Sandbox::RuntimeExecutor` bridges sandbox providers to the
33
+ `ask-runtime` tool executor contract.
34
+
28
35
  ## [0.1.1] - 2026-06-18
29
36
 
30
37
  ### Fixed
data/README.md CHANGED
@@ -44,6 +44,26 @@ Daytona resolves its API key from `Ask::Auth.lookup("DAYTONA_API_KEY")` or `ENV[
44
44
 
45
45
  Every provider returns `Ask::Sandbox::Result`, a `Data` object with `stdout`, `stderr`, `exit_code`, and `timed_out` fields, plus `#success?` (true when `exit_code == 0`).
46
46
 
47
+ ## Runtime integration
48
+
49
+ `Ask::Sandbox::RuntimeExecutor` adapts any configured sandbox provider to the
50
+ `ask-runtime` execution contract:
51
+
52
+ ```ruby
53
+ executor = Ask::Sandbox::RuntimeExecutor.new(Ask::Sandbox.provider)
54
+ call = Ask::Runtime::ToolCall.new(
55
+ tool_name: "sandbox.execute",
56
+ input: { command: ["ruby", "-e", "puts 1"] }
57
+ )
58
+ result = executor.execute(call)
59
+ result.output[:stdout] # => "1\n"
60
+ ```
61
+
62
+ The adapter carries `ExecutionContext#workspace` into the provider when no
63
+ explicit `workdir` is supplied, maps non-zero exits and provider exceptions to
64
+ `ToolResult` failures, maps provider timeouts to timed-out results, and emits
65
+ the standard runtime lifecycle events.
66
+
47
67
  ## Full documentation
48
68
 
49
69
  The full ask-rb documentation lives at https://ask-rb.github.io/ask-docs. [ask-sandbox-providers in depth](https://ask-rb.github.io/ask-docs/core/sandbox) covers each provider and the hardening details. API reference: https://ask-rb.github.io/ask-docs/reference/api.
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ask/runtime"
4
+
5
+ module Ask
6
+ module Sandbox
7
+ # Adapts a sandbox provider to the ask-runtime tool executor contract.
8
+ #
9
+ # The executor accepts a ToolCall whose input contains +command+ and
10
+ # optional sandbox options (+timeout+, +workdir+, +env+, and +stdin+).
11
+ # When no workdir is supplied, the execution context's workspace is used.
12
+ #
13
+ # executor = Ask::Sandbox::RuntimeExecutor.new(Ask::Sandbox.provider)
14
+ # call = Ask::Runtime::ToolCall.new(
15
+ # tool_name: "sandbox.execute",
16
+ # input: { command: ["ruby", "-e", "puts 1"] }
17
+ # )
18
+ # executor.execute(call).output[:stdout] # => "1\n"
19
+ class RuntimeExecutor
20
+ include Ask::Runtime::ToolExecutor
21
+
22
+ TOOL_NAME = "sandbox.execute"
23
+
24
+ attr_reader :provider
25
+
26
+ def initialize(provider = nil)
27
+ @provider = provider || Ask::Sandbox.provider
28
+ end
29
+
30
+ def supported_tools
31
+ [TOOL_NAME]
32
+ end
33
+
34
+ # Execute a sandbox command and normalize its result for ask-runtime.
35
+ #
36
+ # @param tool_call [Ask::Runtime::ToolCall]
37
+ # @param context [Ask::Runtime::ExecutionContext, nil]
38
+ # @return [Ask::Runtime::ToolResult]
39
+ def execute(tool_call, context: nil)
40
+ context ||= Ask::Runtime::ExecutionContext.new
41
+ started_at = Time.now
42
+ started_call = tool_call.with(state: :running, started_at: started_at)
43
+ emit(Ask::Runtime::Events::ToolStarted, tool_call: started_call, context: context)
44
+
45
+ return cancel(started_call, context, started_at, "Cancelled before execution") if context.cancelled?
46
+
47
+ result = execute_command(tool_call.input, context)
48
+ return cancel(started_call, context, started_at, "Cancelled during execution") if context.cancelled?
49
+
50
+ finish(started_call, context, result, started_at)
51
+ rescue StandardError => e
52
+ finish(started_call, context, Ask::Runtime::ToolResult.failure(
53
+ "#{e.class}: #{e.message}"
54
+ ), started_at)
55
+ end
56
+
57
+ private
58
+
59
+ def execute_command(input, context)
60
+ command = input_value(input, :command)
61
+ raise ArgumentError, "command is required" if command.nil?
62
+
63
+ options = {
64
+ workdir: input_value(input, :workdir) || context.workspace,
65
+ env: input_value(input, :env) || {},
66
+ stdin: input_value(input, :stdin)
67
+ }
68
+ timeout = input_value(input, :timeout)
69
+ options[:timeout] = timeout unless timeout.nil?
70
+
71
+ sandbox_result = @provider.call(command, **options)
72
+ metadata = {
73
+ stdout: sandbox_result.stdout,
74
+ stderr: sandbox_result.stderr,
75
+ exit_code: sandbox_result.exit_code,
76
+ timed_out: sandbox_result.timed_out
77
+ }
78
+
79
+ if sandbox_result.timed_out
80
+ timeout_result("Sandbox command timed out", metadata)
81
+ elsif sandbox_result.success?
82
+ Ask::Runtime::ToolResult.success(data: metadata, metadata: metadata)
83
+ else
84
+ Ask::Runtime::ToolResult.failure(
85
+ "Command exited with status #{sandbox_result.exit_code}", metadata: metadata
86
+ )
87
+ end
88
+ end
89
+
90
+ def input_value(input, key)
91
+ input[key] || input[key.to_s]
92
+ end
93
+
94
+ def timeout_result(message, metadata)
95
+ Ask::Runtime::ToolResult.new(
96
+ result: Ask::Result.failure(message, metadata: metadata),
97
+ outcome: :timeout
98
+ )
99
+ end
100
+
101
+ def finish(started_call, context, result, started_at)
102
+ finished_at = Time.now
103
+ duration = finished_at - started_at
104
+ result = with_duration(result, duration)
105
+ state = if result.timeout?
106
+ :timed_out
107
+ elsif result.failure?
108
+ :failed
109
+ else
110
+ :completed
111
+ end
112
+ terminal_call = started_call.with(
113
+ state: state,
114
+ tool_result: result,
115
+ error: result.error_message,
116
+ finished_at: finished_at
117
+ )
118
+ event_class = {
119
+ completed: Ask::Runtime::Events::ToolCompleted,
120
+ failed: Ask::Runtime::Events::ToolFailed,
121
+ timed_out: Ask::Runtime::Events::ToolTimedOut
122
+ }.fetch(state)
123
+ emit(event_class, tool_call: terminal_call, tool_result: result,
124
+ context: context, timestamp: finished_at, duration: duration)
125
+ result
126
+ end
127
+
128
+ def cancel(started_call, context, started_at, reason)
129
+ finished_at = Time.now
130
+ duration = finished_at - started_at
131
+ result = with_duration(Ask::Runtime::ToolResult.cancelled(reason), duration)
132
+ terminal_call = started_call.with(
133
+ state: :cancelled,
134
+ tool_result: result,
135
+ error: result.error_message,
136
+ finished_at: finished_at
137
+ )
138
+ emit(Ask::Runtime::Events::ToolCancelled, tool_call: terminal_call,
139
+ tool_result: result, context: context, timestamp: finished_at, duration: duration)
140
+ result
141
+ end
142
+
143
+ def with_duration(result, duration)
144
+ Ask::Runtime::ToolResult.new(
145
+ result: result.result,
146
+ outcome: result.outcome,
147
+ duration: duration
148
+ )
149
+ end
150
+
151
+ def emit(event_class, tool_call:, context:, **attrs)
152
+ event = event_class.new(
153
+ tool_call: tool_call,
154
+ execution_context: context,
155
+ timestamp: attrs.delete(:timestamp) || Time.now,
156
+ **attrs
157
+ )
158
+ event_type = event_class.name.split("::").last
159
+ .gsub(/([a-z])([A-Z])/, '\\1_\\2').downcase.to_sym
160
+ context.event_sink.emit(event_type, event: event)
161
+ end
162
+ end
163
+ end
164
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ask
4
4
  module Sandbox
5
- VERSION = "0.1.4"
5
+ VERSION = "0.1.5"
6
6
  end
7
7
  end
@@ -6,6 +6,7 @@ require_relative "ask/sandbox/local"
6
6
  require_relative "ask/sandbox/docker"
7
7
  require_relative "ask/sandbox/daytona"
8
8
  require_relative "ask/sandbox/cloudflare"
9
+ require_relative "ask/sandbox/runtime_executor"
9
10
 
10
11
  module Ask
11
12
  module Sandbox
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ask-sandbox-providers
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kaka Ruto
@@ -9,6 +9,20 @@ bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: ask-runtime
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: 0.1.0
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: 0.1.0
12
26
  - !ruby/object:Gem::Dependency
13
27
  name: minitest
14
28
  requirement: !ruby/object:Gem::Requirement
@@ -69,6 +83,7 @@ files:
69
83
  - lib/ask/sandbox/daytona.rb
70
84
  - lib/ask/sandbox/docker.rb
71
85
  - lib/ask/sandbox/local.rb
86
+ - lib/ask/sandbox/runtime_executor.rb
72
87
  - lib/ask/sandbox/version.rb
73
88
  homepage: https://github.com/ask-rb/ask-sandbox-providers
74
89
  licenses:
@@ -91,7 +106,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
91
106
  - !ruby/object:Gem::Version
92
107
  version: '0'
93
108
  requirements: []
94
- rubygems_version: 4.0.3
109
+ rubygems_version: 4.0.18
95
110
  specification_version: 4
96
111
  summary: Sandbox providers for the ask-rb ecosystem
97
112
  test_files: []