mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-09 08:22:19 +02:00
use native python for directory artifact completion
This commit is contained in:
+1
-1
@@ -1 +1 @@
|
|||||||
0.20.13
|
0.20.14
|
||||||
|
|||||||
@@ -1603,16 +1603,21 @@ def required_artifact_completion_tool_choice(required_artifacts, offered_schemas
|
|||||||
targets = [str(path or '').strip().rstrip('/') for path in required_artifacts]
|
targets = [str(path or '').strip().rstrip('/') for path in required_artifacts]
|
||||||
if not targets:
|
if not targets:
|
||||||
return None
|
return None
|
||||||
# A required directory commonly contains several files and may need a
|
|
||||||
# programmatic extractor. Requiring *a* tool call preserves forward
|
|
||||||
# progress without trapping the model in one enormous write_file payload.
|
|
||||||
if len(targets) == 1 and not Path(targets[0]).suffix:
|
|
||||||
return 'required'
|
|
||||||
offered = {
|
offered = {
|
||||||
canonical((schema.get('function') or {}).get('name')):
|
canonical((schema.get('function') or {}).get('name')):
|
||||||
(schema.get('function') or {}).get('name')
|
(schema.get('function') or {}).get('name')
|
||||||
for schema in offered_schemas
|
for schema in offered_schemas
|
||||||
}
|
}
|
||||||
|
# A required directory commonly contains several files and needs a
|
||||||
|
# programmatic extractor. Native Python avoids shell-quoting failures and
|
||||||
|
# one enormous multi-file write_file payload. If Python is unavailable,
|
||||||
|
# require any offered tool rather than forcing the text writer.
|
||||||
|
if len(targets) == 1 and not Path(targets[0]).suffix:
|
||||||
|
name = offered.get('python')
|
||||||
|
return (
|
||||||
|
{'type': 'function', 'function': {'name': name}}
|
||||||
|
if name else 'required'
|
||||||
|
)
|
||||||
preferred = (
|
preferred = (
|
||||||
'python'
|
'python'
|
||||||
if len(targets) == 1
|
if len(targets) == 1
|
||||||
@@ -4689,6 +4694,16 @@ async def stream_preview(*, endpoint_url, model, messages, headers, turn_contrac
|
|||||||
)
|
)
|
||||||
if completion_choice is not None:
|
if completion_choice is not None:
|
||||||
request['tool_choice'] = completion_choice
|
request['tool_choice'] = completion_choice
|
||||||
|
if isinstance(completion_choice, dict):
|
||||||
|
selected_name = (
|
||||||
|
completion_choice.get('function') or {}
|
||||||
|
).get('name')
|
||||||
|
selected = [
|
||||||
|
schema for schema in request.get('tools') or []
|
||||||
|
if (schema.get('function') or {}).get('name') == selected_name
|
||||||
|
]
|
||||||
|
if selected:
|
||||||
|
request['tools'] = selected
|
||||||
# Whole rewrites and inline feedback each have one typed
|
# Whole rewrites and inline feedback each have one typed
|
||||||
# editor output owner. Bind that sole channel at protocol
|
# editor output owner. Bind that sole channel at protocol
|
||||||
# level so prose cannot masquerade as an applied edit or
|
# level so prose cannot masquerade as an applied edit or
|
||||||
|
|||||||
@@ -693,6 +693,10 @@ def test_required_directory_artifact_allows_any_offered_mutation_tool():
|
|||||||
assert required_artifact_completion_tool_choice(
|
assert required_artifact_completion_tool_choice(
|
||||||
('/tmp_workspace/results',), offered,
|
('/tmp_workspace/results',), offered,
|
||||||
) == 'required'
|
) == 'required'
|
||||||
|
offered.append({'type': 'function', 'function': {'name': 'python'}})
|
||||||
|
assert required_artifact_completion_tool_choice(
|
||||||
|
('/tmp_workspace/results',), offered,
|
||||||
|
) == {'type': 'function', 'function': {'name': 'python'}}
|
||||||
|
|
||||||
|
|
||||||
def test_action_promise_response_rejects_future_work_but_not_real_answers():
|
def test_action_promise_response_rejects_future_work_but_not_real_answers():
|
||||||
@@ -5513,15 +5517,14 @@ async def test_native_stream_reserves_remaining_budget_for_required_artifact(mon
|
|||||||
])
|
])
|
||||||
payloads.extend([
|
payloads.extend([
|
||||||
{"choices": [{"delta": {"tool_calls": [{
|
{"choices": [{"delta": {"tool_calls": [{
|
||||||
"index": 0, "id": "hallucinated-bash",
|
"index": 0, "id": "programmatic-directory-write",
|
||||||
"function": {"name": "bash", "arguments": json.dumps({
|
"function": {"name": "python", "arguments": json.dumps({
|
||||||
"command": "echo should-not-run",
|
"code": (
|
||||||
})},
|
"from pathlib import Path\n"
|
||||||
}]}}]},
|
"p = Path('/tmp_workspace/results/out.md')\n"
|
||||||
{"choices": [{"delta": {"tool_calls": [{
|
"p.parent.mkdir(parents=True, exist_ok=True)\n"
|
||||||
"index": 0, "id": "write",
|
"p.write_text('evidence')"
|
||||||
"function": {"name": "write_file", "arguments": json.dumps({
|
),
|
||||||
"path": "/tmp_workspace/results/out.md", "content": "evidence",
|
|
||||||
})},
|
})},
|
||||||
}]}}]},
|
}]}}]},
|
||||||
{"choices": [{"delta": {"content": "Saved."}}]},
|
{"choices": [{"delta": {"content": "Saved."}}]},
|
||||||
@@ -5592,20 +5595,21 @@ async def test_native_stream_reserves_remaining_budget_for_required_artifact(mon
|
|||||||
and event.get("stage") == "provider_request"
|
and event.get("stage") == "provider_request"
|
||||||
and event.get("artifact_write_phase") is True
|
and event.get("artifact_write_phase") is True
|
||||||
)
|
)
|
||||||
assert "write_file" in request_contract["offered_tools"]
|
assert request_contract["offered_tools"] == ["python"]
|
||||||
assert "web_search" not in request_contract["offered_tools"]
|
assert "web_search" not in request_contract["offered_tools"]
|
||||||
assert request_contract["tool_choice"] == "required"
|
assert request_contract["tool_choice"] == {
|
||||||
|
"type": "function", "function": {"name": "python"},
|
||||||
|
}
|
||||||
reserved_names = [tool["function"]["name"] for tool in requests[12]["tools"]]
|
reserved_names = [tool["function"]["name"] for tool in requests[12]["tools"]]
|
||||||
assert reserved_names == ["bash", "python", "write_file"]
|
assert reserved_names == ["python"]
|
||||||
assert requests[12]["tool_choice"] == "required"
|
assert requests[12]["tool_choice"] == {
|
||||||
directory_path = requests[12]["tools"][2]["function"]["parameters"]["properties"]["path"]
|
"type": "function", "function": {"name": "python"},
|
||||||
assert re.search(directory_path["pattern"], "/tmp_workspace/results/out.md")
|
}
|
||||||
assert not re.search(directory_path["pattern"], "/tmp_workspace/results")
|
|
||||||
assert "create one or more files inside" in requests[12]["messages"][-1]["content"]
|
assert "create one or more files inside" in requests[12]["messages"][-1]["content"]
|
||||||
assert "Do not pass the directory itself as a file path" in requests[12]["messages"][-1]["content"]
|
assert "Do not pass the directory itself as a file path" in requests[12]["messages"][-1]["content"]
|
||||||
assert "bash" in executed
|
assert executed[-1] == "python"
|
||||||
assert any(
|
assert any(
|
||||||
event.get("type") == "tool_output" and event.get("tool") == "write_file"
|
event.get("type") == "tool_output" and event.get("tool") == "python"
|
||||||
and not event.get("error") for event in events
|
and not event.get("error") for event in events
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ ROOT = Path(__file__).resolve().parents[1]
|
|||||||
def test_original_harness_version_is_canonical_semver() -> None:
|
def test_original_harness_version_is_canonical_semver() -> None:
|
||||||
version = (ROOT / "HARNESS_VERSION").read_text(encoding="utf-8").strip()
|
version = (ROOT / "HARNESS_VERSION").read_text(encoding="utf-8").strip()
|
||||||
|
|
||||||
assert version == "0.20.13"
|
assert version == "0.20.14"
|
||||||
assert re.fullmatch(r"\d+\.\d+\.\d+", version)
|
assert re.fullmatch(r"\d+\.\d+\.\d+", version)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user