from types import SimpleNamespace import json import jsonschema import pytest from src.clean_agent_preview import conversation, readonly_call, preview_call_allowed, evaluate_preview_call, authorized_write_families, compact_schemas, normalize_preview_function_args, private_browser_dom_batch, stream_preview, denied_response, requests_mutation, claims_completion, recent_successful_write_families, scope_preview_contract, multimodal_image_count, attachment_reference_count, active_document_context_message, active_email_context_message, targets_active_editor, active_editor_whole_draft_request, active_editor_suggestion_request, scope_active_editor_contract, native_execution_limits, runtime_required_artifacts, document_suggestions_event, required_read_tool_choice, sealed_read_arguments def test_compact_notes_preserves_create_vs_edit_and_replacement_semantics(): schema = next(s for s in FUNCTION_TOOL_SCHEMAS if s['function']['name'] == 'manage_notes') compact = compact_schemas([schema])[0]['function'] assert 'add creates a new note' in compact['description'] assert 'update with id' in compact['description'] assert 'replaces the whole checklist' in compact['parameters']['properties']['checklist_items']['description'] def test_compact_notes_exposes_optional_explicit_checked_state(): schema = next(s for s in FUNCTION_TOOL_SCHEMAS if s['function']['name'] == 'manage_notes') for candidate in (schema, compact_schemas([schema])[0]): parameters = candidate['function']['parameters'] assert parameters['properties']['done']['type'] == 'boolean' assert 'omit to toggle' in parameters['properties']['done']['description'] assert 'done' not in parameters['required'] def test_compact_skills_distinguishes_field_edits_from_raw_text_patches(): schema = next(s for s in FUNCTION_TOOL_SCHEMAS if s['function']['name'] == 'manage_skills') compact = compact_schemas([schema])[0]['function'] props = compact['parameters']['properties'] assert props['procedure']['type'] == 'array' assert 'add/edit' in props['procedure']['description'] assert 'not a flag' in props['procedure']['description'] assert 'exactly once' in props['old_string']['description'] assert 'full SKILL.md' in props['old_string']['description'] def test_compact_skills_preserves_reference_read_contract(): schema = next(s for s in FUNCTION_TOOL_SCHEMAS if s['function']['name'] == 'manage_skills') params = compact_schemas([schema])[0]['function']['parameters'] props = params['properties'] assert 'view = SKILL.md' in props['action']['description'] assert 'view_ref = supporting file' in props['action']['description'] assert 'not a file path' in props['name']['description'] assert 'view_ref only' in props['path']['description'] assert params['required'] == ['action'] # listing still needs no name/path def test_interactive_ocr_uses_upload_references_not_arbitrary_workspace_reads(): owned_ref = evaluate_preview_call('extract_text', {'path': 'odysseus://attachment/fixture-upload'}, 'OCR this image') assert owned_ref.allowed assert owned_ref.effects == ('read_private',) assert not evaluate_preview_call('extract_text', {'path': '/etc/passwd'}, 'OCR this image').allowed assert not evaluate_preview_call('extract_text', {'path': '/workspace/image.png'}, 'OCR this image').allowed from src.tool_schemas import FUNCTION_TOOL_SCHEMAS, function_call_to_tool_block from src.tool_policy import ToolPolicy from src.turn_contract import resolve_full_inventory_contract def test_native_execution_limits_allow_multi_artifact_work_without_unbounded_rounds(): assert native_execution_limits(64) == (64, 32) assert native_execution_limits(1000) == (64, 32) assert native_execution_limits("invalid") == (8, 32) @pytest.mark.parametrize('prompt,expected', [ ('Read /workspace/fixtures/paper.pdf and create /workspace/results.csv and /workspace/chart.png', ('/workspace/results.csv', '/workspace/chart.png')), ('Create /workspace/results.csv using /workspace/source.csv', ('/workspace/results.csv',)), ('Create /workspace/results.csv. Read /workspace/source.csv and /workspace/other.csv', ('/workspace/results.csv',)), ('Read /workspace/source.csv and /workspace/other.csv', ()), ('Create /workspace/results.v2.csv and /workspace/chart.v2.png', ('/workspace/results.v2.csv', '/workspace/chart.v2.png')), ]) def test_all_requested_outputs_survive_filename_periods(prompt, expected): from src.clean_agent_preview import declared_workspace_artifacts assert declared_workspace_artifacts(prompt) == expected def test_notes_terminal_response_preserves_links_from_wrapped_executor_output(): from src.clean_agent_preview import notes_terminal_response raw = json.dumps({ 'results': '- [note-123] **Fixture note**', 'exit_code': 0, }) assert notes_terminal_response(raw) == ( 'Here are your notes (1):\n📝 [Fixture note](#note-note-123)' ) def test_successful_document_suggestion_has_one_browser_owned_event(): suggestions = [{'find': 'wordy', 'replace': 'concise', 'reason': 'clarity'}] assert document_suggestions_event({'doc_id': 'doc-1', 'suggestions': suggestions}) == { 'type': 'doc_suggestions', 'doc_id': 'doc-1', 'suggestions': suggestions, } assert document_suggestions_event( {'doc_id': 'doc-1', 'suggestions': suggestions}, failed=True, ) is None assert document_suggestions_event({'error': 'no match'}, failed=True) is None def test_runtime_required_artifacts_includes_runner_declared_directory(): assert runtime_required_artifacts( 'Create the requested output.', {'completion_requirements': {'required_artifacts': ['/tmp_workspace/results/']}}, ) == ('/tmp_workspace/results',) def test_prompt_input_paths_are_not_misclassified_as_required_artifacts(): assert runtime_required_artifacts( 'Use read_file to read /workspace/sample.txt. Read only.', {} ) == () assert runtime_required_artifacts( 'Use OCR to extract text from /workspace/receipt.png. Read only.', {} ) == () def test_prompt_output_path_is_tracked_without_its_source_path(): assert runtime_required_artifacts( 'Create an HTML report at /workspace/output.html from /workspace/input.csv.', {}, ) == ('/workspace/output.html',) def test_compact_writer_advertises_parallel_independent_file_calls(): writer = next( schema for schema in compact_schemas(FUNCTION_TOOL_SCHEMAS) if schema['function']['name'] == 'write_file' ) assert 'multiple write_file calls in the same response' in writer['function']['description'] def test_read_only_gate_blocks_mutation_and_network_shell(): assert readonly_call('manage_notes', {'action': 'list'}) assert readonly_call('manage_notes', {'action': 'view', 'id': 'abc'}) assert not readonly_call('manage_notes', {'action': 'delete', 'id': 'abc'}) assert not readonly_call('bash', {'command': 'curl https://example.com'}) assert not readonly_call('mcp__email__send_email', {}) def test_preview_allows_safe_personal_writes_only(): assert preview_call_allowed('manage_notes', {'action': 'add', 'title': 'x'}, 'add a note') assert preview_call_allowed('manage_tasks', {'action': 'create', 'task': 'x'}, 'add a task') assert preview_call_allowed('manage_calendar', {'action': 'create_event', 'summary': 'x'}, 'add a calendar event') assert preview_call_allowed('manage_memory', {'action': 'edit', 'id': 'x'}, 'edit my memory') assert preview_call_allowed('create_document', {'title': 'x'}, 'make a document') assert preview_call_allowed('manage_notes', {'action': 'delete', 'id': 'x'}, 'delete a note') assert not preview_call_allowed('manage_tasks', {'action': 'run', 'id': 'x'}, 'run task') assert not preview_call_allowed('send_email', {'to': 'x@example.com'}, 'send email') assert not preview_call_allowed('bash', {'command': 'true'}, 'run shell') @pytest.mark.parametrize("tool,args", [ ('manage_research', {'action': 'list'}), ('manage_research', {'action': 'read', 'id': 'report-1'}), ('list_sessions', {}), ('manage_contact', {'action': 'list'}), ('manage_contact', {'action': 'search', 'query': 'Alex'}), ]) def test_supplemental_private_inventory_reads_are_preview_safe(tool, args): decision = evaluate_preview_call(tool, args, 'list my saved data') assert decision.allowed and decision.reason == 'allowed' @pytest.mark.parametrize("tool,args", [ ('manage_research', {'action': 'delete', 'id': 'report-1'}), ('manage_contact', {'action': 'add', 'name': 'Alex', 'email': 'a@example.com'}), ]) def test_supplemental_private_inventory_mutations_remain_blocked(tool, args): decision = evaluate_preview_call(tool, args, 'list my saved data') assert not decision.allowed @pytest.mark.parametrize('sentinel', ['', 'all', 'all sessions', 'all_sessions', 'no_filter', '*']) def test_preview_unfiltered_session_sentinels_do_not_become_literal_title_filters(sentinel): tool, args = normalize_preview_function_args('list_sessions', {'filter': sentinel}) assert tool == 'list_sessions' assert args == {} def test_preview_preserves_real_session_title_filter(): tool, args = normalize_preview_function_args('list_sessions', {'filter': 'audit'}) assert tool == 'list_sessions' assert args == {'filter': 'audit'} def test_server_sealed_read_forces_exact_first_tool_then_releases_choice(): from src.turn_contract import RequiredReadOperation, resolve_turn_contract contract = resolve_turn_contract( capabilities={'contacts'}, schemas=FUNCTION_TOOL_SCHEMAS, policy=ToolPolicy(), required_read_operation=RequiredReadOperation( 'manage_contact', {'action': 'list'}, max_items=3, ), ) offered = compact_schemas(contract.schemas()) assert required_read_tool_choice(contract, offered) == { 'type': 'function', 'function': {'name': 'manage_contact'}, } assert required_read_tool_choice(contract, offered, calls=1) is None assert sealed_read_arguments( contract, 'manage_contact', {'action': 'delete', 'id': 'wrong'} ) == {'action': 'list'} assert sealed_read_arguments( contract, 'manage_contact', {'action': 'delete'}, calls=1 ) == {'action': 'delete'} def test_preview_allows_only_safe_panel_open_ui_control(): assert preview_call_allowed( 'ui_control', {'action': 'open_panel', 'name': 'gallery'}, 'open gallery' ) assert preview_call_allowed( 'ui_control', {'action': 'open_panel', 'name': 'settings'}, 'open settings' ) assert not preview_call_allowed( 'ui_control', {'action': 'switch_model', 'name': 'other'}, 'switch models' ) assert not preview_call_allowed( 'ui_control', {'action': 'toggle', 'name': 'web', 'value': 'off'}, 'turn off web' ) def test_bash_requires_explicit_turn_enablement(): args = {'command': "printf '%s\\n' ODY_SHELL_FILES_READONLY; cat /etc/hostname"} assert not preview_call_allowed('bash', args, 'run this command') assert preview_call_allowed('bash', args, 'run this command', allow_execute_code=True) def test_native_workspace_tools_require_validated_native_scope(): samples = { 'inspect_media': {'path': '/workspace/fixture.webm'}, 'extract_text': {'path': '/workspace/fixture.png'}, 'read_file': {'path': '/workspace/input.txt'}, 'ls': {'path': '/workspace'}, 'write_file': {'path': '/workspace/output.html', 'content': ''}, 'python': {'code': '2 + 2'}, } for name, args in samples.items(): assert not preview_call_allowed( name, args, 'work in the supplied workspace', allow_execute_code=True, ) assert preview_call_allowed( name, args, 'work in the supplied workspace', allow_execute_code=True, allow_native_workspace=True, ) def test_compact_native_file_schemas_preserve_argument_semantics(): schemas = { schema['function']['name']: schema['function'] for schema in compact_schemas(FUNCTION_TOOL_SCHEMAS) } read_properties = schemas['read_file']['parameters']['properties'] write_properties = schemas['write_file']['parameters']['properties'] assert 'line 4 means offset=4' in read_properties['offset']['description'] assert 'final newline' in write_properties['content']['description'] assert '/workspace refers to its root' in schemas['python']['description'] def test_explicit_final_newline_is_preserved_for_exact_file_content(): tool, args = normalize_preview_function_args( 'write_file', {'path': '/workspace/result.txt', 'content': 'DONE'}, user_text=( 'Write /workspace/result.txt containing exactly DONE followed by a newline.' ), ) assert tool == 'write_file' assert args['content'] == 'DONE\n' def test_file_content_is_not_changed_without_explicit_newline_request(): _, args = normalize_preview_function_args( 'write_file', {'path': '/workspace/result.txt', 'content': 'DONE'}, user_text='Write /workspace/result.txt containing DONE.', ) assert args['content'] == 'DONE' def test_unrequested_invalid_transcript_precision_is_dropped(): _, args = normalize_preview_function_args( 'transcribe_media', {'path': '/workspace/audio.wav', 'timestamp_precision': 100}, user_text='Return timestamped segments.', ) assert args == {'path': '/workspace/audio.wav'} def test_explicit_transcript_precision_remains_strictly_validated(): _, args = normalize_preview_function_args( 'transcribe_media', {'path': '/workspace/audio.wav', 'timestamp_precision': 100}, user_text='Use timestamp precision of 2 decimal places.', ) assert args['timestamp_precision'] == 100 def test_visual_tool_result_is_uniformly_bounded_to_endpoint_limit(): from src.clean_agent_preview import bounded_visual_result_blocks blocks = bounded_visual_result_blocks({ 'images': [ {'mimeType': 'image/jpeg', 'data': str(index)} for index in range(5) ], }, max_images=3) assert [block['image_url']['url'] for block in blocks] == [ 'data:image/jpeg;base64,0', 'data:image/jpeg;base64,2', 'data:image/jpeg;base64,4', ] def test_brokered_web_fetch_and_deliberately_offered_private_browser_are_preview_safe_reads(): assert preview_call_allowed( 'web_fetch', {'url': 'https://www.reuters.com/example'}, 'tell me more' ) assert preview_call_allowed( 'private_browser', {'action': 'open', 'url': 'https://example.com'}, 'open this' ) def test_preview_policy_decisions_have_stable_sanitized_reasons(): allowed = evaluate_preview_call('web_fetch', {'url': 'https://example.com'}, 'tell me more') assert allowed.allowed and allowed.reason == 'allowed' assert allowed.audit() == { 'allowed': True, 'reason': 'allowed', 'tool': 'web_fetch', 'family': 'search_browser', 'effects': ['brokered_network_read', 'network_egress'], } denied = evaluate_preview_call('manage_notes', {'action': 'delete', 'id': 'x'}, 'delete it') assert not denied.allowed and denied.reason == 'write_family_not_authorized' assert 'delete it' not in json.dumps(denied.audit()) def test_explicit_calendar_event_delete_is_allowed_without_weakening_other_deletes(): allowed = evaluate_preview_call( 'manage_calendar', {'action': 'delete_event', 'event_id': 'event-1'}, 'remove the happy horizon event', ) assert allowed.allowed and allowed.reason == 'allowed' assert not preview_call_allowed( 'manage_calendar', {'action': 'delete_event', 'event_id': 'event-1'}, 'do it', ) assert preview_call_allowed( 'manage_notes', {'action': 'delete', 'id': 'note-1'}, 'delete the note' ) def test_explicit_followup_mutation_uses_the_immutable_turn_family(): args = {'action': 'delete_event', 'event_id': 'event-1'} denied = evaluate_preview_call( 'manage_calendar', args, 'delete the amazon delivery', ) assert not denied.allowed and denied.reason == 'write_family_not_authorized' allowed = evaluate_preview_call( 'manage_calendar', args, 'delete the amazon delivery', turn_authorized_families={'calendar'}, ) assert allowed.allowed and allowed.reason == 'allowed' def test_explicit_followup_forget_uses_the_immutable_memory_family(): allowed = evaluate_preview_call( 'manage_memory', {'action': 'delete', 'memory_id': 'memory-1'}, 'Forget that memory.', turn_authorized_families={'memory'}, ) assert allowed.allowed and allowed.reason == 'allowed' def test_skill_update_alias_normalizes_to_edit_before_policy(): tool, args = normalize_preview_function_args( 'manage_skills', {'action': 'update', 'name': 'example-skill', 'description': 'new'}, ) assert tool == 'manage_skills' assert args['action'] == 'edit' def test_private_browser_open_normalizes_to_atomic_snapshot_batch(): tool, args = normalize_preview_function_args( 'private_browser', {'action': 'open', 'url': 'https://example.com', 'timeout_ms': 12000}, ) assert tool == 'private_browser' assert args == { 'action': 'batch', 'commands': [['open', 'https://example.com'], ['snapshot']], 'timeout_ms': 12000, } def test_private_browser_local_artifact_open_is_not_rewritten(): tool, args = normalize_preview_function_args( 'private_browser', {'action': 'open', 'url': 'file:///workspace/output.html'}, ) assert tool == 'private_browser' assert args == {'action': 'open', 'url': 'file:///workspace/output.html'} def test_private_browser_dom_batch_only_matches_automatic_open_snapshot(): assert private_browser_dom_batch({ 'action': 'batch', 'commands': [['open', 'https://example.com'], ['snapshot']], }) assert not private_browser_dom_batch({ 'action': 'batch', 'commands': [['open', 'https://example.com'], ['snapshot'], ['screenshot']], }) def test_every_compactly_offered_preview_tool_has_valid_policy_permitted_call(): samples = { 'extract_text': ({'path': 'odysseus://attachment/fixture.png'}, 'OCR this image'), 'bash': ({'command': 'pwd'}, 'run this shell command'), 'create_document': ({'title': 'x', 'content': 'y'}, 'create a document'), 'edit_document': ({'edits': [{'find': 'x', 'replace': 'y'}]}, 'edit my document'), 'list_cached_models': ({}, 'list cached models'), 'list_cookbook_servers': ({}, 'list cookbook servers'), 'list_downloads': ({}, 'list downloads'), 'list_email_accounts': ({}, 'list my email accounts'), 'list_emails': ({'limit': 3}, 'list my emails'), 'list_models': ({}, 'list models'), 'list_serve_presets': ({}, 'list serve presets'), 'list_served_models': ({}, 'list served models'), 'manage_calendar': ({'action': 'list_events'}, 'list my calendar events'), 'manage_contact': ({'action': 'list'}, 'list my contacts'), 'manage_documents': ({'action': 'list'}, 'list my documents'), 'manage_memory': ({'action': 'list'}, 'list my memories'), 'manage_notes': ({'action': 'list'}, 'list my notes'), 'manage_research': ({'action': 'list'}, 'list my saved research reports'), 'manage_skills': ({'action': 'list'}, 'list my skills'), 'manage_tasks': ({'action': 'list'}, 'list my tasks'), 'list_sessions': ({}, 'list my chat sessions'), 'pdf_extract': ({'url': 'https://example.com/x.pdf', 'query': 'metric'}, 'read this pdf'), 'private_browser': ({'action': 'batch', 'commands': [['open', 'https://example.com'], ['snapshot']]}, 'use the private browser'), 'read_email': ({'uid': '1'}, 'read my email'), 'search_chats': ({'query': 'project'}, 'search my chats'), 'search_emails': ({'query': 'project'}, 'search my emails'), 'search_hf_models': ({'query': 'Qwen'}, 'search Hugging Face models'), 'suggest_document': ({'suggestions': [{'find': 'x', 'replace': 'y', 'reason': 'clarity'}]}, 'suggest edits to my document'), 'update_document': ({'content': 'updated'}, 'update my document'), 'ui_control': ({'action': 'open_panel', 'name': 'gallery'}, 'open gallery'), 'trigger_research': ({'topic': 'AI info'}, 'research AI info'), 'web_fetch': ({'url': 'https://example.com'}, 'read this page'), 'web_search': ({'query': 'current AI news'}, 'search the web'), 'youtube_tool': ({'action': 'metadata', 'video_url': 'https://youtube.com/watch?v=x'}, 'read YouTube metadata'), } schemas = { schema['function']['name']: schema for schema in compact_schemas(FUNCTION_TOOL_SCHEMAS) if schema['function']['name'] in samples } assert set(samples) == set(schemas) from src.clean_agent_preview import PREVIEW_TOOLS assert set(samples) == set(PREVIEW_TOOLS) for name, (args, prompt) in samples.items(): jsonschema.validate(args, schemas[name]['function']['parameters']) decision = evaluate_preview_call( name, args, prompt, allow_execute_code=(name == 'bash'), turn_authorized_families={'research'} if name == 'trigger_research' else frozenset(), ) assert decision.allowed, f'{name}: {decision.reason}' def test_pdf_extract_accepts_task_local_path_and_normalizes_it_for_execution(): schema = next( item for item in FUNCTION_TOOL_SCHEMAS if item['function']['name'] == 'pdf_extract' ) arguments = { 'path': '/workspace/fixtures/paper.pdf', 'query': 'references arXiv preprints', } jsonschema.validate(arguments, schema['function']['parameters']) block = function_call_to_tool_block('pdf_extract', json.dumps(arguments)) assert block is not None assert json.loads(block.content) == { 'url': '/workspace/fixtures/paper.pdf', 'query': 'references arXiv preprints', } def test_pdf_extract_rejects_a_guessed_filename_with_actionable_discovery(): from src.tool_schemas import normalized_native_function_argument_error error = normalized_native_function_argument_error( 'pdf_extract', {'url': 'example-paper.pdf', 'query': 'Table 2'} ) assert 'public http(s) PDF URL' in error assert 'use web_search' in error assert normalized_native_function_argument_error( 'pdf_extract', {'url': 'https://example.com/paper.pdf', 'query': 'Table 2'}, ) is None assert normalized_native_function_argument_error( 'pdf_extract', {'url': '/workspace/fixtures/paper.pdf', 'query': 'Table 2'}, ) is None def test_write_authority_prevents_cross_family_substitution(): assert authorized_write_families('send an emil') == {'email'} assert not preview_call_allowed('manage_tasks', {'action': 'create', 'name': 'send mail'}, 'send an email') assert preview_call_allowed('manage_notes', {'action': 'add', 'title': 'Sweden'}, 'add todo go to Sweden tomorrow') assert not preview_call_allowed('manage_notes', {'action': 'add', 'title': 'x'}, 'do it') def test_immediate_successful_write_allows_same_family_revision_only(): turn = [ {'role': 'assistant', 'tool_calls': [{'id': 'c1', 'function': { 'name': 'manage_calendar', 'arguments': '{"action":"create_event","summary":"Meeting with Elon","dtstart":"2026-09-09T10:00:00Z"}', }}]}, {'role': 'tool', 'tool_call_id': 'c1', 'content': '{"uid":"event-1","exit_code":0}'}, {'role': 'assistant', 'content': 'Meeting added.'}, ] session = SimpleNamespace(history=[ {'role': 'user', 'content': 'add meeting elon at 10am'}, {'role': 'assistant', 'content': 'Meeting added.', 'metadata': {'clean_v3_turn': turn}}, ]) inherited = recent_successful_write_families(session) assert inherited == {'calendar'} assert preview_call_allowed( 'manage_calendar', {'action': 'update_event', 'event_id': 'event-1'}, 'no tomorrow 10am and alon', contextual_write_families=inherited, ) assert not preview_call_allowed( 'manage_calendar', {'action': 'create_event', 'summary': 'another'}, 'do it again', contextual_write_families=inherited, ) assert not preview_call_allowed( 'manage_notes', {'action': 'update', 'id': 'note-1'}, 'change it', contextual_write_families=inherited, ) def test_active_editor_authorizes_revision_but_not_replacement_creation(): context = frozenset({'documents'}) assert preview_call_allowed( 'update_document', {'content': 'Hello'}, 'write the email', contextual_write_families=context, ) assert not preview_call_allowed( 'create_document', {'title': 'Replacement', 'content': 'Hello'}, 'write the email', contextual_write_families=context, ) def test_open_email_reply_is_scoped_to_one_whole_draft_writer(): document = SimpleNamespace( title='New Email', language='email', current_content='To: a@example.com\nSubject: Hello\n---\nOriginal body', ) assert active_editor_whole_draft_request(document, 'Write reply this email') contract = resolve_full_inventory_contract( schemas=FUNCTION_TOOL_SCHEMAS, policy=ToolPolicy(), ) scoped = scope_active_editor_contract(contract, whole_draft=True) offered = {name.removeprefix('mcp__email__') for name in scoped.offered} assert 'update_document' in offered assert not {'create_document', 'manage_documents', 'edit_document', 'suggest_document'} & offered def test_active_review_is_scoped_to_suggestions_without_applying_changes(): document = SimpleNamespace( title='Draft', language='markdown', current_content='A wordy sentence.', ) prompt = 'Add another inline suggestion. Do not apply either suggestion.' assert active_editor_suggestion_request(document, prompt) contract = resolve_full_inventory_contract( schemas=FUNCTION_TOOL_SCHEMAS, policy=ToolPolicy(), ) scoped = scope_active_editor_contract(contract, suggestion_only=True) assert {name.removeprefix('mcp__email__') for name in scoped.offered} == {'suggest_document'} def test_failed_write_does_not_authorize_contextual_revision(): session = SimpleNamespace(history=[{'role': 'assistant', 'content': 'Failed.', 'metadata': { 'clean_v3_turn': [ {'role': 'assistant', 'tool_calls': [{'id': 'c1', 'function': { 'name': 'manage_calendar', 'arguments': '{"action":"create_event"}', }}]}, {'role': 'tool', 'tool_call_id': 'c1', 'content': '{"error":"blocked","exit_code":1}'}, ], }}]) assert not recent_successful_write_families(session) def test_denial_never_claims_action_succeeded(): text = denied_response().casefold() assert 'no changes were made' in text assert 'deleted' not in text and 'created' not in text and 'sent' not in text @pytest.mark.parametrize('text', [ 'Delete all my notes.', 'add a calendar event tomorrow', 'remember that I like tea', 'create a document called Atlas', 'send an email to Alex', 'write the email for me', ]) def test_mutation_request_detection_is_family_aware(text): assert requests_mutation(text) @pytest.mark.parametrize('text', [ 'Show my notes.', 'What is on my calendar?', 'Search the web for Sweden.', 'Explain tasks.', 'List my notes. Read-only; do not change data or send messages.', 'List my scheduled tasks. Do not change data or send messages.', "Show my notes without changing or deleting anything.", ]) def test_reads_and_general_questions_are_not_mutations(text): assert not requests_mutation(text) def test_definition_note_and_sports_set_are_not_a_notes_mutation(): text = ( 'How many set points did the player fail to convert? ' 'Note: a set point is one point away from winning the set.' ) assert authorized_write_families(text) == frozenset() assert not requests_mutation(text) def test_plain_note_request_still_authorizes_note_mutation(): assert authorized_write_families('Set my note title to Travel') == {'notes'} assert requests_mutation('Set my note title to Travel') def test_negated_mutation_does_not_hide_later_positive_instruction(): assert requests_mutation("Don't delete my note; update its title instead.") def test_completion_claim_distinguishes_success_from_denial_or_question(): assert claims_completion('All notes have been deleted.') assert claims_completion("Done — I've added the note.") assert not claims_completion("I can't delete those. No changes were made.") assert not claims_completion('What title should be added?') def test_history_preserves_native_call_result_group_and_current_user(): saved = [{'role': 'assistant', 'tool_calls': [{'id': 'c1', 'function': {'name': 'manage_notes', 'arguments': '{"action":"list"}'}}]}, {'role': 'tool', 'tool_call_id': 'c1', 'content': 'notes'}, {'role': 'assistant', 'content': 'Here are notes.'}] session = SimpleNamespace(history=[{'role': 'user', 'content': 'notes'}, {'role': 'assistant', 'content': 'Here are notes.', 'metadata': {'clean_v3_turn': saved}}]) result = conversation(session, [{'role': 'user', 'content': 'second one?'}]) assert result[1:4] == saved assert result[-1] == {'role': 'user', 'content': 'second one?'} def test_history_drops_large_older_turn_without_losing_recent_note_evidence(): saved = [ {'role': 'assistant', 'tool_calls': [{'id': 'notes-call', 'function': { 'name': 'manage_notes', 'arguments': '{"action":"list"}'}}]}, {'role': 'tool', 'tool_call_id': 'notes-call', 'content': '[fixture-id] Today'}, {'role': 'assistant', 'content': 'Today'}, ] session = SimpleNamespace(history=[ {'role': 'user', 'content': 'Calendar'}, {'role': 'assistant', 'content': 'x' * 23000}, {'role': 'user', 'content': 'Notes'}, {'role': 'assistant', 'content': 'Today', 'metadata': {'clean_v3_turn': saved}}, ]) result = conversation(session, [{'role': 'user', 'content': 'Delete that note'}]) assert result[0]['content'] == 'Notes' assert result[1:4] == saved def test_history_character_limit_drops_whole_oversized_reference_turn(): # Character-budget limitation: availability of the tool alone does not # guarantee that an oversized prior result remains in the model context. saved = [ {'role': 'assistant', 'tool_calls': [{'id': 'large-call', 'function': { 'name': 'manage_notes', 'arguments': '{"action":"list"}'}}]}, {'role': 'tool', 'tool_call_id': 'large-call', 'content': 'x' * 23000}, ] session = SimpleNamespace(history=[ {'role': 'user', 'content': 'Notes'}, {'role': 'assistant', 'content': 'Notes', 'metadata': {'clean_v3_turn': saved}}, ]) result = conversation(session, [{'role': 'user', 'content': 'Read the second one'}]) assert result == [{'role': 'user', 'content': 'Read the second one'}] def test_history_rehydrates_most_recent_owner_checked_image_for_followup(monkeypatch, tmp_path): image_path = tmp_path / 'fixture.png' image_path.write_bytes(b'PNG-test-bytes') class Uploads: def resolve_upload(self, upload_id, owner=None, allow_admin=True): assert upload_id == 'a' * 32 + '.png' assert owner == 'alice' assert allow_admin is False return {'path': str(image_path), 'mime': 'image/png', 'name': 'fixture.png'} def is_image_file(self, name, mime): return mime == 'image/png' monkeypatch.setattr('src.tool_utils.get_upload_handler', lambda: Uploads()) upload_id = 'a' * 32 + '.png' session = SimpleNamespace(history=[ {'role': 'user', 'content': 'What is shown?', 'metadata': { 'attachments': [{'id': upload_id, 'name': 'fixture.png', 'mime': 'image/png'}], }}, {'role': 'assistant', 'content': 'A test image.', 'metadata': { 'clean_v3_turn': [{'role': 'assistant', 'content': 'A test image.'}], }}, ]) result = conversation(session, [{'role': 'user', 'content': 'What color was it?'}], owner='alice') image_turn = result[0] assert image_turn['role'] == 'user' assert image_turn['content'][0] == {'type': 'text', 'text': 'What is shown?'} assert image_turn['content'][1]['image_url']['url'].startswith('data:image/png;base64,') assert any(block.get('type') == 'text' and f'odysseus://attachment/{upload_id}' in block.get('text', '') for block in image_turn['content']) assert result[-1] == {'role': 'user', 'content': 'What color was it?'} def test_history_trims_after_removing_raw_image_bytes(monkeypatch, tmp_path): image_path = tmp_path / 'fixture.png' image_path.write_bytes(b'PNG-test-bytes') class Uploads: def resolve_upload(self, upload_id, owner=None, allow_admin=True): return {'path': str(image_path), 'mime': 'image/png', 'name': 'fixture.png'} def is_image_file(self, name, mime): return mime == 'image/png' monkeypatch.setattr('src.tool_utils.get_upload_handler', lambda: Uploads()) upload_id = 'b' * 32 + '.png' session = SimpleNamespace(history=[ {'role': 'user', 'content': [ {'type': 'text', 'text': 'Inspect this dashboard.'}, {'type': 'image_url', 'image_url': {'url': 'data:image/png;base64,' + 'x' * 30000}}, ], 'metadata': {'attachments': [{'id': upload_id}]}}, {'role': 'assistant', 'content': 'Q3 is highest.'}, {'role': 'user', 'content': 'Save that in a note.', 'metadata': {}}, ]) diagnostics = {} result = conversation( session, [{'role': 'user', 'content': 'Save that in a note.'}], owner='alice', diagnostics=diagnostics, ) assert result[0]['content'][1]['type'] == 'image_url' assert diagnostics['image_rehydration'] == 'rehydrated' def test_history_never_rehydrates_image_without_an_authenticated_owner(monkeypatch): monkeypatch.setattr('src.tool_utils.get_upload_handler', lambda: (_ for _ in ()).throw(AssertionError('must not resolve'))) session = SimpleNamespace(history=[ {'role': 'user', 'content': 'Image marker', 'metadata': {'attachments': [{'id': 'a' * 32 + '.png'}]}}, {'role': 'assistant', 'content': 'Seen.'}, ]) assert conversation(session, [{'role': 'user', 'content': 'Again?'}])[0]['content'] == 'Image marker' def test_multimodal_image_count_never_exposes_payloads(): messages = [ {'role': 'user', 'content': [ {'type': 'text', 'text': 'look'}, {'type': 'image_url', 'image_url': {'url': 'data:image/png;base64,secret'}}, ]}, {'role': 'assistant', 'content': 'seen'}, ] assert multimodal_image_count(messages) == 1 def test_attachment_reference_count_uses_metadata_only(): session = SimpleNamespace(history=[ SimpleNamespace(role='user', content='one', metadata={'attachments': [{'id': 'a'}, {'id': 'b'}]}), SimpleNamespace(role='assistant', content='seen', metadata={}), ]) assert attachment_reference_count(session) == 2 def test_plain_system_wrappers_are_not_conversation_messages(): assert conversation(None, [{'role': 'system', 'content': 'legacy wrapper'}, {'role': 'user', 'content': 'hi'}]) == [{'role': 'user', 'content': 'hi'}] def test_open_empty_email_draft_is_still_visible_context(): message = active_document_context_message(SimpleNamespace( id='draft-1', title='Reply to Jordan', language='email', current_content='', )) assert message['role'] == 'user' assert message['metadata']['trusted'] is False assert 'Open editor kind: email draft' in message['content'] assert 'Title: Reply to Jordan' in message['content'] assert 'Content (currently empty)' in message['content'] def test_open_document_context_includes_current_text(): message = active_document_context_message(SimpleNamespace( id='doc-1', title='Trip', language='markdown', current_content='Visit Uppsala.', )) assert 'Open editor kind: document' in message['content'] assert 'Visit Uppsala.' in message['content'] def test_open_email_reader_is_visible_as_typed_untrusted_context(): message = active_email_context_message({ 'uid': 'fixture-uid', 'folder': 'INBOX', 'account': 'fixture-account', 'subject': 'Status', 'from': 'Jordan', 'body_preview': 'Can we meet tomorrow?', }) assert message['role'] == 'user' assert message['metadata']['trusted'] is False assert 'Open email reader' in message['content'] assert 'Subject: Status' in message['content'] assert 'Can we meet tomorrow?' in message['content'] def test_active_editor_targeting_distinguishes_edit_from_new_document(): draft = SimpleNamespace(title='Reply', language='email', current_content='') assert targets_active_editor(draft, 'Write reply') assert targets_active_editor(draft, 'Draft a reply') assert targets_active_editor(draft, "Write a friendly email saying I'll reply tomorrow") assert targets_active_editor(draft, 'Make it friendlier') assert not targets_active_editor(draft, 'Write a note') assert not targets_active_editor(draft, 'Write a JavaScript function') assert not targets_active_editor(draft, 'Create a new separate document about Sweden') def test_active_editor_contract_excludes_other_tool_families(): schemas = [s for s in FUNCTION_TOOL_SCHEMAS if s['function']['name'] in { 'create_document', 'update_document', 'manage_documents', 'manage_notes', 'ui_control', }] contract = resolve_full_inventory_contract(schemas=schemas, policy=ToolPolicy()) scoped = scope_active_editor_contract(contract) assert 'create_document' not in scoped.offered assert 'ui_control' not in scoped.offered assert 'manage_documents' not in scoped.offered assert scoped.offered == {'update_document'} assert {'create_document', 'ui_control', 'manage_documents'} <= scoped.executable def test_empty_active_editor_contract_offers_only_whole_document_update_from_document_family(): schemas = [s for s in FUNCTION_TOOL_SCHEMAS if s['function']['name'] in { 'create_document', 'edit_document', 'suggest_document', 'update_document', 'manage_documents', 'manage_notes', 'ui_control', }] contract = resolve_full_inventory_contract(schemas=schemas, policy=ToolPolicy()) scoped = scope_active_editor_contract(contract, empty=True) document_tools = { name for name in scoped.offered if name in {'create_document', 'edit_document', 'suggest_document', 'update_document', 'manage_documents'} } assert document_tools == {'update_document'} assert 'manage_notes' not in scoped.offered def test_v3_schema_preserves_names_and_action_enum(): notes = next(s for s in FUNCTION_TOOL_SCHEMAS if s['function']['name'] == 'manage_notes') compact = compact_schemas([notes])[0] assert compact['function']['name'] == 'manage_notes' assert compact['function']['parameters']['properties']['action']['enum'] == notes['function']['parameters']['properties']['action']['enum'] assert compact['function']['description'] @pytest.mark.parametrize('name', ['read_email', 'mcp__email__read_email']) def test_v3_live_email_schema_preserves_identifier_semantics(name): import asyncio import copy from mcp_servers import email_server live = next(tool for tool in asyncio.run(email_server.list_tools()) if tool.name == 'read_email') schema = {'type': 'function', 'function': { 'name': name, 'description': live.description, 'parameters': copy.deepcopy(live.inputSchema), }} original = copy.deepcopy(schema) function = compact_schemas([schema])[0]['function'] properties = function['parameters']['properties'] assert function['name'] == name assert schema == original assert set(properties) == set(live.inputSchema['properties']) assert 'UID' in properties['uid'].get('description', '') assert 'search_emails' in properties['uid']['description'] assert 'within its account and folder' in properties['uid']['description'] assert 'Folder from the selected result' in properties['folder'].get('description', '') assert 'RFC Message-ID header' in properties['message_id'].get('description', '') assert 'not a UID' in properties['message_id']['description'] assert 'uid or message_id' in function['description'] def test_v3_media_schema_preserves_temporal_sampling_semantics(): media = next( schema for schema in compact_schemas(FUNCTION_TOOL_SCHEMAS) if schema['function']['name'] == 'inspect_media' ) function = media['function'] properties = function['parameters']['properties'] assert 'does not locate or count events' in function['description'] assert 'does not search' in properties['query']['description'] assert 'overview' in properties['sampling']['description'] assert 'up to 24' in properties['frames']['description'] assert properties['frames']['maximum'] == 24 assert 'midpoint' in properties['segments']['description'] def test_v3_media_overview_defaults_to_three_lossless_sheets(): import src.clean_agent_preview as module tool, arguments = module.normalize_preview_function_args( 'inspect_media', {'path': '/workspace/video.mp4', 'sampling': 'overview'}, ) assert tool == 'inspect_media' assert arguments['frames'] == 24 def test_v3_transcription_schema_rejects_media_output_semantics_in_prose(): transcript = next( schema for schema in compact_schemas(FUNCTION_TOOL_SCHEMAS) if schema['function']['name'] == 'transcribe_media' )['function'] assert 'does not inspect pixels or create media clips' in transcript['description'] assert '.jsonl' in transcript['parameters']['properties']['output_path']['description'] def test_compact_browser_distinguishes_element_refs_from_keyboard_keys(): original = next(s for s in FUNCTION_TOOL_SCHEMAS if s['function']['name'] == 'private_browser') browser = compact_schemas([original])[0]['function'] assert 'fill/click/press' not in browser['description'] assert 'key' in browser['description'] and 'Enter' in browser['description'] assert 'focused' in browser['parameters']['properties']['key']['description'] assert set(browser['parameters']['properties']) == set(original['function']['parameters']['properties']) def test_v3_browser_batch_schema_matches_executor_sequence_contract(): browser = next( schema for schema in compact_schemas(FUNCTION_TOOL_SCHEMAS) if schema['function']['name'] == 'private_browser' )['function'] commands = browser['parameters']['properties']['commands'] assert commands['items']['type'] == 'array' assert commands['items']['items'] == {'type': 'string'} assert '[["open"' in commands['description'] assert 'snapshot' in browser['description'] assert 'does not search the site' in browser['description'] def test_v3_browser_target_fields_preserve_selector_semantics(): browser = next(schema for schema in compact_schemas(FUNCTION_TOOL_SCHEMAS) if schema['function']['name'] == 'private_browser')['function'] for name in ('target', 'selector'): description = browser['parameters']['properties'][name].get('description', '') assert '@e2' in description assert 'CSS selector' in description assert 'not visible text' in description def test_long_skill_index_retains_readable_names_instead_of_truncated_json(): from src.clean_agent_preview import preview_tool_result_text index = '- **first-skill**: Description\n- **second-skill**: Description\n' + 'Long description ' * 800 text = preview_tool_result_text({'results': index, 'exit_code': 0}, 'manage_skills', {'action': 'list'}) assert text.startswith('- **first-skill**: Description\n- **second-skill**: Description\n') assert '[Tool result truncated' in text # Structured document results retain IDs and other payload fields. result = {'results': 'Saved', 'doc_id': 'doc-test'} assert json.loads(preview_tool_result_text(result, 'create_document', {})) == result def test_v3_schema_uses_configured_versioned_contract_root(tmp_path, monkeypatch): import src.clean_agent_preview as module contract_root = tmp_path / 'contract' contract_root.mkdir() (contract_root / 'eval_alltools_unseen_compare.py').write_text( "def tools_for_mode(tools, mode):\n" " assert mode == 'compact_contract_v5'\n" " tools[0]['function']['description'] = 'versioned-contract-loaded'\n" " return tools\n", encoding='utf-8', ) monkeypatch.setenv('ODYSSEUS_TOOL_CONTRACT_ROOT', str(contract_root)) module.contract_builder.cache_clear() try: notes = next( s for s in FUNCTION_TOOL_SCHEMAS if s['function']['name'] == 'manage_notes' ) compact = module.compact_schemas([notes])[0] assert compact['function']['description'].startswith('versioned-contract-loaded') finally: module.contract_builder.cache_clear() def test_v3_document_edit_schema_has_one_unambiguous_structured_form(): edit = next(s for s in compact_schemas(FUNCTION_TOOL_SCHEMAS) if s['function']['name'] == 'edit_document') parameters = edit['function']['parameters'] assert parameters['required'] == ['edits'] assert set(parameters['properties']) == {'edits'} def test_v3_ui_schema_advertises_only_policy_executable_panel_open(): ui = next(s for s in compact_schemas(FUNCTION_TOOL_SCHEMAS) if s['function']['name'] == 'ui_control') parameters = ui['function']['parameters'] assert parameters['required'] == ['action', 'name'] assert parameters['properties']['action']['enum'] == ['open_panel'] assert parameters['properties']['name']['enum'] == [ 'documents', 'gallery', 'calendar', 'email', 'sessions', 'notes', 'brain', 'skills', 'settings', 'theme', 'cookbook', ] assert set(parameters['properties']) == {'action', 'name'} def test_preview_contract_exposes_no_fallback_family_when_required_tool_is_unavailable(): notes = next(s for s in FUNCTION_TOOL_SCHEMAS if s['function']['name'] == 'manage_notes') preview = resolve_full_inventory_contract(schemas=[notes], policy=ToolPolicy()) routed = SimpleNamespace(unavailable=frozenset({'web_search'}), offered=frozenset()) scoped = scope_preview_contract(preview, routed, {'search_browser'}) assert scoped.offered == frozenset() assert scoped.schemas() == [] assert scoped.unavailable == {'web_search', 'capability:search_browser'} assert scoped.active_capabilities == {'search_browser'} def test_preview_contract_detects_active_family_removed_by_preview_permissions(): notes = next(s for s in FUNCTION_TOOL_SCHEMAS if s['function']['name'] == 'manage_notes') preview = resolve_full_inventory_contract(schemas=[notes], policy=ToolPolicy()) scoped = scope_preview_contract( preview, SimpleNamespace(unavailable=frozenset(), offered=frozenset()), {'search_browser'} ) assert scoped.offered == frozenset() assert scoped.unavailable == {'capability:search_browser'} def test_preview_contract_keeps_only_routed_family_from_trained_inventory(): schemas = [s for s in FUNCTION_TOOL_SCHEMAS if s['function']['name'] in { 'manage_notes', 'manage_calendar', 'web_search', }] preview = resolve_full_inventory_contract(schemas=schemas, policy=ToolPolicy()) routed = SimpleNamespace( unavailable=frozenset(), offered=frozenset({'manage_notes'}), required=frozenset({'manage_notes'}), capabilities=frozenset({'notes'}), required_read_operation=None, ) scoped = scope_preview_contract( preview, routed, {'notes'} ) assert scoped.offered == {'manage_notes'} assert scoped.required == {'manage_notes'} assert scoped.capabilities == {'notes'} assert {s['function']['name'] for s in scoped.schemas()} == {'manage_notes'} assert scoped.active_capabilities == {'notes'} def test_preview_contract_keeps_available_family_when_an_independent_family_is_unavailable(): schemas = [s for s in FUNCTION_TOOL_SCHEMAS if s['function']['name'] in { 'write_file', 'web_search', }] preview = resolve_full_inventory_contract(schemas=schemas, policy=ToolPolicy()) routed = SimpleNamespace( unavailable=frozenset({'manage_calendar'}), offered=frozenset({'write_file'}), required=frozenset(), capabilities=frozenset({'calendar', 'shell_files'}), required_read_operation=None, ) scoped = scope_preview_contract( preview, routed, {'calendar', 'shell_files'} ) assert scoped.offered == {'write_file'} assert {s['function']['name'] for s in scoped.schemas()} == {'write_file'} assert scoped.unavailable == {'manage_calendar', 'capability:calendar'} assert scoped.active_capabilities == {'calendar', 'shell_files'} @pytest.mark.asyncio async def test_stream_emits_incremental_text_and_persistable_history(monkeypatch): import src.clean_agent_preview as module requests = [] class Response: async def __aenter__(self): return self async def __aexit__(self, *args): pass def raise_for_status(self): pass async def aiter_lines(self): for text in ['Hello', ' there']: yield 'data: ' + json.dumps({'choices': [{'delta': {'content': text}}]}) yield 'data: [DONE]' class Client: def __init__(self, **kwargs): pass async def __aenter__(self): return self async def __aexit__(self, *args): pass def stream(self, *args, **kwargs): requests.append(kwargs['json']) assert kwargs['json']['temperature'] == 0 assert kwargs['json']['chat_template_kwargs']['enable_thinking'] is False return Response() monkeypatch.setattr(module.httpx, 'AsyncClient', Client) contract = resolve_full_inventory_contract(schemas=[], policy=ToolPolicy()) raw = [chunk async for chunk in stream_preview(endpoint_url='http://test', model='test', messages=[{'role': 'user', 'content': 'hi'}], headers={}, turn_contract=contract, session_id='test', owner='test', disabled_tools=set(), tool_policy=ToolPolicy())] events = [json.loads(s[6:]) for s in raw if '[DONE]' not in s] assert [e['delta'] for e in events if 'delta' in e] == ['Hello', ' there'] assert requests[0]['messages'][0]['content'].startswith('You are Odysseus.') metrics = next(e['data'] for e in events if e.get('type') == 'metrics') assert metrics['clean_v3_turn'] == [{'role': 'assistant', 'content': 'Hello there'}] assert raw[-1] == 'data: [DONE]\n\n' @pytest.mark.asyncio async def test_calendar_list_structured_result_owns_linked_terminal_render(monkeypatch): import src.clean_agent_preview as module requests = [] class Response: async def __aenter__(self): return self async def __aexit__(self, *args): pass def raise_for_status(self): pass async def aiter_lines(self): yield 'data: ' + json.dumps({'choices': [{'delta': {'tool_calls': [{ 'index': 0, 'id': 'calendar-1', 'function': { 'name': 'manage_calendar', 'arguments': '{"action":"list_events","start":"2026-09-14","end":"2026-09-21"}', }, }]}}]}) yield 'data: [DONE]' class Client: def __init__(self, **kwargs): pass async def __aenter__(self): return self async def __aexit__(self, *args): pass def stream(self, *args, **kwargs): requests.append(kwargs['json']) return Response() linked = ( 'Found 1 event(s) between 2026-09-14 and 2026-09-21:\n' '- 2026-09-15T11:00:00 -> 2026-09-15T12:30:00: ' '[Interior meeting](#event-event-123) (Personal)' ) async def execute(block, **kwargs): return 'manage_calendar', {'response': linked, 'exit_code': 0} monkeypatch.setattr(module.httpx, 'AsyncClient', Client) monkeypatch.setattr(module, 'execute_tool_block', execute) schema = next(s for s in FUNCTION_TOOL_SCHEMAS if s['function']['name'] == 'manage_calendar') contract = resolve_full_inventory_contract(schemas=[schema], policy=ToolPolicy()) raw = [chunk async for chunk in stream_preview( endpoint_url='http://test', model='test', messages=[{'role': 'user', 'content': 'whats my events this week'}], headers={}, turn_contract=contract, session_id='test', owner='test', disabled_tools=set(), tool_policy=ToolPolicy())] events = [json.loads(s[6:]) for s in raw if '[DONE]' not in s] assert len(requests) == 1 rendered = next(event['delta'] for event in events if 'delta' in event) assert '[Interior meeting](#event-event-123) — Sep 15, 11:00 AM–12:30 PM' in rendered assert '2026-09-15T11:00:00' not in rendered metrics = next(event['data'] for event in events if event.get('type') == 'metrics') assert metrics['clean_v3_turn'][-1] == {'role': 'assistant', 'content': rendered} @pytest.mark.asyncio async def test_whole_email_draft_is_protocol_bound_to_update_document(monkeypatch): import src.clean_agent_preview as module requests = [] responses = iter([ { 'choices': [{'delta': {'tool_calls': [{ 'index': 0, 'id': 'write-1', 'function': { 'name': 'update_document', 'arguments': '{"content":"To: alex@example.com\\nSubject: Re: Meeting\\n---\\nTomorrow works."}', }, }]}}], 'usage': {'prompt_tokens': 321, 'completion_tokens': 20}, }, { 'choices': [{'delta': {'content': 'Draft ready.'}}], 'usage': {'prompt_tokens': 400, 'completion_tokens': 9}, }, ]) class Response: def __init__(self, payload): self.payload = payload async def __aenter__(self): return self async def __aexit__(self, *args): pass def raise_for_status(self): pass async def aiter_lines(self): yield 'data: ' + json.dumps(self.payload) yield 'data: [DONE]' class Client: def __init__(self, **kwargs): pass async def __aenter__(self): return self async def __aexit__(self, *args): pass def stream(self, *args, **kwargs): requests.append(kwargs['json']) return Response(next(responses)) async def execute(block, **kwargs): assert block.tool_type == 'update_document' return 'update_document', { 'output': 'Document updated', 'exit_code': 0, 'doc_id': 'draft-1', 'title': 'Meeting', 'language': 'email', 'content': 'To: alex@example.com\nSubject: Re: Meeting\n---\nTomorrow works.', 'version': 2, } monkeypatch.setattr(module.httpx, 'AsyncClient', Client) monkeypatch.setattr(module, 'execute_tool_block', execute) schemas = [s for s in FUNCTION_TOOL_SCHEMAS if s['function']['name'] in { 'update_document', 'edit_document', 'suggest_document', 'ui_control', }] contract = resolve_full_inventory_contract(schemas=schemas, policy=ToolPolicy()) draft = SimpleNamespace( id='draft-1', title='Meeting', language='email', current_content='To: alex@example.com\nSubject: Re: Meeting\n---\nCan we meet tomorrow?', ) raw = [chunk async for chunk in stream_preview( endpoint_url='http://test', model='test', messages=[{'role': 'user', 'content': 'Write reply to this email'}], headers={}, turn_contract=contract, session_id='test', owner='test', disabled_tools=set(), tool_policy=ToolPolicy(), active_document=draft)] assert [tool['function']['name'] for tool in requests[0]['tools']] == ['update_document'] assert requests[0]['tool_choice'] == { 'type': 'function', 'function': {'name': 'update_document'}, } assert 'tools' not in requests[1] assert 'tool_choice' not in requests[1] events = [json.loads(s[6:]) for s in raw if '[DONE]' not in s] event_types = [event.get('type') for event in events] assert event_types.index('doc_update') < event_types.index('tool_output') doc_update = next(event for event in events if event.get('type') == 'doc_update') assert doc_update == { 'type': 'doc_update', 'doc_id': 'draft-1', 'title': 'Meeting', 'language': 'email', 'content': 'To: alex@example.com\nSubject: Re: Meeting\n---\nTomorrow works.', 'version': 2, } tool_output = next(event for event in events if event.get('type') == 'tool_output') assert tool_output['doc_id'] == 'draft-1' assert tool_output['document_content'].endswith('Tomorrow works.') metrics = next(e['data'] for e in events if e.get('type') == 'metrics') assert metrics['injected_tokens'] == 321 assert metrics['tool_schema_count'] == 1 assert metrics['agent_rounds'] == 2 assert metrics['tool_calls'] == 1 assert metrics['tokens_per_second'] >= 0 @pytest.mark.asyncio async def test_stream_forwards_successful_panel_open_to_ui(monkeypatch): import src.clean_agent_preview as module responses = iter([ {'choices': [{'delta': {'tool_calls': [{'index': 0, 'id': 'ui-1', 'function': { 'name': 'ui_control', 'arguments': '{"action":"open_panel","name":"gallery"}', }}]}}]}, {'choices': [{'delta': {'content': 'Opened the gallery.'}}]}, ]) class Response: def __init__(self, payload): self.payload = payload async def __aenter__(self): return self async def __aexit__(self, *args): pass def raise_for_status(self): pass async def aiter_lines(self): yield 'data: ' + json.dumps(self.payload) yield 'data: [DONE]' class Client: def __init__(self, **kwargs): pass async def __aenter__(self): return self async def __aexit__(self, *args): pass def stream(self, *args, **kwargs): return Response(next(responses)) async def execute(block, **kwargs): assert block.tool_type == 'ui_control' assert block.content == 'open_panel gallery' return 'ui_control', { 'ui_event': 'open_panel', 'panel': 'gallery', 'results': 'Opening gallery panel', 'exit_code': 0, } monkeypatch.setattr(module.httpx, 'AsyncClient', Client) monkeypatch.setattr(module, 'execute_tool_block', execute) schema = next(s for s in FUNCTION_TOOL_SCHEMAS if s['function']['name'] == 'ui_control') contract = resolve_full_inventory_contract(schemas=[schema], policy=ToolPolicy()) raw = [chunk async for chunk in stream_preview( endpoint_url='http://test', model='test', messages=[{'role': 'user', 'content': 'Open gallery'}], headers={}, turn_contract=contract, session_id='test', owner='test', disabled_tools=set(), tool_policy=ToolPolicy())] events = [json.loads(s[6:]) for s in raw if '[DONE]' not in s] assert any(e.get('type') == 'tool_start' and e.get('tool') == 'ui_control' for e in events) assert any( e.get('type') == 'ui_control' and (e.get('data') or {}).get('ui_event') == 'open_panel' and (e.get('data') or {}).get('panel') == 'gallery' for e in events ) @pytest.mark.asyncio async def test_stream_replaces_unsupported_write_completion(monkeypatch): import src.clean_agent_preview as module class Response: async def __aenter__(self): return self async def __aexit__(self, *args): pass def raise_for_status(self): pass async def aiter_lines(self): yield 'data: ' + json.dumps({'choices': [{'delta': {'content': 'All notes have been deleted.'}}]}) yield 'data: [DONE]' class Client: def __init__(self, **kwargs): pass async def __aenter__(self): return self async def __aexit__(self, *args): pass def stream(self, *args, **kwargs): return Response() monkeypatch.setattr(module.httpx, 'AsyncClient', Client) contract = resolve_full_inventory_contract(schemas=[], policy=ToolPolicy()) raw = [chunk async for chunk in stream_preview(endpoint_url='http://test', model='test', messages=[{'role': 'user', 'content': 'Delete all my notes.'}], headers={}, turn_contract=contract, session_id='test', owner='test', disabled_tools=set(), tool_policy=ToolPolicy())] events = [json.loads(s[6:]) for s in raw if '[DONE]' not in s] final = next(e['content'] for e in events if e.get('type') == 'final_response') assert final == denied_response() metrics = next(e['data'] for e in events if e.get('type') == 'metrics') assert metrics['clean_v3_turn'] == [{'role': 'assistant', 'content': denied_response()}] @pytest.mark.asyncio async def test_native_workspace_write_allows_evidence_backed_completion(monkeypatch): import src.clean_agent_preview as module requests = [] responses = iter([ {'choices': [{'delta': {'tool_calls': [{ 'index': 0, 'id': 'write', 'function': { 'name': 'write_file', 'arguments': json.dumps({ 'path': '/workspace/output.txt', 'content': 'verified result', }), }, }]}}]}, {'choices': [{'delta': {'content': 'Saved the workspace artifact.'}}]}, ]) class Response: def __init__(self, payload): self.payload = payload async def __aenter__(self): return self async def __aexit__(self, *args): pass def raise_for_status(self): pass async def aiter_lines(self): yield 'data: ' + json.dumps(self.payload) yield 'data: [DONE]' class Client: def __init__(self, **kwargs): pass async def __aenter__(self): return self async def __aexit__(self, *args): pass def stream(self, *args, **kwargs): requests.append(kwargs['json']) return Response(next(responses)) async def execute(block, **kwargs): assert block.tool_type == 'write_file' return 'write_file', {'output': 'Wrote output.txt', 'exit_code': 0} monkeypatch.setattr(module.httpx, 'AsyncClient', Client) monkeypatch.setattr(module, 'execute_tool_block', execute) schema = next(s for s in FUNCTION_TOOL_SCHEMAS if s['function']['name'] == 'write_file') contract = resolve_full_inventory_contract(schemas=[schema], policy=ToolPolicy()) raw = [chunk async for chunk in stream_preview( endpoint_url='http://test', model='test', messages=[{'role': 'user', 'content': 'Create a workspace artifact.'}], headers={}, turn_contract=contract, session_id='test', owner='test', disabled_tools=set(), tool_policy=ToolPolicy(), workspace='/tmp/workspace', client_runtime_context={ 'surface': 'odysseus-native', 'terminal_agent': True, 'unattended_mode': True, }, max_rounds=2, )] events = [json.loads(chunk[6:]) for chunk in raw if '[DONE]' not in chunk] assert not [event for event in events if event.get('type') == 'final_response'] assert any( event.get('delta') == 'Saved the workspace artifact.' for event in events ) native_system = requests[0]['messages'][0]['content'] assert 'batch independent known URLs' in native_system assert 'create required artifacts incrementally' in native_system @pytest.mark.asyncio async def test_policy_denied_batch_executes_nothing(monkeypatch): import src.clean_agent_preview as module class Response: async def __aenter__(self): return self async def __aexit__(self, *args): pass def raise_for_status(self): pass async def aiter_lines(self): calls = [ {'index': 0, 'id': 'read', 'function': {'name': 'manage_notes', 'arguments': '{"action":"list"}'}}, {'index': 1, 'id': 'delete', 'function': {'name': 'manage_notes', 'arguments': '{"action":"delete","id":"x"}'}}, ] yield 'data: ' + json.dumps({'choices': [{'delta': {'tool_calls': calls}}]}) yield 'data: [DONE]' class Client: def __init__(self, **kwargs): pass async def __aenter__(self): return self async def __aexit__(self, *args): pass def stream(self, *args, **kwargs): return Response() async def must_not_execute(*args, **kwargs): raise AssertionError('a partially permitted batch must not execute') monkeypatch.setattr(module.httpx, 'AsyncClient', Client) monkeypatch.setattr(module, 'execute_tool_block', must_not_execute) notes = next(s for s in FUNCTION_TOOL_SCHEMAS if s['function']['name'] == 'manage_notes') contract = resolve_full_inventory_contract(schemas=[notes], policy=ToolPolicy()) raw = [chunk async for chunk in stream_preview(endpoint_url='http://test', model='test', messages=[{'role': 'user', 'content': 'List my notes.'}], headers={}, turn_contract=contract, session_id='test', owner='test', disabled_tools=set(), tool_policy=ToolPolicy())] events = [json.loads(s[6:]) for s in raw if '[DONE]' not in s] assert not [e for e in events if e.get('type') in {'tool_start', 'tool_output'}] assert next(e['content'] for e in events if e.get('type') == 'final_response') == denied_response() @pytest.mark.asyncio async def test_native_bash_arguments_use_canonical_execution_content(monkeypatch): import src.clean_agent_preview as module command = "printf '%s\\n' ODY_SHELL_FILES_READONLY; cat /etc/hostname" responses = iter([ {'choices': [{'delta': {'tool_calls': [{'index': 0, 'id': 'shell', 'function': {'name': 'bash', 'arguments': json.dumps({'command': command})}}]}}]}, {'choices': [{'delta': {'content': 'Marker verified.'}}]}, ]) class Response: def __init__(self, payload): self.payload = payload async def __aenter__(self): return self async def __aexit__(self, *args): pass def raise_for_status(self): pass async def aiter_lines(self): yield 'data: ' + json.dumps(self.payload) yield 'data: [DONE]' class Client: def __init__(self, **kwargs): pass async def __aenter__(self): return self async def __aexit__(self, *args): pass def stream(self, *args, **kwargs): return Response(next(responses)) captured = [] async def execute(block, **kwargs): captured.append(block) return 'bash', {'output': 'ODY_SHELL_FILES_READONLY\nkierkegaard', 'exit_code': 0} monkeypatch.setattr(module.httpx, 'AsyncClient', Client) monkeypatch.setattr(module, 'execute_tool_block', execute) bash = next(s for s in FUNCTION_TOOL_SCHEMAS if s['function']['name'] == 'bash') contract = resolve_full_inventory_contract(schemas=[bash], policy=ToolPolicy()) raw = [chunk async for chunk in stream_preview(endpoint_url='http://test', model='test', messages=[{'role': 'user', 'content': 'Run the displayed read-only shell command.'}], headers={}, turn_contract=contract, session_id='test', owner='test', disabled_tools=set(), tool_policy=ToolPolicy())] assert len(captured) == 1 assert captured[0].tool_type == 'bash' assert captured[0].content == command @pytest.mark.asyncio async def test_native_stream_normalizes_single_clip_exports_before_validation(monkeypatch): import src.clean_agent_preview as module arguments = json.dumps({ "path": "/workspace/fixtures/video.mp4", "exports": json.dumps([{ "start": "10", "end": "16", "output_path": "/workspace/clip.mp4", }]), }) responses = iter([ {"choices": [{"delta": {"tool_calls": [{ "index": 0, "id": "clip-1", "function": {"name": "inspect_media", "arguments": arguments}, }]}}]}, {"choices": [{"delta": {"content": "Created the requested clip."}}]}, ]) class Response: def __init__(self, payload): self.payload = payload async def __aenter__(self): return self async def __aexit__(self, *args): pass def raise_for_status(self): pass async def aiter_lines(self): yield "data: " + json.dumps(self.payload) yield "data: [DONE]" class Client: def __init__(self, **kwargs): pass async def __aenter__(self): return self async def __aexit__(self, *args): pass def stream(self, *args, **kwargs): return Response(next(responses)) captured = [] async def execute(block, **kwargs): captured.append(block) return "inspect_media", {"output": "clip created", "exit_code": 0} monkeypatch.setattr(module.httpx, "AsyncClient", Client) monkeypatch.setattr(module, "execute_tool_block", execute) schema = next( item for item in FUNCTION_TOOL_SCHEMAS if item["function"]["name"] == "inspect_media" ) contract = resolve_full_inventory_contract(schemas=[schema], policy=ToolPolicy()) raw = [chunk async for chunk in stream_preview( endpoint_url="http://test", model="test", messages=[{"role": "user", "content": "Create the requested short video clip."}], headers={}, turn_contract=contract, session_id="test", owner="test", disabled_tools=set(), tool_policy=ToolPolicy(), workspace="/tmp/workspace", client_runtime_context={ "surface": "odysseus-native", "terminal_agent": True, "unattended_mode": True, }, max_rounds=2, )] events = [json.loads(chunk[6:]) for chunk in raw if "[DONE]" not in chunk] assert len(captured) == 1 assert json.loads(captured[0].content) == { "path": "/workspace/fixtures/video.mp4", "start": "10", "end": "16", "output_path": "/workspace/clip.mp4", } assert not [event for event in events if event.get("type") == "tool_output" and event.get("error")] @pytest.mark.asyncio async def test_native_stream_explains_how_to_recover_from_timestamp_free_export(monkeypatch): import src.clean_agent_preview as module malformed = json.dumps({ "path": "/workspace/fixtures/video.mp4", "exports": [{"output_path": "/workspace/diagram.png", "type": "diagram"}], }) corrected = json.dumps({ "path": "/workspace/fixtures/video.mp4", "query": "inspect the room layout", }) responses = iter([ {"choices": [{"delta": {"tool_calls": [{ "index": 0, "id": "bad-export", "function": {"name": "inspect_media", "arguments": malformed}, }]}}]}, {"choices": [{"delta": {"tool_calls": [{ "index": 0, "id": "inspect-first", "function": {"name": "inspect_media", "arguments": corrected}, }]}}]}, {"choices": [{"delta": {"content": "I inspected the video before authoring."}}]}, ]) class Response: def __init__(self, payload): self.payload = payload async def __aenter__(self): return self async def __aexit__(self, *args): pass def raise_for_status(self): pass async def aiter_lines(self): yield "data: " + json.dumps(self.payload) yield "data: [DONE]" class Client: def __init__(self, **kwargs): pass async def __aenter__(self): return self async def __aexit__(self, *args): pass def stream(self, *args, **kwargs): return Response(next(responses)) captured = [] async def execute(block, **kwargs): captured.append(block) return "inspect_media", {"output": "visual evidence", "exit_code": 0} monkeypatch.setattr(module.httpx, "AsyncClient", Client) monkeypatch.setattr(module, "execute_tool_block", execute) schema = next( item for item in FUNCTION_TOOL_SCHEMAS if item["function"]["name"] == "inspect_media" ) contract = resolve_full_inventory_contract(schemas=[schema], policy=ToolPolicy()) raw = [chunk async for chunk in stream_preview( endpoint_url="http://test", model="test", messages=[{"role": "user", "content": "Inspect a video and create a diagram."}], headers={}, turn_contract=contract, session_id="test", owner="test", disabled_tools=set(), tool_policy=ToolPolicy(), workspace="/tmp/workspace", client_runtime_context={ "surface": "odysseus-native", "terminal_agent": True, "unattended_mode": True, }, max_rounds=3, )] events = [json.loads(chunk[6:]) for chunk in raw if "[DONE]" not in chunk] errors = [ event["output"] for event in events if event.get("type") == "tool_output" and event.get("error") ] assert len(captured) == 1 assert json.loads(captured[0].content) == json.loads(corrected) assert len(errors) == 1 assert "explicit timestamp plus output_path" in errors[0] assert "write_file or python" in errors[0] @pytest.mark.asyncio async def test_native_stream_terminates_on_first_post_budget_tool_call(monkeypatch): import src.clean_agent_preview as module responses = iter([ {"choices": [{"delta": {"tool_calls": [{ "index": 0, "id": "first", "function": { "name": "inspect_media", "arguments": json.dumps({"path": "/workspace/fixtures/video.mp4"}), }, }]}}]}, {"choices": [{"delta": {"tool_calls": [{ "index": 0, "id": "over-budget", "function": { "name": "inspect_media", "arguments": json.dumps({ "path": "/workspace/fixtures/video.mp4", "query": "different evidence", }), }, }]}}]}, ]) class Response: def __init__(self, payload): self.payload = payload async def __aenter__(self): return self async def __aexit__(self, *args): pass def raise_for_status(self): pass async def aiter_lines(self): yield "data: " + json.dumps(self.payload) yield "data: [DONE]" class Client: def __init__(self, **kwargs): pass async def __aenter__(self): return self async def __aexit__(self, *args): pass def stream(self, *args, **kwargs): return Response(next(responses)) executions = [] async def execute(block, **kwargs): executions.append(block) return "inspect_media", {"output": "visual evidence", "exit_code": 0} monkeypatch.setattr(module, "NATIVE_TOOL_CALL_LIMIT", 1) monkeypatch.setattr(module.httpx, "AsyncClient", Client) monkeypatch.setattr(module, "execute_tool_block", execute) schema = next( item for item in FUNCTION_TOOL_SCHEMAS if item["function"]["name"] == "inspect_media" ) contract = resolve_full_inventory_contract(schemas=[schema], policy=ToolPolicy()) raw = [chunk async for chunk in stream_preview( endpoint_url="http://test", model="test", messages=[{"role": "user", "content": "Inspect the video."}], headers={}, turn_contract=contract, session_id="test", owner="test", disabled_tools=set(), tool_policy=ToolPolicy(), workspace="/tmp/workspace", client_runtime_context={ "surface": "odysseus-native", "terminal_agent": True, "unattended_mode": True, }, max_rounds=20, )] events = [json.loads(chunk[6:]) for chunk in raw if "[DONE]" not in chunk] assert len(executions) == 1 budget_errors = [ event for event in events if event.get("type") == "tool_output" and "budget exhausted" in event.get("output", "") ] assert len(budget_errors) == 1 final = [event for event in events if event.get("type") == "final_response"] assert len(final) == 1 assert "budget was exhausted" in final[0]["content"] @pytest.mark.asyncio async def test_model_choice_budget_preserves_evidence_for_final_answer_without_extra_execution(monkeypatch): from dataclasses import replace import src.clean_agent_preview as module def call(i): return {'index': i, 'id': f'call-{i}', 'function': { 'name': 'web_fetch', 'arguments': json.dumps({'url': f'https://example.org/{i}'})}} packets = iter([ {'choices': [{'delta': {'tool_calls': [call(i) for i in range(6)]}}]}, {'choices': [{'delta': {'tool_calls': [call(7)]}}]}, {'choices': [{'delta': {'content': 'Found six source pages; further details are unverified.'}}]}, ]) requests, executions = [], [] class Response: def __init__(self, payload): self.payload = payload async def __aenter__(self): return self async def __aexit__(self, *args): pass def raise_for_status(self): pass async def aiter_lines(self): yield 'data: ' + json.dumps(self.payload) yield 'data: [DONE]' class Client: def __init__(self, **kwargs): pass async def __aenter__(self): return self async def __aexit__(self, *args): pass def stream(self, *args, **kwargs): requests.append(kwargs['json']) return Response(next(packets)) async def execute(block, **kwargs): executions.append(block) return 'web_fetch', {'output': f'Source page {len(executions)}', 'exit_code': 0} monkeypatch.setattr(module.httpx, 'AsyncClient', Client) monkeypatch.setattr(module, 'execute_tool_block', execute) schema = next(s for s in FUNCTION_TOOL_SCHEMAS if s['function']['name'] == 'web_fetch') contract = replace(resolve_full_inventory_contract(schemas=[schema], policy=ToolPolicy()), routing_experiment='recent_model_choice') raw = [chunk async for chunk in stream_preview( endpoint_url='http://test', model='test', messages=[{'role':'user','content':'Read these public pages.'}], headers={}, turn_contract=contract, session_id='test', owner='test', disabled_tools=set(), tool_policy=ToolPolicy())] assert len(executions) == 6 assert len(requests) == 3 assert 'tools' not in requests[-1] assert any(m.get('role') == 'tool' and 'Source page' in m.get('content','') for m in requests[-1]['messages']) assert any('tool_budget_final_synthesis' in chunk for chunk in raw) assert any('Found six source pages' in chunk for chunk in raw) @pytest.mark.asyncio async def test_native_stream_reserves_remaining_budget_for_required_artifact(monkeypatch): import src.clean_agent_preview as module payloads = [ {"choices": [{"delta": {"tool_calls": [{ "index": 0, "id": f"search-{index}", "function": {"name": "web_search", "arguments": json.dumps({"query": f"topic {index}"})}, }]}}]} for index in range(module.NATIVE_ARTIFACT_RESEARCH_LIMIT) ] payloads.extend([ {"choices": [{"delta": {"tool_calls": [{ "index": 0, "id": "write", "function": {"name": "write_file", "arguments": json.dumps({ "path": "/tmp_workspace/results/out.md", "content": "evidence", })}, }]}}]}, {"choices": [{"delta": {"content": "Saved."}}]}, ]) responses = iter(payloads) requests = [] class Response: def __init__(self, payload): self.payload = payload async def __aenter__(self): return self async def __aexit__(self, *args): pass def raise_for_status(self): pass async def aiter_lines(self): yield "data: " + json.dumps(self.payload) yield "data: [DONE]" class Client: def __init__(self, **kwargs): pass async def __aenter__(self): return self async def __aexit__(self, *args): pass def stream(self, *args, **kwargs): requests.append(kwargs["json"]) return Response(next(responses)) async def execute(block, **kwargs): return block.tool_type, {"output": "ok", "exit_code": 0} monkeypatch.setattr(module.httpx, "AsyncClient", Client) monkeypatch.setattr(module, "execute_tool_block", execute) schemas = [ item for item in FUNCTION_TOOL_SCHEMAS if item["function"]["name"] in {"web_search", "write_file"} ] contract = resolve_full_inventory_contract(schemas=schemas, policy=ToolPolicy()) raw = [chunk async for chunk in stream_preview( endpoint_url="http://test", model="test", messages=[{"role": "user", "content": "Research and create the requested output."}], headers={}, turn_contract=contract, session_id="test", owner="test", disabled_tools=set(), tool_policy=ToolPolicy(), workspace="/tmp/workspace", client_runtime_context={ "surface": "odysseus-native", "terminal_agent": True, "unattended_mode": True, "completion_requirements": {"required_artifacts": ["/tmp_workspace/results"]}, }, max_rounds=32, )] events = [json.loads(chunk[6:]) for chunk in raw if "[DONE]" not in chunk] recovery = next( event for event in events if event.get("type") == "completion_recovery" and event.get("reason") == "artifact_write_budget_reserved" ) assert recovery["calls_used"] == module.NATIVE_ARTIFACT_RESEARCH_LIMIT assert [tool["function"]["name"] for tool in requests[12]["tools"]] == ["write_file"] assert requests[12]["tool_choice"] == { "type": "function", "function": {"name": "write_file"}, } assert any( event.get("type") == "tool_output" and event.get("tool") == "write_file" and not event.get("error") for event in events ) @pytest.mark.asyncio async def test_detailed_video_answer_requires_second_focused_inspection(monkeypatch): import src.clean_agent_preview as module responses = iter([ {"choices": [{"delta": {"tool_calls": [{"index": 0, "id": "broad", "function": { "name": "inspect_media", "arguments": json.dumps({"path": "/workspace/video.mp4", "frames": 1}), }}]}}]}, {"choices": [{"delta": {"content": "There were three events."}}]}, {"choices": [{"delta": {"tool_calls": [{"index": 0, "id": "focused", "function": { "name": "inspect_media", "arguments": json.dumps({ "path": "/workspace/video.mp4", "start": "00:00:10", "end": "00:00:20", "frames": 8, }), }}]}}]}, {"choices": [{"delta": {"content": "There were two verified events."}}]}, ]) class Response: def __init__(self, payload): self.payload = payload async def __aenter__(self): return self async def __aexit__(self, *args): pass def raise_for_status(self): pass async def aiter_lines(self): yield "data: " + json.dumps(self.payload) yield "data: [DONE]" requests = [] class Client: def __init__(self, **kwargs): pass async def __aenter__(self): return self async def __aexit__(self, *args): pass def stream(self, *args, **kwargs): requests.append(kwargs['json']) return Response(next(responses)) executions = [] async def execute(block, **kwargs): executions.append(block) return "inspect_media", { "output": "Video duration: 00:01:00.000\nTimestamped visual evidence.", "exit_code": 0, } monkeypatch.setattr(module.httpx, "AsyncClient", Client) monkeypatch.setattr(module, "execute_tool_block", execute) schema = next( item for item in FUNCTION_TOOL_SCHEMAS if item["function"]["name"] == "inspect_media" ) contract = resolve_full_inventory_contract(schemas=[schema], policy=ToolPolicy()) raw = [chunk async for chunk in stream_preview( endpoint_url="http://test", model="test", messages=[{"role": "user", "content": "How many events occur in this video?"}], headers={}, turn_contract=contract, session_id="test", owner="test", disabled_tools=set(), tool_policy=ToolPolicy(), workspace="/tmp/workspace", client_runtime_context={ "surface": "odysseus-native", "terminal_agent": True, "unattended_mode": True, }, max_rounds=4, )] events = [json.loads(chunk[6:]) for chunk in raw if "[DONE]" not in chunk] assert len(executions) == 2 assert len(requests) == 4 recovery = next(event for event in events if event.get('type') == 'completion_recovery') assert recovery['reason'] == 'detailed_video_requires_focused_inspection' final = next(event for event in events if event.get('type') == 'final_response') assert final['content'] == 'There were two verified events.' metrics = next(event['data'] for event in events if event.get('type') == 'metrics') assert metrics['clean_v3_turn'][-1]['content'] == 'There were two verified events.' assert not any( message.get('content') == 'There were three events.' for message in metrics['clean_v3_turn'] ) @pytest.mark.asyncio async def test_exact_native_read_gets_tool_free_synthesis_round(monkeypatch): from dataclasses import replace import src.clean_agent_preview as module responses = iter([ {"choices": [{"delta": {"tool_calls": [{"index": 0, "id": "read-1", "function": { "name": "read_file", "arguments": json.dumps({"path": "/workspace/sample.txt", "limit": 3}), }}]}}]}, {"choices": [{"delta": {"content": "Read the requested three lines."}}]}, ]) class Response: def __init__(self, payload): self.payload = payload async def __aenter__(self): return self async def __aexit__(self, *args): pass def raise_for_status(self): pass async def aiter_lines(self): yield "data: " + json.dumps(self.payload) yield "data: [DONE]" requests = [] class Client: def __init__(self, **kwargs): pass async def __aenter__(self): return self async def __aexit__(self, *args): pass def stream(self, *args, **kwargs): requests.append(kwargs['json']) return Response(next(responses)) executions = [] async def execute(block, **kwargs): executions.append(block) return "read_file", {"output": "alpha\nbeta\ngamma", "exit_code": 0} monkeypatch.setattr(module.httpx, "AsyncClient", Client) monkeypatch.setattr(module, "execute_tool_block", execute) schema = next(item for item in FUNCTION_TOOL_SCHEMAS if item["function"]["name"] == "read_file") contract = replace( resolve_full_inventory_contract(schemas=[schema], policy=ToolPolicy()), required=frozenset({"read_file"}), capabilities=frozenset({"shell_files"}), active_capabilities=frozenset({"shell_files"}), ) raw = [chunk async for chunk in stream_preview( endpoint_url="http://test", model="test", messages=[{"role": "user", "content": "Use read_file to read the first three lines of /workspace/sample.txt."}], headers={}, turn_contract=contract, session_id="test", owner="test", disabled_tools=set(), tool_policy=ToolPolicy(), workspace="/tmp/workspace", client_runtime_context={ "surface": "odysseus-native", "terminal_agent": True, "unattended_mode": True, }, max_rounds=4, )] assert len(executions) == 1 assert len(requests) == 2 assert 'tools' in requests[0] assert 'tools' not in requests[1] events = [json.loads(chunk[6:]) for chunk in raw if "[DONE]" not in chunk] assert any(event.get('delta') == 'Read the requested three lines.' for event in events) @pytest.mark.asyncio @pytest.mark.parametrize('recovery_kind', ['none', 'budget', 'duplicate']) async def test_context_overflow_retries_model_request_without_replaying_tool(monkeypatch, recovery_kind): from dataclasses import replace import httpx import src.clean_agent_preview as module requests, executions = [], [] overflow_request = 2 if recovery_kind == 'none' else 3 if recovery_kind == 'budget': monkeypatch.setattr(module, 'INTERACTIVE_TOOL_CALL_LIMIT', 1) async def handle(request): payload = json.loads(request.content) requests.append(payload) if len(requests) == overflow_request: return httpx.Response(400, json={'error': {'message': "This model's maximum context length is 4096 tokens. However, " "you requested 768 output tokens and your prompt contains at least " "3900 input tokens, for a total of at least 4668 tokens."}}) delta = ({'tool_calls': [{'index': 0, 'id': 'page-read', 'function': { 'name': 'private_browser', 'arguments': json.dumps({ 'action': 'open', 'url': 'https://example.org'})}}]} if len(requests) == 1 else {'content': 'The page was read.'}) if recovery_kind != 'none' and len(requests) == 2: args = ({'action': 'click', 'target': '@e2'} if recovery_kind == 'budget' else {'action': 'find', 'text': 'heading'}) delta = {'tool_calls': [{'index': 0, 'id': 'blocked-click', 'function': { 'name': 'private_browser', 'arguments': json.dumps(args)}}]} if recovery_kind == 'duplicate' and len(requests) == 1: delta['tool_calls'][0]['function']['arguments'] = json.dumps({'action': 'find', 'text': 'heading'}) return httpx.Response(200, text='data: ' + json.dumps({'choices': [{'delta': delta}]}) + '\n\ndata: [DONE]\n\n') original_client = httpx.AsyncClient monkeypatch.setattr(module.httpx, 'AsyncClient', lambda **kwargs: original_client( **kwargs, transport=httpx.MockTransport(handle))) async def execute(block, **kwargs): executions.append(block) return 'page', {'output': 'heading current page\n' + '木' * 7900, 'exit_code': 0} monkeypatch.setattr(module, 'execute_tool_block', execute) schema = next(s for s in FUNCTION_TOOL_SCHEMAS if s['function']['name'] == 'private_browser') contract = replace(resolve_full_inventory_contract(schemas=[schema], policy=ToolPolicy()), routing_experiment='recent_model_choice') prompt = 'Open https://example.org and report the page heading.' raw = [chunk async for chunk in stream_preview( endpoint_url='http://test', model='test', headers={}, turn_contract=contract, messages=[{'role': 'user', 'content': prompt}], session_id='test', owner='test', disabled_tools=set(), tool_policy=ToolPolicy())] assert any('The page was read.' in chunk for chunk in raw) assert len(executions) == 1 assert len(requests) == overflow_request + 1 before, after = requests[-2:] assert after['max_tokens'] == before['max_tokens'] assert len(json.dumps(after)) < len(json.dumps(before)) assert {'role': 'user', 'content': prompt} in after['messages'] assert after.get('tools') == before.get('tools') calls = {call['id'] for msg in after['messages'] for call in msg.get('tool_calls', [])} assert all(msg['tool_call_id'] in calls for msg in after['messages'] if msg['role'] == 'tool') assert not any('_harness_control' in msg for request in requests for msg in request['messages']) if recovery_kind == 'budget': assert any('budget exhausted' in str(msg.get('content')) for msg in after['messages']) @pytest.mark.asyncio @pytest.mark.parametrize('status,context_error,expected_requests', [ (400, True, 3), (413, True, 3), (400, False, 1), (401, True, 1), ]) async def test_context_recovery_is_bounded_and_not_used_for_other_errors( monkeypatch, status, context_error, expected_requests): import httpx import src.clean_agent_preview as module requests = [] async def handle(request): requests.append(request) message = ("This model's maximum context length is 16384 tokens. However, " "your messages resulted in 17000 tokens." if context_error else 'Invalid tool schema') return httpx.Response(status, json={'error': {'message': message}}) original_client = httpx.AsyncClient monkeypatch.setattr(module.httpx, 'AsyncClient', lambda **kwargs: original_client( **kwargs, transport=httpx.MockTransport(handle))) contract = resolve_full_inventory_contract(schemas=[], policy=ToolPolicy()) raw = [chunk async for chunk in stream_preview( endpoint_url='http://test', model='test', headers={}, turn_contract=contract, messages=[{'role': 'user', 'content': 'Hello'}], session_id='test', owner='test', disabled_tools=set(), tool_policy=ToolPolicy())] assert len(requests) == expected_requests assert not any('"type": "tool_start"' in chunk for chunk in raw) assert any('encountered an error' in chunk for chunk in raw) @pytest.mark.asyncio async def test_started_model_stream_is_never_retried(monkeypatch): import httpx import src.clean_agent_preview as module requests = [] class BrokenStream(httpx.AsyncByteStream): async def __aiter__(self): yield b'data: {"choices":[{"delta":{"content":"Partial answer"}}]}\n\n' raise httpx.ReadError('stream disconnected') async def handle(request): requests.append(request) return httpx.Response(200, stream=BrokenStream()) original_client = httpx.AsyncClient monkeypatch.setattr(module.httpx, 'AsyncClient', lambda **kwargs: original_client( **kwargs, transport=httpx.MockTransport(handle))) contract = resolve_full_inventory_contract(schemas=[], policy=ToolPolicy()) raw = [chunk async for chunk in stream_preview( endpoint_url='http://test', model='test', headers={}, turn_contract=contract, messages=[{'role': 'user', 'content': 'Hello'}], session_id='test', owner='test', disabled_tools=set(), tool_policy=ToolPolicy())] assert len(requests) == 1 assert sum('Partial answer' in chunk for chunk in raw) == 1 @pytest.mark.asyncio @pytest.mark.parametrize('transition,transition_result,can_repeat', [ ({'action': 'open', 'url': 'https://example.org/next'}, {}, True), ({'action': 'snapshot'}, {}, True), ({'action': 'click', 'target': '@e2'}, {'error': 'Element changed', 'exit_code': 1}, True), ({'action': 'read'}, {}, False), ({'action': 'open', 'url': 'https://example.org/next'}, {'blocked': True, 'error': 'Denied', 'exit_code': 1}, False), ]) async def test_browser_can_repeat_observation_after_navigation( monkeypatch, transition, transition_result, can_repeat): from dataclasses import replace import src.clean_agent_preview as module actions = [{'action': 'find', 'text': 'heading'}, transition, {'action': 'find', 'text': 'heading'}] packets = iter([{'choices': [{'delta': {'tool_calls': [{ 'index': 0, 'id': f'browser-{i}', 'function': {'name': 'private_browser', 'arguments': json.dumps(args)}}]}}]} for i, args in enumerate(actions)] + [{'choices': [{'delta': {'content': 'New page heading.'}}]}]) class Response: def __init__(self, payload): self.payload = payload async def __aenter__(self): return self async def __aexit__(self, *args): pass def raise_for_status(self): pass async def aiter_lines(self): yield 'data: ' + json.dumps(self.payload) yield 'data: [DONE]' class Client: def __init__(self, **kwargs): pass async def __aenter__(self): return self async def __aexit__(self, *args): pass def stream(self, *args, **kwargs): return Response(next(packets)) executions = [] async def execute(block, **kwargs): executions.append(json.loads(block.content)) return 'browser fixture', { 'output': f'Page evidence {len(executions)}', 'exit_code': 0, **(transition_result if len(executions) == 2 else {}), } monkeypatch.setattr(module.httpx, 'AsyncClient', Client) monkeypatch.setattr(module, 'execute_tool_block', execute) schema = next(s for s in FUNCTION_TOOL_SCHEMAS if s['function']['name'] == 'private_browser') contract = replace(resolve_full_inventory_contract(schemas=[schema], policy=ToolPolicy()), routing_experiment='recent_model_choice') raw = [chunk async for chunk in stream_preview( endpoint_url='http://test', model='test', headers={}, turn_contract=contract, messages=[{'role': 'user', 'content': 'Inspect the heading, open the next page, then inspect its heading.'}], session_id='test', owner='test', disabled_tools=set(), tool_policy=ToolPolicy())] assert executions == (actions if can_repeat else actions[:2]) if not transition_result.get('blocked'): assert any('already returned evidence' in chunk for chunk in raw) != can_repeat @pytest.mark.asyncio @pytest.mark.parametrize('recovers', [True, False]) async def test_empty_search_retry_preserves_evidence_dedupe_and_execution_budget(monkeypatch, recovers): from dataclasses import replace import src.clean_agent_preview as module import src.search as search from src.agent_tools.web_tools import WebSearchTool arguments = json.dumps({'query': 'documentation domains'}) packets = iter([ {'choices': [{'delta': {'tool_calls': [{'index': 0, 'id': f'search-{i}', 'function': {'name': 'web_search', 'arguments': arguments}}]}}]} for i in range(3 if recovers else 7) ] + [{'choices': [{'delta': {'content': 'Used the available source.'}}]}]) class Response: def __init__(self, payload): self.payload = payload async def __aenter__(self): return self async def __aexit__(self, *args): pass def raise_for_status(self): pass async def aiter_lines(self): yield 'data: ' + json.dumps(self.payload) yield 'data: [DONE]' class Client: def __init__(self, **kwargs): pass async def __aenter__(self): return self async def __aexit__(self, *args): pass def stream(self, *args, **kwargs): return Response(next(packets)) queries = [] def provider(query, **kwargs): queries.append(query) if len(queries) == 1 or not recovers: return 'No search results found.', [] return 'Example domains are for documentation.', [ {'title': 'Example Domains', 'url': 'https://www.iana.org/help/example-domains'}] async def execute(block, **kwargs): return 'web_search', await WebSearchTool().execute(arguments, {}) monkeypatch.setattr(module.httpx, 'AsyncClient', Client) monkeypatch.setattr(module, 'execute_tool_block', execute) monkeypatch.setattr(search, 'comprehensive_web_search', provider) schema = next(s for s in FUNCTION_TOOL_SCHEMAS if s['function']['name'] == 'web_search') contract = replace(resolve_full_inventory_contract(schemas=[schema], policy=ToolPolicy()), routing_experiment='recent_model_choice') raw = [chunk async for chunk in stream_preview( endpoint_url='http://test', model='test', headers={}, turn_contract=contract, messages=[{'role': 'user', 'content': 'Search for documentation domains.'}], session_id='test', owner='test', disabled_tools=set(), tool_policy=ToolPolicy())] events = [json.loads(chunk[6:]) for chunk in raw if '[DONE]' not in chunk] outputs = [event for event in events if event.get('type') == 'tool_output'] assert len(queries) == (2 if recovers else 6) assert outputs[0]['evidence_status'] == 'empty' if recovers: assert outputs[1]['evidence_status'] == 'available' assert 'iana.org' in outputs[1]['output'] assert outputs[2]['error'] and 'already returned evidence' in outputs[2]['output'] else: assert all(output['evidence_status'] == 'empty' for output in outputs[:6]) assert outputs[-1]['error'] and 'budget exhausted' in outputs[-1]['output'] assert not any('already returned evidence' in output['output'] for output in outputs) @pytest.mark.asyncio async def test_native_stream_does_not_reexecute_an_identical_successful_call(monkeypatch): import src.clean_agent_preview as module arguments = json.dumps({"path": "/workspace/fixtures/video.mp4"}) responses = iter([ {"choices": [{"delta": {"tool_calls": [{"index": 0, "id": "inspect-1", "function": { "name": "inspect_media", "arguments": arguments, }}]}}]}, {"choices": [{"delta": {"tool_calls": [{"index": 0, "id": "inspect-2", "function": { "name": "inspect_media", "arguments": arguments, }}]}}]}, {"choices": [{"delta": {"content": "Used the existing visual evidence."}}]}, ]) class Response: def __init__(self, payload): self.payload = payload async def __aenter__(self): return self async def __aexit__(self, *args): pass def raise_for_status(self): pass async def aiter_lines(self): yield "data: " + json.dumps(self.payload) yield "data: [DONE]" requests = [] class Client: def __init__(self, **kwargs): pass async def __aenter__(self): return self async def __aexit__(self, *args): pass def stream(self, *args, **kwargs): requests.append(kwargs['json']) return Response(next(responses)) executions = [] async def execute(block, **kwargs): executions.append(block) return "inspect_media", {"output": "four useful frames", "exit_code": 0} monkeypatch.setattr(module.httpx, "AsyncClient", Client) monkeypatch.setattr(module, "execute_tool_block", execute) schema = next( item for item in FUNCTION_TOOL_SCHEMAS if item["function"]["name"] == "inspect_media" ) contract = resolve_full_inventory_contract(schemas=[schema], policy=ToolPolicy()) raw = [chunk async for chunk in stream_preview( endpoint_url="http://test", model="test", messages=[{"role": "user", "content": "Inspect the video."}], headers={}, turn_contract=contract, session_id="test", owner="test", disabled_tools=set(), tool_policy=ToolPolicy(), workspace="/tmp/workspace", client_runtime_context={ "surface": "odysseus-native", "terminal_agent": True, "unattended_mode": True, }, max_rounds=3, )] events = [json.loads(chunk[6:]) for chunk in raw if "[DONE]" not in chunk] assert len(executions) == 1 duplicate = [ event for event in events if event.get("type") == "tool_output" and event.get("error") ] assert len(duplicate) == 1 assert "already returned evidence" in duplicate[0]["output"] assert 'tools' not in requests[2] assert requests[2]['messages'][-1]['role'] == 'user' assert 'finish from the evidence' in requests[2]['messages'][-1]['content'].lower() @pytest.mark.asyncio async def test_native_stream_permanently_withholds_tool_when_model_ignores_duplicate_correction( monkeypatch, ): import src.clean_agent_preview as module arguments = json.dumps({"path": "/workspace/fixtures/video.mp4"}) responses = iter([ {"choices": [{"delta": {"tool_calls": [{"index": 0, "id": "inspect-1", "function": { "name": "inspect_media", "arguments": arguments, }}]}}]}, {"choices": [{"delta": {"tool_calls": [{"index": 0, "id": "inspect-2", "function": { "name": "inspect_media", "arguments": arguments, }}]}}]}, {"choices": [{"delta": {"tool_calls": [{"index": 0, "id": "inspect-3", "function": { "name": "inspect_media", "arguments": arguments, }}]}}]}, {"choices": [{"delta": {"content": "Finished from the existing evidence."}}]}, ]) class Response: def __init__(self, payload): self.payload = payload async def __aenter__(self): return self async def __aexit__(self, *args): pass def raise_for_status(self): pass async def aiter_lines(self): yield "data: " + json.dumps(self.payload) yield "data: [DONE]" requests = [] class Client: def __init__(self, **kwargs): pass async def __aenter__(self): return self async def __aexit__(self, *args): pass def stream(self, *args, **kwargs): requests.append(kwargs['json']) return Response(next(responses)) executions = [] async def execute(block, **kwargs): executions.append(block) return "inspect_media", {"output": "visual evidence", "exit_code": 0} monkeypatch.setattr(module.httpx, "AsyncClient", Client) monkeypatch.setattr(module, "execute_tool_block", execute) schema = next( item for item in FUNCTION_TOOL_SCHEMAS if item["function"]["name"] == "inspect_media" ) contract = resolve_full_inventory_contract(schemas=[schema], policy=ToolPolicy()) raw = [chunk async for chunk in stream_preview( endpoint_url="http://test", model="test", messages=[{"role": "user", "content": "Inspect the video."}], headers={}, turn_contract=contract, session_id="test", owner="test", disabled_tools=set(), tool_policy=ToolPolicy(), workspace="/tmp/workspace", client_runtime_context={ "surface": "odysseus-native", "terminal_agent": True, "unattended_mode": True, }, max_rounds=4, )] events = [json.loads(chunk[6:]) for chunk in raw if "[DONE]" not in chunk] assert len(executions) == 1 duplicate_errors = [ event for event in events if event.get("type") == "tool_output" and "already returned evidence" in event.get("output", "") ] assert len(duplicate_errors) == 2 assert 'tools' not in requests[2] assert 'tools' not in requests[3] @pytest.mark.asyncio async def test_native_stream_terminates_after_calling_a_permanently_suppressed_tool(monkeypatch): import src.clean_agent_preview as module arguments = json.dumps({"path": "/workspace/fixtures/video.mp4"}) responses = iter([ {"choices": [{"delta": {"tool_calls": [{"index": 0, "id": f"inspect-{index}", "function": { "name": "inspect_media", "arguments": arguments, }}]}}]} for index in range(1, 5) ] + [{"choices": [{"delta": {"content": "Final answer from existing evidence."}}]}]) class Response: def __init__(self, payload): self.payload = payload async def __aenter__(self): return self async def __aexit__(self, *args): pass def raise_for_status(self): pass async def aiter_lines(self): yield "data: " + json.dumps(self.payload) yield "data: [DONE]" requests = [] class Client: def __init__(self, **kwargs): pass async def __aenter__(self): return self async def __aexit__(self, *args): pass def stream(self, *args, **kwargs): requests.append(kwargs["json"]) return Response(next(responses)) executions = [] async def execute(block, **kwargs): executions.append(block) return "inspect_media", {"output": "visual evidence", "exit_code": 0} monkeypatch.setattr(module.httpx, "AsyncClient", Client) monkeypatch.setattr(module, "execute_tool_block", execute) schema = next( item for item in FUNCTION_TOOL_SCHEMAS if item["function"]["name"] == "inspect_media" ) contract = resolve_full_inventory_contract(schemas=[schema], policy=ToolPolicy()) raw = [chunk async for chunk in stream_preview( endpoint_url="http://test", model="test", messages=[{"role": "user", "content": "Inspect the video."}], headers={}, turn_contract=contract, session_id="test", owner="test", disabled_tools=set(), tool_policy=ToolPolicy(), workspace="/tmp/workspace", client_runtime_context={ "surface": "odysseus-native", "terminal_agent": True, "unattended_mode": True, }, max_rounds=20, )] events = [json.loads(chunk[6:]) for chunk in raw if "[DONE]" not in chunk] assert len(executions) == 1 assert len(requests) == 5 assert 'tools' not in requests[4] assert 'best concise final answer' in requests[4]['messages'][-1]['content'].lower() final = [event for event in events if event.get("type") == "final_response"] assert final == [] metrics = next(event['data'] for event in events if event.get('type') == 'metrics') assert metrics['clean_v3_turn'][-1]['content'] == 'Final answer from existing evidence.' @pytest.mark.asyncio async def test_native_stream_stops_reexecuting_an_identical_failed_call(monkeypatch): import src.clean_agent_preview as module arguments = json.dumps({ "path": "/workspace/fixtures/video.mp4", "exports": [{ "timestamp": "00:00:00", "output_path": "/workspace/clip.mp4", }], }) responses = iter([ {"choices": [{"delta": {"tool_calls": [{"index": 0, "id": f"clip-{index}", "function": { "name": "inspect_media", "arguments": arguments, }}]}}]} for index in range(1, 5) ] + [{"choices": [{"delta": {"content": "Unable to create the clip."}}]}]) class Response: def __init__(self, payload): self.payload = payload async def __aenter__(self): return self async def __aexit__(self, *args): pass def raise_for_status(self): pass async def aiter_lines(self): yield "data: " + json.dumps(self.payload) yield "data: [DONE]" requests = [] class Client: def __init__(self, **kwargs): pass async def __aenter__(self): return self async def __aexit__(self, *args): pass def stream(self, *args, **kwargs): requests.append(kwargs['json']) return Response(next(responses)) executions = [] async def execute(block, **kwargs): executions.append(block) return "inspect_media", { "error": "a video clip export requires explicit start and end", "exit_code": 1, } monkeypatch.setattr(module.httpx, "AsyncClient", Client) monkeypatch.setattr(module, "execute_tool_block", execute) schema = next( item for item in FUNCTION_TOOL_SCHEMAS if item["function"]["name"] == "inspect_media" ) contract = resolve_full_inventory_contract(schemas=[schema], policy=ToolPolicy()) raw = [chunk async for chunk in stream_preview( endpoint_url="http://test", model="test", messages=[{"role": "user", "content": ( "Inspect the video and save /workspace/clip.mp4." )}], headers={}, turn_contract=contract, session_id="test", owner="test", disabled_tools=set(), tool_policy=ToolPolicy(), workspace="/tmp/workspace", client_runtime_context={ "surface": "odysseus-native", "terminal_agent": True, "unattended_mode": True, }, max_rounds=5, )] events = [json.loads(chunk[6:]) for chunk in raw if "[DONE]" not in chunk] assert len(executions) == 2 assert any(event.get('type') == 'tool_loop_recovery' for event in events) assert any(s['function']['name'] == 'inspect_media' for s in requests[3]['tools']) assert len(requests) == 5 assert any( event.get('type') == 'tool_output' and 'already failed twice' in event.get('output', '') and event.get('execution_attempted') is False for event in events ) @pytest.mark.asyncio async def test_native_stream_recovers_when_declared_workspace_artifact_is_missing( monkeypatch, tmp_path, ): import src.clean_agent_preview as module responses = iter([ {"choices": [{"delta": {"content": "The collision occurs at 00:00:15."}}]}, {"choices": [{"delta": {"tool_calls": [{"index": 0, "id": "clip-1", "function": { "name": "inspect_media", "arguments": json.dumps({ "path": "/workspace/fixtures/video.mp4", "start": "00:00:12", "end": "00:00:18", "output_path": "/workspace/clip.mp4", }), }}]}}]}, {"choices": [{"delta": {"content": "Saved /workspace/clip.mp4."}}]}, ]) requests = [] class Response: def __init__(self, payload): self.payload = payload async def __aenter__(self): return self async def __aexit__(self, *args): pass def raise_for_status(self): pass async def aiter_lines(self): yield "data: " + json.dumps(self.payload) yield "data: [DONE]" class Client: def __init__(self, **kwargs): pass async def __aenter__(self): return self async def __aexit__(self, *args): pass def stream(self, *args, **kwargs): requests.append(kwargs["json"]) return Response(next(responses)) async def execute(block, **kwargs): (tmp_path / "clip.mp4").write_bytes(b"video") return "inspect_media", { "output": "Created clip: /workspace/clip.mp4", "exit_code": 0, "output_path": "/workspace/clip.mp4", } monkeypatch.setattr(module.httpx, "AsyncClient", Client) monkeypatch.setattr(module, "execute_tool_block", execute) schema = next( item for item in FUNCTION_TOOL_SCHEMAS if item["function"]["name"] == "inspect_media" ) contract = resolve_full_inventory_contract(schemas=[schema], policy=ToolPolicy()) raw = [chunk async for chunk in stream_preview( endpoint_url="http://test", model="test", messages=[{"role": "user", "content": ( "Inspect /workspace/fixtures/video.mp4 and save the collision clip " "to /workspace/clip.mp4." )}], headers={}, turn_contract=contract, session_id="test", owner="test", disabled_tools=set(), tool_policy=ToolPolicy(), workspace=str(tmp_path), client_runtime_context={ "surface": "odysseus-native", "terminal_agent": True, "unattended_mode": True, }, max_rounds=4, )] assert (tmp_path / "clip.mp4").read_bytes() == b"video" assert len(requests) == 3 assert all( message['role'] != 'system' for message in requests[1]['messages'][1:] ) assert requests[1]['messages'][-1]['role'] == 'user' assert any( event.get("type") == "completion_recovery" for event in (json.loads(chunk[6:]) for chunk in raw if "[DONE]" not in chunk) ) events = [json.loads(chunk[6:]) for chunk in raw if "[DONE]" not in chunk] assert not any( event.get("type") == "final_response" and event.get("content") == denied_response() for event in events ) @pytest.mark.asyncio async def test_preview_propagates_active_document_and_truthfully_marks_tool_error(monkeypatch): import src.clean_agent_preview as module arguments = json.dumps({'edits': [{'find': 'alpha', 'replace': 'beta'}]}) responses = iter([ {'choices': [{'delta': {'tool_calls': [{'index': 0, 'id': 'edit', 'function': { 'name': 'edit_document', 'arguments': arguments, }}]}}]}, {'choices': [{'delta': {'content': 'Could not edit.'}}]}, ]) class Response: def __init__(self, payload): self.payload = payload async def __aenter__(self): return self async def __aexit__(self, *args): pass def raise_for_status(self): pass async def aiter_lines(self): yield 'data: ' + json.dumps(self.payload) yield 'data: [DONE]' class Client: def __init__(self, **kwargs): pass async def __aenter__(self): return self async def __aexit__(self, *args): pass def stream(self, *args, **kwargs): return Response(next(responses)) captured = {} async def execute(block, **kwargs): captured.update(kwargs) return 'edit_document', {'error': 'FIND did not match'} monkeypatch.setattr(module.httpx, 'AsyncClient', Client) monkeypatch.setattr(module, 'execute_tool_block', execute) edit = next(s for s in FUNCTION_TOOL_SCHEMAS if s['function']['name'] == 'edit_document') contract = resolve_full_inventory_contract(schemas=[edit], policy=ToolPolicy()) raw = [chunk async for chunk in stream_preview( endpoint_url='http://test', model='test', messages=[{'role': 'user', 'content': 'Edit my document.'}], headers={}, turn_contract=contract, session_id='test', owner='test', disabled_tools=set(), tool_policy=ToolPolicy(), active_document=SimpleNamespace( id='doc-123', title='Fixture', language='text', current_content='alpha', ))] events = [json.loads(s[6:]) for s in raw if '[DONE]' not in s] output = next(e for e in events if e.get('type') == 'tool_output') assert captured['active_document_id'] == 'doc-123' assert output['error'] is True assert output['exit_code'] == 1