millforge 0.1.0__py3-none-any.whl
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.
- millforge/__init__.py +1174 -0
- millforge/_forge/LICENSE +21 -0
- millforge/_forge/PROVENANCE.json +295 -0
- millforge/_forge/UPDATE_POLICY.md +24 -0
- millforge/_forge/__init__.py +14 -0
- millforge/_forge/adapter.py +2232 -0
- millforge/_forge/base_runner.py +121 -0
- millforge/_forge/clients/__init__.py +10 -0
- millforge/_forge/clients/base.py +200 -0
- millforge/_forge/context/__init__.py +23 -0
- millforge/_forge/context/manager.py +178 -0
- millforge/_forge/context/strategies.py +335 -0
- millforge/_forge/core/__init__.py +16 -0
- millforge/_forge/core/inference.py +433 -0
- millforge/_forge/core/messages.py +119 -0
- millforge/_forge/core/runner.py +479 -0
- millforge/_forge/core/steps.py +108 -0
- millforge/_forge/core/workflow.py +400 -0
- millforge/_forge/errors.py +222 -0
- millforge/_forge/guardrails/__init__.py +21 -0
- millforge/_forge/guardrails/error_tracker.py +71 -0
- millforge/_forge/guardrails/guardrails.py +194 -0
- millforge/_forge/guardrails/nudge.py +47 -0
- millforge/_forge/guardrails/response_validator.py +119 -0
- millforge/_forge/guardrails/step_enforcer.py +183 -0
- millforge/_forge/prompts/__init__.py +16 -0
- millforge/_forge/prompts/nudges.py +95 -0
- millforge/_forge/prompts/templates.py +285 -0
- millforge/_version.py +3 -0
- millforge/artifacts.py +570 -0
- millforge/base/__init__.py +97 -0
- millforge/base/composition.py +402 -0
- millforge/base/context.py +285 -0
- millforge/base/harness.py +138 -0
- millforge/base/identity.py +465 -0
- millforge/base/options.py +34 -0
- millforge/base/platform.py +17 -0
- millforge/base/prompt.py +317 -0
- millforge/base/runner.py +546 -0
- millforge/compiled_plan.py +970 -0
- millforge/compiler/__init__.py +231 -0
- millforge/compiler/artifact_validation.py +257 -0
- millforge/compiler/canonicalization.py +169 -0
- millforge/compiler/capabilities.py +66 -0
- millforge/compiler/catalogs.py +500 -0
- millforge/compiler/diagnostics.py +491 -0
- millforge/compiler/graph.py +678 -0
- millforge/compiler/lowering.py +198 -0
- millforge/compiler/output.py +692 -0
- millforge/compiler/parsing.py +1424 -0
- millforge/compiler/requests.py +1180 -0
- millforge/compiler/schema_validation.py +272 -0
- millforge/compiler/semantic.py +490 -0
- millforge/compiler/service.py +448 -0
- millforge/compiler/source.py +375 -0
- millforge/compiler/validators.py +184 -0
- millforge/connectors/__init__.py +95 -0
- millforge/connectors/admission.py +801 -0
- millforge/connectors/broker.py +202 -0
- millforge/connectors/contracts.py +1159 -0
- millforge/connectors/diagnostics.py +189 -0
- millforge/connectors/fake.py +66 -0
- millforge/connectors/runtime.py +236 -0
- millforge/contracts.py +2860 -0
- millforge/custom_tools/__init__.py +67 -0
- millforge/custom_tools/compiler.py +724 -0
- millforge/custom_tools/contracts.py +1093 -0
- millforge/custom_tools/diagnostics.py +205 -0
- millforge/eval_artifacts.py +952 -0
- millforge/eval_boundary.py +2435 -0
- millforge/eval_fixtures/__init__.py +1 -0
- millforge/eval_fixtures/default_pack/__init__.py +1 -0
- millforge/eval_fixtures/default_pack/fixtures/fixture.08a.bug_diagnosis.traceback.v1.json +52 -0
- millforge/eval_fixtures/default_pack/fixtures/fixture.08a.direct_edit.import_sort.v1.json +52 -0
- millforge/eval_fixtures/default_pack/fixtures/fixture.08a.evidence_discipline.no_source_change.v1.json +51 -0
- millforge/eval_fixtures/default_pack/fixtures/fixture.08a.false_closure.visible_green.v1.json +52 -0
- millforge/eval_fixtures/default_pack/fixtures/fixture.08a.multi_file.api_contract.v1.json +54 -0
- millforge/eval_fixtures/default_pack/fixtures/fixture.08a.recovery.malformed_artifact.v1.json +54 -0
- millforge/eval_fixtures/default_pack/manifest.json +12 -0
- millforge/eval_modes.py +1282 -0
- millforge/eval_presets.py +1398 -0
- millforge/eval_reports.py +2517 -0
- millforge/eval_suite.py +2429 -0
- millforge/eval_trials.py +2632 -0
- millforge/eval_workflow.py +794 -0
- millforge/exceptions.py +122 -0
- millforge/model_backend.py +2098 -0
- millforge/protocols.py +340 -0
- millforge/py.typed +0 -0
- millforge/runtime.py +1791 -0
- millforge/testing/__init__.py +1089 -0
- millforge/tools/__init__.py +83 -0
- millforge/tools/builtin_runtime.py +1339 -0
- millforge/tools/builtins.py +773 -0
- millforge/tools/execution.py +1545 -0
- millforge/tools/path_policy.py +155 -0
- millforge/tools/pi_compat/PI_LICENSE +21 -0
- millforge/tools/pi_compat/PROVENANCE.json +55 -0
- millforge/tools/pi_compat/UPDATE_POLICY.md +36 -0
- millforge/tools/pi_compat/__init__.py +34 -0
- millforge/tools/pi_compat/contracts.py +49 -0
- millforge/tools/pi_compat/editing.py +390 -0
- millforge/tools/pi_compat/mutations.py +57 -0
- millforge/tools/pi_compat/operations.py +401 -0
- millforge/tools/pi_compat/paths.py +155 -0
- millforge/tools/pi_compat/process.py +1375 -0
- millforge/tools/pi_compat/search.py +738 -0
- millforge/tools/pi_compat/truncation.py +267 -0
- millforge/tools/pi_compat_catalog.py +396 -0
- millforge/tools/pi_compat_runtime.py +460 -0
- millforge/tools/registry.py +553 -0
- millforge/tools/results.py +533 -0
- millforge-0.1.0.dist-info/METADATA +844 -0
- millforge-0.1.0.dist-info/RECORD +116 -0
- millforge-0.1.0.dist-info/WHEEL +4 -0
- millforge-0.1.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
"""Runtime adapters for the Pi-compatible unrestricted tool catalog."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
from collections.abc import Callable, Mapping, Sequence
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, cast
|
|
9
|
+
|
|
10
|
+
from millforge import (
|
|
11
|
+
CancellationResolver,
|
|
12
|
+
SideEffectCertainty,
|
|
13
|
+
SideEffectRecord,
|
|
14
|
+
ToolExecutionContext,
|
|
15
|
+
ToolExecutionResult,
|
|
16
|
+
ToolExecutionStatus,
|
|
17
|
+
ValidatedToolCall,
|
|
18
|
+
)
|
|
19
|
+
from millforge.compiled_plan import CompiledHarnessPlan
|
|
20
|
+
from millforge.tools.execution import CompiledToolBindingExecutor, RuntimeToolRegistry
|
|
21
|
+
from millforge.tools.pi_compat.contracts import (
|
|
22
|
+
PiCompatErrorKind,
|
|
23
|
+
PiCompatOperationResult,
|
|
24
|
+
)
|
|
25
|
+
from millforge.tools.pi_compat.editing import execute_edit
|
|
26
|
+
from millforge.tools.pi_compat.operations import execute_ls, execute_read, execute_write
|
|
27
|
+
from millforge.tools.pi_compat.process import PiCompatShellConfig, execute_bash
|
|
28
|
+
from millforge.tools.pi_compat.search import execute_find, execute_grep
|
|
29
|
+
from millforge.tools.results import (
|
|
30
|
+
ToolExecutionErrorCode,
|
|
31
|
+
canonical_sha256,
|
|
32
|
+
make_tool_result,
|
|
33
|
+
)
|
|
34
|
+
from millforge.tools.registry import ToolDescriptor
|
|
35
|
+
|
|
36
|
+
from .pi_compat_catalog import (
|
|
37
|
+
DEFAULT_BASE_TERMINAL_RESULTS,
|
|
38
|
+
_create_pi_compat_tool_snapshot_for_terminal_results,
|
|
39
|
+
_descriptors_for_terminal_results,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
__all__ = ["create_pi_compat_tool_executor"]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
_ERROR_STATUS_AND_CODE: dict[
|
|
46
|
+
PiCompatErrorKind, tuple[ToolExecutionStatus, ToolExecutionErrorCode]
|
|
47
|
+
] = {
|
|
48
|
+
PiCompatErrorKind.INVALID_ARGUMENTS: (
|
|
49
|
+
ToolExecutionStatus.NOT_EXECUTED,
|
|
50
|
+
ToolExecutionErrorCode.INVALID_ARGUMENTS,
|
|
51
|
+
),
|
|
52
|
+
PiCompatErrorKind.NOT_FOUND: (
|
|
53
|
+
ToolExecutionStatus.SOFT_FAILURE,
|
|
54
|
+
ToolExecutionErrorCode.NOT_FOUND,
|
|
55
|
+
),
|
|
56
|
+
PiCompatErrorKind.PERMISSION_DENIED: (
|
|
57
|
+
ToolExecutionStatus.SOFT_FAILURE,
|
|
58
|
+
ToolExecutionErrorCode.PERMISSION_DENIED,
|
|
59
|
+
),
|
|
60
|
+
PiCompatErrorKind.CONFLICT: (
|
|
61
|
+
ToolExecutionStatus.SOFT_FAILURE,
|
|
62
|
+
ToolExecutionErrorCode.CONFLICT,
|
|
63
|
+
),
|
|
64
|
+
PiCompatErrorKind.IO_ERROR: (
|
|
65
|
+
ToolExecutionStatus.SOFT_FAILURE,
|
|
66
|
+
ToolExecutionErrorCode.IO_ERROR,
|
|
67
|
+
),
|
|
68
|
+
PiCompatErrorKind.PROCESS_EXIT_NONZERO: (
|
|
69
|
+
ToolExecutionStatus.SOFT_FAILURE,
|
|
70
|
+
ToolExecutionErrorCode.PROCESS_EXIT_NONZERO,
|
|
71
|
+
),
|
|
72
|
+
PiCompatErrorKind.PROCESS_TIMEOUT: (
|
|
73
|
+
ToolExecutionStatus.TIMED_OUT,
|
|
74
|
+
ToolExecutionErrorCode.TIMEOUT,
|
|
75
|
+
),
|
|
76
|
+
PiCompatErrorKind.CANCELLED: (
|
|
77
|
+
ToolExecutionStatus.CANCELLED,
|
|
78
|
+
ToolExecutionErrorCode.CANCELLED,
|
|
79
|
+
),
|
|
80
|
+
PiCompatErrorKind.PROCESS_LAUNCH_ERROR: (
|
|
81
|
+
ToolExecutionStatus.HARD_FAILURE,
|
|
82
|
+
ToolExecutionErrorCode.PROCESS_LAUNCH_ERROR,
|
|
83
|
+
),
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
_SIDE_EFFECT_RECORD_CERTAINTIES = frozenset(
|
|
87
|
+
{
|
|
88
|
+
SideEffectCertainty.CONFIRMED_ABSENT,
|
|
89
|
+
SideEffectCertainty.ROLLED_BACK,
|
|
90
|
+
SideEffectCertainty.COMPLETION_UNKNOWN,
|
|
91
|
+
}
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class _PiCompatCancellationBridge:
|
|
96
|
+
"""Expose only the poll/wait protocol owned by the 11A bash operation."""
|
|
97
|
+
|
|
98
|
+
def __init__(self, token: Any) -> None:
|
|
99
|
+
self._token = token
|
|
100
|
+
|
|
101
|
+
def is_cancelled(self) -> bool:
|
|
102
|
+
return self._token.is_cancelled()
|
|
103
|
+
|
|
104
|
+
async def wait(self) -> None:
|
|
105
|
+
await self._token.wait()
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def create_pi_compat_tool_executor(
|
|
109
|
+
plan: CompiledHarnessPlan,
|
|
110
|
+
*,
|
|
111
|
+
cwd: Path,
|
|
112
|
+
cancellation_resolver: CancellationResolver,
|
|
113
|
+
shell_config: PiCompatShellConfig | None = None,
|
|
114
|
+
) -> CompiledToolBindingExecutor:
|
|
115
|
+
"""Create a plan-scoped executor for the Pi-compatible tool catalog."""
|
|
116
|
+
|
|
117
|
+
return _create_pi_compat_tool_executor_for_terminal_results(
|
|
118
|
+
plan,
|
|
119
|
+
cwd=cwd,
|
|
120
|
+
cancellation_resolver=cancellation_resolver,
|
|
121
|
+
shell_config=shell_config,
|
|
122
|
+
legal_terminal_results=DEFAULT_BASE_TERMINAL_RESULTS,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _create_pi_compat_tool_executor_for_terminal_results(
|
|
127
|
+
plan: CompiledHarnessPlan,
|
|
128
|
+
*,
|
|
129
|
+
cwd: Path,
|
|
130
|
+
cancellation_resolver: CancellationResolver,
|
|
131
|
+
shell_config: PiCompatShellConfig | None,
|
|
132
|
+
legal_terminal_results: tuple[str, ...],
|
|
133
|
+
) -> CompiledToolBindingExecutor:
|
|
134
|
+
"""Create an executor from already-canonical terminal configuration."""
|
|
135
|
+
|
|
136
|
+
if not cwd.is_absolute():
|
|
137
|
+
raise ValueError("cwd must be absolute")
|
|
138
|
+
if _plan_uses_bash(plan) and shell_config is None:
|
|
139
|
+
raise ValueError("Pi-compatible bash requires a resolved shell_config")
|
|
140
|
+
|
|
141
|
+
descriptors = {
|
|
142
|
+
descriptor.tool_id: descriptor
|
|
143
|
+
for descriptor in _descriptors_for_terminal_results(legal_terminal_results)
|
|
144
|
+
}
|
|
145
|
+
read = descriptors["builtin.pi_compat.read"]
|
|
146
|
+
bash = descriptors["builtin.pi_compat.bash"]
|
|
147
|
+
edit = descriptors["builtin.pi_compat.edit"]
|
|
148
|
+
write = descriptors["builtin.pi_compat.write"]
|
|
149
|
+
grep = descriptors["builtin.pi_compat.grep"]
|
|
150
|
+
find = descriptors["builtin.pi_compat.find"]
|
|
151
|
+
ls = descriptors["builtin.pi_compat.ls"]
|
|
152
|
+
|
|
153
|
+
registry = RuntimeToolRegistry()
|
|
154
|
+
|
|
155
|
+
def read_implementation(
|
|
156
|
+
call: ValidatedToolCall, _context: ToolExecutionContext
|
|
157
|
+
) -> ToolExecutionResult:
|
|
158
|
+
return _adapt_operation_result(
|
|
159
|
+
call,
|
|
160
|
+
read,
|
|
161
|
+
execute_read(
|
|
162
|
+
cwd=cwd,
|
|
163
|
+
path=cast(str, call.arguments["path"]),
|
|
164
|
+
offset=cast(int | float | None, call.arguments.get("offset")),
|
|
165
|
+
limit=cast(int | float | None, call.arguments.get("limit")),
|
|
166
|
+
),
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
async def bash_implementation(
|
|
170
|
+
call: ValidatedToolCall, context: ToolExecutionContext
|
|
171
|
+
) -> ToolExecutionResult:
|
|
172
|
+
timeout_seconds = _bash_timeout_seconds(call, context, bash)
|
|
173
|
+
if timeout_seconds <= 0 or not math.isfinite(timeout_seconds):
|
|
174
|
+
return _pre_entry_result(
|
|
175
|
+
call,
|
|
176
|
+
bash,
|
|
177
|
+
ToolExecutionErrorCode.TIMEOUT,
|
|
178
|
+
"tool deadline expired before implementation entry",
|
|
179
|
+
)
|
|
180
|
+
if context.cancellation_requested:
|
|
181
|
+
return _pre_entry_result(
|
|
182
|
+
call,
|
|
183
|
+
bash,
|
|
184
|
+
ToolExecutionErrorCode.CANCELLED,
|
|
185
|
+
"tool call was cancelled before implementation entry",
|
|
186
|
+
)
|
|
187
|
+
token = cancellation_resolver.resolve(context.cancellation)
|
|
188
|
+
cancellation = _PiCompatCancellationBridge(token)
|
|
189
|
+
if cancellation.is_cancelled():
|
|
190
|
+
return _pre_entry_result(
|
|
191
|
+
call,
|
|
192
|
+
bash,
|
|
193
|
+
ToolExecutionErrorCode.CANCELLED,
|
|
194
|
+
"tool call was cancelled before implementation entry",
|
|
195
|
+
)
|
|
196
|
+
assert shell_config is not None
|
|
197
|
+
operation_result = await execute_bash(
|
|
198
|
+
cwd=cwd,
|
|
199
|
+
command=cast(str, call.arguments["command"]),
|
|
200
|
+
timeout_seconds=timeout_seconds,
|
|
201
|
+
cancellation=cancellation,
|
|
202
|
+
shell_config=shell_config,
|
|
203
|
+
)
|
|
204
|
+
return _adapt_operation_result(call, bash, operation_result)
|
|
205
|
+
|
|
206
|
+
def edit_implementation(
|
|
207
|
+
call: ValidatedToolCall, _context: ToolExecutionContext
|
|
208
|
+
) -> ToolExecutionResult:
|
|
209
|
+
return _adapt_operation_result(
|
|
210
|
+
call,
|
|
211
|
+
edit,
|
|
212
|
+
execute_edit(
|
|
213
|
+
cwd=cwd,
|
|
214
|
+
path=cast(str, call.arguments["path"]),
|
|
215
|
+
edits=cast(Sequence[Mapping[str, object]], call.arguments["edits"]),
|
|
216
|
+
),
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
def write_implementation(
|
|
220
|
+
call: ValidatedToolCall, _context: ToolExecutionContext
|
|
221
|
+
) -> ToolExecutionResult:
|
|
222
|
+
return _adapt_operation_result(
|
|
223
|
+
call,
|
|
224
|
+
write,
|
|
225
|
+
execute_write(
|
|
226
|
+
cwd=cwd,
|
|
227
|
+
path=cast(str, call.arguments["path"]),
|
|
228
|
+
content=cast(str, call.arguments["content"]),
|
|
229
|
+
),
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
def grep_implementation(
|
|
233
|
+
call: ValidatedToolCall, _context: ToolExecutionContext
|
|
234
|
+
) -> ToolExecutionResult:
|
|
235
|
+
return _adapt_operation_result(
|
|
236
|
+
call,
|
|
237
|
+
grep,
|
|
238
|
+
execute_grep(
|
|
239
|
+
cwd=cwd,
|
|
240
|
+
pattern=cast(str, call.arguments["pattern"]),
|
|
241
|
+
path=cast(str | None, call.arguments.get("path")),
|
|
242
|
+
glob=cast(str | None, call.arguments.get("glob")),
|
|
243
|
+
ignoreCase=cast(bool | None, call.arguments.get("ignoreCase")),
|
|
244
|
+
literal=cast(bool | None, call.arguments.get("literal")),
|
|
245
|
+
context=cast(int | float | None, call.arguments.get("context")),
|
|
246
|
+
limit=cast(int | float | None, call.arguments.get("limit")),
|
|
247
|
+
),
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
def find_implementation(
|
|
251
|
+
call: ValidatedToolCall, _context: ToolExecutionContext
|
|
252
|
+
) -> ToolExecutionResult:
|
|
253
|
+
return _adapt_operation_result(
|
|
254
|
+
call,
|
|
255
|
+
find,
|
|
256
|
+
execute_find(
|
|
257
|
+
cwd=cwd,
|
|
258
|
+
pattern=cast(str, call.arguments["pattern"]),
|
|
259
|
+
path=cast(str | None, call.arguments.get("path")),
|
|
260
|
+
limit=cast(int | float | None, call.arguments.get("limit")),
|
|
261
|
+
),
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
def ls_implementation(
|
|
265
|
+
call: ValidatedToolCall, _context: ToolExecutionContext
|
|
266
|
+
) -> ToolExecutionResult:
|
|
267
|
+
return _adapt_operation_result(
|
|
268
|
+
call,
|
|
269
|
+
ls,
|
|
270
|
+
execute_ls(
|
|
271
|
+
cwd=cwd,
|
|
272
|
+
path=cast(str | None, call.arguments.get("path")),
|
|
273
|
+
limit=cast(int | float | None, call.arguments.get("limit")),
|
|
274
|
+
),
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
registry.register(read.implementation_id, read_implementation)
|
|
278
|
+
registry.register(bash.implementation_id, bash_implementation)
|
|
279
|
+
registry.register(edit.implementation_id, edit_implementation)
|
|
280
|
+
registry.register(write.implementation_id, write_implementation)
|
|
281
|
+
registry.register(grep.implementation_id, grep_implementation)
|
|
282
|
+
registry.register(find.implementation_id, find_implementation)
|
|
283
|
+
registry.register(ls.implementation_id, ls_implementation)
|
|
284
|
+
for descriptor in descriptors.values():
|
|
285
|
+
if descriptor.side_effect_class.value == "terminal":
|
|
286
|
+
registry.register(
|
|
287
|
+
descriptor.implementation_id,
|
|
288
|
+
_terminal_implementation(descriptor),
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
return CompiledToolBindingExecutor(
|
|
292
|
+
plan=plan,
|
|
293
|
+
descriptor_snapshot=_create_pi_compat_tool_snapshot_for_terminal_results(
|
|
294
|
+
legal_terminal_results
|
|
295
|
+
),
|
|
296
|
+
runtime_registry=registry,
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _plan_uses_bash(plan: CompiledHarnessPlan) -> bool:
|
|
301
|
+
return any(node.binding.tool_id == "builtin.pi_compat.bash" for node in plan.nodes)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _terminal_implementation(
|
|
305
|
+
descriptor: ToolDescriptor,
|
|
306
|
+
) -> Callable[[ValidatedToolCall, ToolExecutionContext], ToolExecutionResult]:
|
|
307
|
+
def implementation(
|
|
308
|
+
call: ValidatedToolCall, _context: ToolExecutionContext
|
|
309
|
+
) -> ToolExecutionResult:
|
|
310
|
+
return _terminal_result(call, descriptor)
|
|
311
|
+
|
|
312
|
+
return implementation
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def _bash_timeout_seconds(
|
|
316
|
+
call: ValidatedToolCall,
|
|
317
|
+
context: ToolExecutionContext,
|
|
318
|
+
descriptor: ToolDescriptor,
|
|
319
|
+
) -> float:
|
|
320
|
+
supplied_timeout = cast(int | float | None, call.arguments.get("timeout"))
|
|
321
|
+
model_timeout = (
|
|
322
|
+
float(supplied_timeout)
|
|
323
|
+
if supplied_timeout is not None and supplied_timeout > 0
|
|
324
|
+
else math.inf
|
|
325
|
+
)
|
|
326
|
+
return min(
|
|
327
|
+
float(descriptor.timeout_policy.timeout_seconds),
|
|
328
|
+
model_timeout,
|
|
329
|
+
context.timeout.timeout_seconds,
|
|
330
|
+
context.deadline.remaining(lambda: context.current_monotonic),
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _adapt_operation_result(
|
|
335
|
+
call: ValidatedToolCall,
|
|
336
|
+
descriptor: ToolDescriptor,
|
|
337
|
+
operation_result: PiCompatOperationResult,
|
|
338
|
+
) -> ToolExecutionResult:
|
|
339
|
+
error_code = _operation_error_code(operation_result.error_kind)
|
|
340
|
+
status = (
|
|
341
|
+
ToolExecutionStatus.SUCCESS
|
|
342
|
+
if operation_result.error_kind is None
|
|
343
|
+
else _ERROR_STATUS_AND_CODE[operation_result.error_kind][0]
|
|
344
|
+
)
|
|
345
|
+
certainty = SideEffectCertainty(operation_result.side_effect_state.value)
|
|
346
|
+
side_effect_record = _side_effect_record(
|
|
347
|
+
certainty=certainty,
|
|
348
|
+
error_code=error_code,
|
|
349
|
+
summary=operation_result.model_text,
|
|
350
|
+
)
|
|
351
|
+
structured_data: dict[str, Any] = {
|
|
352
|
+
"model_text": operation_result.model_text,
|
|
353
|
+
"truncated": operation_result.truncated,
|
|
354
|
+
}
|
|
355
|
+
if operation_result.exit_code is not None:
|
|
356
|
+
structured_data["exit_code"] = operation_result.exit_code
|
|
357
|
+
if (
|
|
358
|
+
operation_result.error_kind is None
|
|
359
|
+
and operation_result.changed_path is not None
|
|
360
|
+
and descriptor.tool_id in {"builtin.pi_compat.edit", "builtin.pi_compat.write"}
|
|
361
|
+
):
|
|
362
|
+
structured_data["changed_path"] = operation_result.changed_path.as_posix()
|
|
363
|
+
return make_tool_result(
|
|
364
|
+
call_id=call.call_id,
|
|
365
|
+
status=status,
|
|
366
|
+
code=error_code,
|
|
367
|
+
summary=operation_result.model_text,
|
|
368
|
+
structured_data=structured_data,
|
|
369
|
+
side_effect_class=descriptor.side_effect_class,
|
|
370
|
+
idempotency=descriptor.idempotency,
|
|
371
|
+
side_effect_certainty=certainty,
|
|
372
|
+
side_effect_record=side_effect_record,
|
|
373
|
+
input_sha256=canonical_sha256(call.arguments),
|
|
374
|
+
retryable=False,
|
|
375
|
+
output_policy=descriptor.output_policy,
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def _operation_error_code(
|
|
380
|
+
error_kind: PiCompatErrorKind | None,
|
|
381
|
+
) -> ToolExecutionErrorCode | None:
|
|
382
|
+
if error_kind is None:
|
|
383
|
+
return None
|
|
384
|
+
return _ERROR_STATUS_AND_CODE[error_kind][1]
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def _side_effect_record(
|
|
388
|
+
*,
|
|
389
|
+
certainty: SideEffectCertainty,
|
|
390
|
+
error_code: ToolExecutionErrorCode | None,
|
|
391
|
+
summary: str,
|
|
392
|
+
) -> SideEffectRecord | None:
|
|
393
|
+
if certainty not in _SIDE_EFFECT_RECORD_CERTAINTIES:
|
|
394
|
+
return None
|
|
395
|
+
if error_code is None:
|
|
396
|
+
raise ValueError("uncertain Pi-compatible operation result requires an error")
|
|
397
|
+
return SideEffectRecord(
|
|
398
|
+
certainty=certainty,
|
|
399
|
+
detail_code=error_code.value,
|
|
400
|
+
summary=summary,
|
|
401
|
+
retry_allowed=False,
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def _terminal_result(
|
|
406
|
+
call: ValidatedToolCall,
|
|
407
|
+
descriptor: ToolDescriptor,
|
|
408
|
+
) -> ToolExecutionResult:
|
|
409
|
+
summary = cast(str, call.arguments["summary"])
|
|
410
|
+
if not summary.strip():
|
|
411
|
+
return make_tool_result(
|
|
412
|
+
call_id=call.call_id,
|
|
413
|
+
status=ToolExecutionStatus.NOT_EXECUTED,
|
|
414
|
+
code=ToolExecutionErrorCode.INVALID_ARGUMENTS,
|
|
415
|
+
summary="terminal summary must not be blank",
|
|
416
|
+
structured_data={
|
|
417
|
+
"model_text": "terminal summary must not be blank",
|
|
418
|
+
"truncated": False,
|
|
419
|
+
},
|
|
420
|
+
side_effect_class=descriptor.side_effect_class,
|
|
421
|
+
idempotency=descriptor.idempotency,
|
|
422
|
+
side_effect_certainty=SideEffectCertainty.NOT_ATTEMPTED,
|
|
423
|
+
input_sha256=canonical_sha256(call.arguments),
|
|
424
|
+
retryable=False,
|
|
425
|
+
output_policy=descriptor.output_policy,
|
|
426
|
+
)
|
|
427
|
+
return make_tool_result(
|
|
428
|
+
call_id=call.call_id,
|
|
429
|
+
status=ToolExecutionStatus.SUCCESS,
|
|
430
|
+
code=None,
|
|
431
|
+
summary=summary,
|
|
432
|
+
structured_data={"model_text": summary, "truncated": False},
|
|
433
|
+
side_effect_class=descriptor.side_effect_class,
|
|
434
|
+
idempotency=descriptor.idempotency,
|
|
435
|
+
side_effect_certainty=SideEffectCertainty.CONFIRMED_COMPLETE,
|
|
436
|
+
input_sha256=canonical_sha256(call.arguments),
|
|
437
|
+
retryable=False,
|
|
438
|
+
output_policy=descriptor.output_policy,
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def _pre_entry_result(
|
|
443
|
+
call: ValidatedToolCall,
|
|
444
|
+
descriptor: ToolDescriptor,
|
|
445
|
+
code: ToolExecutionErrorCode,
|
|
446
|
+
summary: str,
|
|
447
|
+
) -> ToolExecutionResult:
|
|
448
|
+
return make_tool_result(
|
|
449
|
+
call_id=call.call_id,
|
|
450
|
+
status=ToolExecutionStatus.NOT_EXECUTED,
|
|
451
|
+
code=code,
|
|
452
|
+
summary=summary,
|
|
453
|
+
structured_data={"model_text": summary, "truncated": False},
|
|
454
|
+
side_effect_class=descriptor.side_effect_class,
|
|
455
|
+
idempotency=descriptor.idempotency,
|
|
456
|
+
side_effect_certainty=SideEffectCertainty.NOT_ATTEMPTED,
|
|
457
|
+
input_sha256=canonical_sha256(call.arguments),
|
|
458
|
+
retryable=False,
|
|
459
|
+
output_policy=descriptor.output_policy,
|
|
460
|
+
)
|