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 +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +20 -0
- data/lib/ask/sandbox/runtime_executor.rb +164 -0
- data/lib/ask/sandbox/version.rb +1 -1
- data/lib/ask-sandbox-providers.rb +1 -0
- metadata +17 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8883fc8f89fd8afef751cd38bbd2c22180fc85191176d63a3ff78059e5c638c5
|
|
4
|
+
data.tar.gz: 710bb7bbda30c7062ab3c3c512693ee56737b5bae3b2c44f80273f1b2d643a26
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
data/lib/ask/sandbox/version.rb
CHANGED
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
|
+
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.
|
|
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: []
|